开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
得到连续 K 个 1 的最少相邻交换次数
给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1 。每一次移动,你可以选择 相邻 两个数字并将它们交换。
请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数。
示例 1:
输入: nums = [1,0,0,1,0,1], k = 2
输出: 1
解释: 在第一次操作时,nums 可以变成 [1,0,0,0,1,1] 得到连续两个 1 。
示例 2:
输入: nums = [1,0,0,0,0,0,1,1], k = 3
输出: 5
解释: 通过 5 次操作,最左边的 1 可以移到右边直到 nums 变为 [0,0,0,0,0,1,1,1] 。
示例 3:
输入: nums = [1,1,0,1], k = 2
输出: 0
解释: nums 已经有连续 2 个 1 了。
数据范围:
1 <= nums.length <= 10^5nums[i]要么是0,要么是1。1 <= k <= sum(nums)
思路
要使得连续的k个1在一起,其实我们不关心到底在哪儿。
只要满足连续的k个1一起,且移动次数最少。
那么,这个显然是一个窗口一样的形式,只不过在这k个1之间,有k-1个缝隙。
缝隙中有这么多个0存在。
1 0 0 0 1 1 0 0 1 0 1 0 1
对于每一个1来说,需要移动多少次呢?
假设当前k取3,则可以被划分为这样几个区域:
每三个一组
可以发现,对于每一组来说,只需要将0移动出去。即可。
而0移动的次数和1相关。
比如移动到左边以外去,若是左边有2个1,则需要移动两次。
0可以往左边移动或者向右边移动。
可以将每一个1中间的0统计一下有多少个,组成一个新的数组 zero。
然后,就知道了每一组的0移动到右边和左边对于需要的次数。
这样做的时间复杂度是
如何优化优化呢?
其实可以发现,当移动窗口的时候,
取向左移动的0,全部都减少了一个1的次数
取向右移动的0,全部都增加了一个1的次数
所以,这个就对应则在答案上变得很容易转移了。
因为 是转移是固定的:
现在,你只需要记录一下前缀和,然后就可以在O(1)的时间里面进行转移了。
整体时间复杂度
代码
class Solution {
public:
int minMoves(vector<int>& nums, int k) {
vector<int> ne;
int st = 0;
while (nums[st++] == 0);
for (int pre = nums[st], cnt = 0; st < nums.size(); st ++) {
if (nums[st] == 1) {
ne.push_back(cnt); cnt = 0;
} else cnt ++;
}
vector<int> sum(ne.size() + 2, 0);
for (int i = 1; i <= ne.size(); i ++)
sum[i] = sum[i-1] + ne[i-1];
#define S(l, r) (sum[(r)+1] - sum[(l)])
int ans = 0x3f3f3f3f, cur = 0;
--k;
for (int i = 0; i < k; i ++) {
cur += ne[i] * min(i + 1, k - i);
}
ans = cur;
for (int i = k; i < ne.size(); i ++) {
int l = i - k, r = i;
int mid = l + r >> 1;
cur -= S(l, mid);
cur += S(mid+k%2, r);
ans = min(ans, cur);
}
#undef S
return ans;
}
};