题目描述
有 n
个盒子。给你一个长度为 n
的二进制字符串 boxes
,其中 boxes[i]
的值为 '0'
表示第 i
个盒子是 空 的,而 boxes[i]
的值为 '1'
表示盒子里有 一个 小球。
在一步操作中,你可以将 一个 小球从某个盒子移动到一个与之相邻的盒子中。第 i
个盒子和第 j
个盒子相邻需满足 abs(i - j) == 1
。注意,操作执行后,某些盒子中可能会存在不止一个小球。
返回一个长度为 n
的数组 answer
,其中 answer[i]
是将所有小球移动到第 i
个盒子所需的 最小 操作数。
每个 answer[i]
都需要根据盒子的 初始状态 进行计算。
样例
输入:boxes = "110"
输出:[1,1,3]
解释:每个盒子对应的最小操作数如下:
1) 第 1 个盒子:将一个小球从第 2 个盒子移动到第 1 个盒子,需要 1 步操作。
2) 第 2 个盒子:将一个小球从第 1 个盒子移动到第 2 个盒子,需要 1 步操作。
3) 第 3 个盒子:将一个小球从第 1 个盒子移动到第 3 个盒子,需要 2 步操作。
将一个小球从第 2 个盒子移动到第 3 个盒子,需要 1 步操作。共计 3 步操作。
输入:boxes = "001011"
输出:[11,8,5,4,3,4]
限制
n == boxes.length
1 <= n <= 2000
boxes[i]
为 ‘0’ 或 ‘1’。
算法1
(暴力) $O(n^2)$
- 按照题目描述逐一计算每个位置的值。
时间复杂度
- 每个位置需要 $O(n)$ 的时间计算,故总时间复杂度为 $O(n^2)$。
空间复杂度
- 需要 $O(n)$ 的额外空间存储答案。
C++ 代码
class Solution {
private:
int solve(const string &b, int n, int p) {
int res = 0;
for (int i = 0; i < n; i++)
if (b[i] == '1')
res += abs(i - p);
return res;
}
public:
vector<int> minOperations(string boxes) {
const int n = boxes.size();
vector<int> ans(n);
for (int i = 0; i < n; i++)
ans[i] = solve(boxes, n, i);
return ans;
}
};
算法2
(动态维护) $O(n)$
- 动态维护当前位置与上个位置值的差距。
- 首先计算第一个位置的值,当做后缀值
suf
,以及后缀中拥有1
的总个数suf_c
,初始化前缀值pre
以及前缀总个数pre_c
。 - 当前位置的答案就是前缀值与后缀值之和。然后维护前缀与后缀的变化:
- 如果当前位置为
1
,则前缀个数加 1,后缀个数减 1。 - 每一次移动,都需要后缀减少后缀所有的
1
带来的值,前缀增加前缀所有的1
带来的值。
- 如果当前位置为
时间复杂度
- 暴力计算出第一个位置的值,之后每个位置的值依赖于上一个位置计算的结果,每个位置均摊只需要常数的时间计算每个位置的值。
- 总时间复杂度为 $O(n)$。
空间复杂度
- 需要 $O(n)$ 的额外空间存储答案。
C++ 代码
class Solution {
public:
vector<int> minOperations(string boxes) {
const int n = boxes.size();
vector<int> ans(n);
int suf = 0, suf_c = 0;
for (int i = 0; i < n; i++)
if (boxes[i] == '1') {
suf += i;
suf_c++;
}
int pre = 0, pre_c = 0;
for (int i = 0; i < n; i++) {
ans[i] = pre + suf;
if (boxes[i] == '1') {
pre_c++;
suf_c--;
}
suf -= suf_c;
pre += pre_c;
}
return ans;
}
};