Minimum Operations to Equalize Binary String
Minimum Operations to Equalize Binary String
You are given a binary string s, and an integer k.
In one operation, you must choose exactly k different indices and flip each '0' to '1' and each '1' to '0'.
Return the minimum number of operations required to make all characters in the string equal to '1'. If it is not possible, return -1.
Example 1:
Input: s = "110", k = 1
Output: 1
Explanation:
- There is one
'0'ins. - Since
k = 1, we can flip it directly in one operation.
Example 2:
Input: s = "0101", k = 3
Output: 2
Explanation:
One optimal set of operations choosing k = 3 indices in each operation is:
- Operation 1: Flip indices
[0, 1, 3].schanges from"0101"to"1000". - Operation 2: Flip indices
[1, 2, 3].schanges from"1000"to"1111".
Thus, the minimum number of operations is 2.
Example 3:
Input: s = "101", k = 2
Output: -1
Explanation:
Since k = 2 and s has only one '0', it is impossible to flip exactly k indices to make all '1'. Hence, the answer is -1.
Constraints:
1 <= s.length <= 105s[i]is either'0'or'1'.1 <= k <= s.length
解题思路
赛时完全想不到可以抽象成最短路来做,当然也有偏数学或思维的方法,这个就更想不到了。
这题的整个思考过程和做法其实与 Minimum Reverse Operations 还挺像的。
首先由于每次可以任选下标,因此我们并不需要关心每个下标是 $0$ 还是 $1$,只需关心序列中个 $0$ 和 $1$ 的数量即可。进一步的,具有相同数量的 $0$ 和 $1$ 的序列我们都视为是同一个序列。因此每次操作时只需关心选多少个 $0$ 和 $1$,再进行翻转操作后就会从当前序列变成另外一个 $0$ 和 $1$ 数量不同的序列(当然也有可能变成自己本身)。
不妨用序列中 $0$ 的数量来表示序列的状态,假设当前序列中有 $u$ 个 $0$,在一次操作中选择了 $x$ 个 $0$ 和 $k-x$ 个 $1$,翻转后序列中 $0$ 的数量变成了 $u+k-2x$,我们就说状态 $u$ 可以到达 $u-x+y$(由于操作具有可逆性,状态 $u-x+y$ 也可以到达 $u$)。因此我们就可以根据每个状态能到达的状态进行连边(无向边)来建图,问题就变成了求从初始状态(初始序列中 $0$ 的数量)到 $0$ 状态(序列中没有 $0$)的最短距离。
考虑每个状态 $u$ 可以到达哪些状态,假设在一次操作中选择 $0$ 的数量为 $x$,那么 $1$ 的数量就是 $k-x$,其中 $x$ 和 $k-x$ 分别满足 $\displaylines{\begin{cases} 0 \leq x \leq \min\{k,u\} \\ 0 \leq k-x \leq \min\{k,n-u\} \end{cases}} \Rightarrow k - \min\{k,n-u\} \leq x \leq \min\{k,u\}$。记 $t = u+k-2 \cdot \min\{k,u\}$,意味着 $u$ 可以到达状态 $t, t + 2, \ldots, t + i \cdot 2, \ldots, t + (\min\{k,u\} - (k - \min\{k,n-u\})) \cdot 2$。如果在转移时直接暴力枚举所有可到达的状态,整个最短路的复杂度为 $O(n^2)$。
实际上并非每个状态都需要枚举,在最短路算法(bfs)中,如果某个状态之前被枚举过,则从初始状态到它的最短路是已经确定了的,所以我们可以跳过之前枚举过的状态。又因为可到达的状态是以 $t$ 为首项,$2$ 为公差的等差数列,具有连续性,如果我们可以动态维护还没被访问过的状态,那就可以从 $t$ 开始向右跳到下一个没访问过且与 $t$ 奇偶性相同的状态(因为每次都是加上 $2$ 的倍数,奇偶性不变),直到超过 $t + (\min\{k,u\} - (k - \min\{k,n-u\})) \cdot 2$。
可以用 std::set 或并查集来动态维护还没被访问过的点。
并查集做法 AC 代码如下,时间复杂度为 $O(n)$:
class Solution {
public:
int minOperations(string s, int k) {
int n = s.size();
int c = count(s.begin(), s.end(), '0');
queue<int> q({c});
vector<int> d(n + 1, 0x3f3f3f3f);
d[c] = 0;
vector<int> fa(n + 3);
iota(fa.begin(), fa.end(), 0);
auto find = [&](auto &&find, int x) -> int {
return fa[x] == x ? fa[x] : fa[x] = find(find, fa[x]);
};
while (!q.empty()) {
int u = q.front();
q.pop();
if (!u) break;
int l = u + k - 2 * min(k, u), r = l + (min(k, u) - k + min(k, n - u)) * 2;
while (l <= r) {
if (d[l] > d[u] + 1) {
d[l] = d[u] + 1;
q.push(l);
}
l = fa[l] = find(find, l + 2);
}
}
return d[0] < 0x3f3f3f3f ? d[0] : -1;
}
};
平衡树做法 AC 代码如下,时间复杂度为 $O(n \log{n})$:
class Solution {
public:
int minOperations(string s, int k) {
int n = s.size();
int c = count(s.begin(), s.end(), '0');
queue<int> q({c});
vector<int> d(n + 1, 0x3f3f3f3f);
d[c] = 0;
array<set<int>, 2> st({});
for (int i = 0; i <= n + 2; i++) {
st[i & 1].insert(i);
}
while (!q.empty()) {
int u = q.front();
q.pop();
if (!u) break;
int l = u + k - 2 * min(k, u), r = l + (min(k, u) - k + min(k, n - u)) * 2;
while (l <= r) {
if (d[l] > d[u] + 1) {
d[l] = d[u] + 1;
q.push(l);
}
st[l & 1].erase(l);
l = *st[l & 1].lower_bound(l + 2);
}
}
return d[0] < 0x3f3f3f3f ? d[0] : -1;
}
};
下面再简单介绍一下数学的做法,核心思路是求出操作次数的下界。
设初始序列中有 $c$ 个 $0$,一共操作了 $m$ 次,那么翻转下标的总数就是 $mk$。由于初始序列的 $c$ 个 $0$ 都需要翻转奇数次才能变成 $1$,同理 $n-c$ 个 $1$ 都需要翻转偶数次保持不变。假设 $c$ 个 $0$ 都通过 $1$ 次翻转变成 $1$,那么 $mk - c$ 一定要是偶数(因为剩下的都是 $1$)。这是其中一个性质。
下面求解 $m$ 的下界,需要求解 $mk$ 的上下界得到。先考虑 $mk$ 的下界,首先想要让 $c$ 个 $0$ 变成 $1$,那么翻转的下标次数至少为 $c$,因此有 $mk \geq c \Rightarrow m \geq \left\lceil \frac{c}{k} \right\rceil$。
再根据 $m$ 的奇偶性考虑 $mk$ 的上界。如果 $m$ 是偶数,那么序列中的 $0$ 至多翻转 $m-1$ 次,$1$ 至多翻转 $m$ 次,因此有 $mk \leq (m-1)c +m(n-c) \Rightarrow m \geq \left\lceil \frac{c}{n-k} \right\rceil$。结合 $m \geq \left\lceil \frac{c}{k} \right\rceil$ 有 $m$ 的下界 $m \geq \max\left\{ \left\lceil \frac{c}{k} \right\rceil, \left\lceil \frac{c}{n-k} \right\rceil \right\}$。另外需要注意的是,由于 $mk - c$ 是偶数,因此当 $m$ 为偶数时 $c$ 也应该是偶数。
如果 $m$ 是奇数,同理序列中的 $0$ 至多翻转 $m$ 次,$1$ 至多翻转 $m-1$ 次,因此有 $mk \leq mc +(m-1)(n-c) \Rightarrow m \geq \left\lceil \frac{n-c}{n-k} \right\rceil$,因此有 $m$ 的下界 $m \geq \max\left\{ \left\lceil \frac{c}{k} \right\rceil, \left\lceil \frac{n-c}{n-k} \right\rceil \right\}$。由于 $mk - c$ 是偶数,因此当 $m$ 为奇数时 $c$ 应该与 $k$ 具有相同奇偶性,这是因为 $mk - c \equiv 0 \pmod{2} \Rightarrow k \equiv c \pmod{2}$。
再根据 Gale-Ryser 定理可以证明下界是可以取到的。
AC 代码如下,时间复杂度为 $O(n)$:
class Solution {
public:
int minOperations(string s, int k) {
int n = s.size();
int c = count(s.begin(), s.end(), '0');
if (!c) return 0;
if (k == n) return c == n ? 1 : -1;
int ret = 0x3f3f3f3f;
if (c % 2 == 0) {
int m = max((c + k - 1) / k, (c + n - k - 1) / (n - k));
ret = m + m % 2;
}
if ((k - c) % 2 == 0) {
int m = max((c + k - 1) / k, (n - c + n - k - 1) / (n - k));
ret = min(ret, m | 1);
}
return ret < 0x3f3f3f3f ? ret : -1;
}
};
参考资料
BFS & 有序集合:https://leetcode.cn/problems/minimum-operations-to-equalize-binary-string/solutions/3767987/bfs-you-xu-ji-he-by-tsreaper-1isk/
网格图 DP【力扣双周赛 164】:https://www.bilibili.com/video/BV1aCaGzWEm4/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/19070197

浙公网安备 33010602011771号