leetcode 995. K 连续位的最小翻转次数(差分)

在仅包含 0 和 1 的数组 A 中,一次 K 位翻转包括选择一个长度为 K 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0。

返回所需的 K 位翻转的最小次数,以便数组没有值为 0 的元素。如果不可能,返回 -1。

假hard真medium。这种题一定是从一边开始处理,逐步翻转。因为如果有解的话,翻转次数是一定的。所有的翻转区间一定是由一个或者多个有重叠的翻转区间(成为联通区间)组成。比如[1, 4]、[3, 6]和[5, 8]构成了一个联通区间[1, 8]。这个问题实际上可以拆分为处理这些联通区间。可以知道如果有解的话,联通区间的左右端点一定只被翻转了一次(翻转奇数次相当于翻转一次,翻转偶数次相当于没有翻转)。那么可以从左端点出发,对于每个点,如果为1则跳过,为0则翻转以当前位置为左端点长度为k的区间。如果能处理完该联通区间的话则有解。

对于这个题,有以上结论的话实际上直接遍历1到i + k - 1,当前为1跳过,为0则翻转。朴素地翻转显然会t,这里可以用差分的思想对区间进行翻转,在遍历的时候求前缀和得到当前点的翻转次数,偶数次忽略,奇数次则用a[i] = a[i] ^ 1来更新a[i],再进行判断。注意由于遍历范围并非1到n,因此最后一部分的a[i]需要单独更新。

class Solution {
public:
    int n, k, a[30005], diff[30005];
    void add(int x, int y)
    {
    	diff[x]++;
	    diff[y + 1]--;
    }
    int minKBitFlips(vector<int>& A, int K) {
        k = K, n = A.size();
        for(int i = 0; i < A.size(); i++) a[i + 1] = A[i];
        int ans = 0;
	    for(int i = 1; i + k - 1 <= n; i++)
	    {
		    diff[i] += diff[i - 1];
		    if(diff[i] & 1) a[i] ^= 1;
		    if(a[i] == 1) continue;
		    else
		    {
		    	add(i, i + k - 1);
		    	ans++;
		    	a[i] = 1;
		    }
	    }
	    for(int i = n - k + 2; i <= n; i++)
	    {
	    	diff[i] += diff[i - 1];
	    	if(diff[i] & 1) a[i] ^= 1;
	    }
        for(int i = 1; i <= n; i++) if(a[i] == 0) return -1;
        return ans;
    }
};
原文地址:https://www.cnblogs.com/lipoicyclic/p/14413188.html