以下是 LeetCode 3666 使二进制字符串全为 1 的最少操作次数 的 JavaScript 实现,采用 BFS + 并查集(DSU) 优化,时间复杂度 O(n \cdot \alpha(n)),可通过 n \leq 10^5 的数据。

---

解题思路

1. 状态定义
用当前字符串中 `'0'` 的数量 `cur` 作为状态。目标状态是 `0`(全为 `'1'`)。

2. 状态转移
每次操作翻转恰好 `k` 个位置,设其中 `x` 个是 `'0'`(变为 `'1'`),`k-x` 个是 `'1'`(变为 `'0'`)。

翻转后 `'0'` 的数量为:
\text{cur}' = \text{cur} + (k-x) - x = \text{cur} + k - 2x

`x` 的约束:
- 最多翻转 `cur` 个 `'0'`:x \leq \min(\text{cur}, k)
- 最多翻转 `n-cur` 个 `'1'`:k-x \leq n-\text{cur} \Rightarrow x \geq \max(0, k-n+\text{cur})

因此新状态的范围是 [l, r]:
- l = \text{cur} + k - 2 \cdot \min(\text{cur}, k)
- r = \text{cur} + k - 2 \cdot \max(0, k-n+\text{cur})

3. 关键观察:奇偶性
由于 \text{cur}' = \text{cur} + k - 2x,而 2x 是偶数,所以:
\text{cur}' \equiv \text{cur} + k \pmod{2}

同一 BFS 层的所有状态,能到达的下一层状态具有相同的奇偶性。因此只需维护两个集合(偶数 / 奇数),分别存储未访问的状态。

4. 并查集优化
需要高效地在一个范围内找到所有未访问的状态并批量删除。用两个并查集分别维护偶数和奇数:
- 删除状态 `v` 时,将其索引指向下一个同奇偶性的未访问状态
- 查询时从 `find(l)` 开始,不断取下一个,直到超出范围 `r`

---

JavaScript 代码

```javascript
/**
 * @param {string} s
 * @param {number} k
 * @return {number}
 */
var minOperations = function(s, k) {
    const n = s.length;
    
    // 统计 '0' 的数量
    let cnt0 = 0;
    for (let i = 0; i < n; i++) {
        if (s[i] === '0') cnt0++;
    }
    
    if (cnt0 === 0) return 0;
    
    // 并查集:分别维护未访问的偶数和奇数状态
    // 偶数 2*i 对应索引 i;奇数 2*i+1 对应索引 i
    const maxE = n >> 1;           // floor(n/2)
    const maxO = (n - 1) >> 1;     // floor((n-1)/2)
    
    const pe = new Array(maxE + 2);
    const po = new Array(maxO + 2);
    for (let i = 0; i < pe.length; i++) pe[i] = i;
    for (let i = 0; i < po.length; i++) po[i] = i;
    
    // 迭代版并查集 find(避免递归栈溢出)
    function findE(x) {
        if (x >= pe.length) return pe.length - 1;
        let root = x;
        while (pe[root] !== root) root = pe[root];
        while (x !== root) {
            const nxt = pe[x];
            pe[x] = root;
            x = nxt;
        }
        return root;
    }
    
    function findO(x) {
        if (x >= po.length) return po.length - 1;
        let root = x;
        while (po[root] !== root) root = po[root];
        while (x !== root) {
            const nxt = po[x];
            po[x] = root;
            x = nxt;
        }
        return root;
    }
    
    // 从集合中移除初始状态
    if ((cnt0 & 1) === 0) {
        pe[cnt0 >> 1] = findE((cnt0 >> 1) + 1);
    } else {
        po[(cnt0 - 1) >> 1] = findO(((cnt0 - 1) >> 1) + 1);
    }
    
    let q = [cnt0];
    let ans = 0;
    
    while (q.length > 0) {
        const nq = [];
        
        for (const cur of q) {
            if (cur === 0) return ans;
            
            // 计算翻转后 '0' 数量的可达范围 [l, r]
            const l = cur + k - 2 * Math.min(cur, k);
            const r = cur + k - 2 * Math.max(k - n + cur, 0);
            
            if (l > r) continue;
            
            if ((l & 1) === 0) {
                // 查询偶数集合:值 = 索引 * 2
                let nxt = findE(l >> 1);
                while (nxt <= maxE && (nxt << 1) <= r) {
                    nq.push(nxt << 1);
                    pe[nxt] = findE(nxt + 1);  // 删除该状态
                    nxt = findE(nxt);
                }
            } else {
                // 查询奇数集合:值 = 索引 * 2 + 1
                let nxt = findO((l - 1) >> 1);
                while (nxt <= maxO && ((nxt << 1) | 1) <= r) {
                    nq.push((nxt << 1) | 1);
                    po[nxt] = findO(nxt + 1);  // 删除该状态
                    nxt = findO(nxt);
                }
            }
        }
        
        q = nq;
        ans++;
    }
    
    return -1;
};
```

---

复杂度分析

项目    复杂度    
时间    O(n \cdot \alpha(n)) — 每个状态最多被访问并删除一次,并查集操作近似 O(1)    
空间    O(n) — 两个并查集数组 + BFS 队列    

其中 \alpha(n) 是阿克曼函数的反函数,在实际应用中可视为常数。

 

更多推荐