来源:力扣(LeetCode)
描述:
给你一个整数数组 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 <= 105
- nums[i] 要么是 0 ,要么是 1 。
- 1 <= k <= sum(nums)
方法:贪心 + 前缀和
思路
设数组 nums 的长度为 n,其中共有 m 个 1,它们在数组 nums 中的下标分别为 p0, p1, ,…, pm−1 。假设经过若干次交换后,数组 nums 中形成的 k 个连续的 1 的下标为 q, q + 1, …, q + k − 1。根据贪心的思想,要想交换次数最少,必须满足以下两个条件:
- 这 k 个连续的 1 的起始下标在 p 数组中是连续的,即下标为 pi , pi+1 ,…, pi+k−1 。
- k 个 1 的起始下标和结尾下标是按顺序对应的,即下标为 pi 的 1 交换到 q,下标为 pi+k−1 的 1 交换到 q + k - 1 ,中间的元素也需要按照顺序。
代码:
class Solution {
public:
int minMoves(vector<int>& nums, int k) {
vector<int> g;
vector<int> preSum(1, 0);
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == 1) {
g.emplace_back(i - g.size());
preSum.emplace_back(preSum.back() + g.back());
}
}
int m = g.size(), res = INT_MAX;
for (int i = 0; i <= m - k; i++) {
int mid = i + k / 2;
res = min(res, (1 - k % 2) * g[mid] + \
(preSum[i + k] - preSum[mid + 1]) - \
(preSum[mid] - preSum[i]));
}
return res;
}
};
执行用时:100 ms, 在所有 C++ 提交中击败了89.80%的用户
内存消耗:122.6 MB, 在所有 C++ 提交中击败了14.96%的用户
复杂度分析
时间复杂度: O(n),其中 n 是数组 nums 的长度。
空间复杂度: O(n)。
author:LeetCode-Solution