得到连续 K 个 1 的最少相邻交换次数

129 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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^5
  • nums[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,则可以被划分为这样几个区域:

image.png

每三个一组

可以发现,对于每一组来说,只需要将0移动出去。即可。

而0移动的次数和1相关。

比如移动到左边以外去,若是左边有2个1,则需要移动两次。

0可以往左边移动或者向右边移动。

可以将每一个1中间的0统计一下有多少个,组成一个新的数组 zero。

然后,就知道了每一组的0移动到右边和左边对于需要的次数。

对于i[1,m]每一个0移动到右边需要mi+1每一个0移动到左边需要i次同一组的0移动次数相同,则答案为:mini=1,2,3,...,nki=xx+kzero[i]min(i,mi+1)对于 i \in [1, m] \\ 每一个0移动到右边需要 m - i + 1 次 \\ 每一个0移动到左边需要 i 次 同一组的0移动次数相同,\\ 则 答案为: \\ min_{i=1, 2, 3, ..., n-k}\sum_{i=x}^{x+k} zero[i] * min(i, m-i+1) \\

这样做的时间复杂度是 O(n2)O(n^2)

如何优化优化呢?

其实可以发现,当移动窗口的时候,

min(i,mi+1)min(i, m-i+1) 取向左移动的0,全部都减少了一个1的次数

min(i,mi+1)min(i, m-i+1) 取向右移动的0,全部都增加了一个1的次数

所以,这个就对应则在答案上变得很容易转移了。

因为 min(i,mi+1)min(i, m-i+1)是转移是固定的:

mid=(l+r)2;若是len(r,l)为偶数,则(l,mid)向左移动,(mid+1,r)向右移动若是len(r,l)为奇数,则(l,mid1)向左移动,mid两边都一样,(mid+1,r)向右移动设 mid = \frac{(l + r)} {2}; \\ 若是 len(r, l) 为偶数,则 (l, mid) 向左移动, (mid+1, r) 向右移动 \\ 若是 len(r, l) 为奇数,则 (l, mid-1) 向左移动,mid 两边都一样,(mid+1, r)向右移动

现在,你只需要记录一下前缀和,然后就可以在O(1)的时间里面进行转移了。

整体时间复杂度O(n)O(n)

代码

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;
    }
};