Leetcode 995. Minimum Number of K Consecutive Bit Flips

原題

In an array A containing only 0s and 1s, a K-bit flip consists of choosing a (contiguous) subarray of length K and simultaneously changing every 0 in the subarray to 1, and every 1 in the subarray to 0.

Return the minimum number of K-bit flips required so that there is no 0 in the array. If it is not possible, return -1.

Example 1:

Input: A = [0,1,0], K = 1
Output: 2
Explanation: Flip A[0], then flip A[2].

Example 2:

Input: A = [1,1,0], K = 2
Output: -1
Explanation: No matter how we flip subarrays of size 2, we can't make the array become [1,1,1].

Example 3:

Input: A = [0,0,0,1,0,1,1,0], K = 3
Output: 3
Explanation:
Flip A[0],A[1],A[2]: A becomes [1,1,1,1,0,1,1,0]
Flip A[4],A[5],A[6]: A becomes [1,1,1,1,1,0,0,0]
Flip A[5],A[6],A[7]: A becomes [1,1,1,1,1,1,1,1]

Note:

1 <= A.length <= 30000
1 <= K <= A.length

分析

在給出一個0, 1序列後,可以對其中連續K個數字進行翻轉(0翻轉爲1,1翻轉爲0)。如果想要全部翻轉爲1,那麼直觀的一個感受就是從左到右,如果當前位置的數字是0,那麼可以作爲需要翻轉的連續K個數字的起點,因此最簡單的思路:

  1. 從左到右,尋找值爲0的下標index
  2. 對A[index, index + K-1]的數字進行翻轉
  3. 重複1, 2,直至數組值全爲1,或者index + K > A.length爲止

拍腦門解法

Note:需要注意的一點是,在進行翻轉的時候所採取的方法決定是否超時,用^進行異或操作會減少用時,特別地,與1異或表示對當前位進行翻轉

Accept代碼

class Solution {
public:
	bool flip(vector<int> & A, int K, int pos) {
		if (pos + K > A.size())
			return false;
		for (int i = pos; i < pos + K; ++i) {
			A[i] ^= 1;
		}
		return true;
		
	}

	int minKBitFlips(vector<int>& A, int K) {
		int n = A.size();
		int ans = 0, last_pos = 0; 
		
		for (int i = 0; i < n; ++i) {
			if (A[i] == 0) {
				ans++;
				if (!flip(A, K, i))
					return -1;
			}
		}
		return ans;

	}
};

在上面的解法中,之前用if else語句對A數組值進行翻轉,結果超時

bool flip(vector<int> & A, int K, int pos) {
		if (pos + K > A.size())
			return false;
		for (int i = pos; i < pos + K; ++i) {
			//超時代碼
			if(A[i] == 1)
				A[i] = 0;
			else 
				A[i] = 1;
		}
		return true;
		
	}

這個方法速度很慢,用時5216 ms,在超時的邊緣徘徊,於是借鑑別人的解法。

他山之石,可以攻玉(別人的答案)

在提交之後可以看到別人提交的答案,看完後再看看自己的代碼便更覺得羞澀難擋,因此去掉了貼自己代碼這一步,轉而分析別人代碼。

作者@awice

思路:
之前的貪心算法雖然可以成功運行,但是耗時較長(主要耗時的部分在翻轉A[index, index+K-1]部分,當次翻轉範圍與上次翻轉範圍存在重疊,當重疊部分進行翻轉的兩次的時候,相當於沒有翻轉,造成了時間的浪費)。因此用一個flip位來記錄當前K個連續數字是否已經翻轉,用一個hint數組來記錄翻轉。

算法流程:

  1. 初始化flip =0, hint數組爲0

  2. 遍歷數組A,

  3. flip ^= hint[i], 異或運算後flip位表示當前位置i是否被翻轉過,flip=0,未翻轉,flip=1,翻轉。hint數組用來記錄翻轉,(特別地,當A[i]需要翻轉時,hint[i + K]記錄翻轉截止範圍,見下面第4步驟)

  4. 判斷A[i] == flip,

    • A[i]與flip均爲0時,表示A[i] = 0且當前位未被翻轉,需要進行翻轉
    • A[i]與flip均爲1時,表示A[i] = 1且當前位已被翻轉(實際值爲0),需要再次翻轉

    若A[i] == flip, 表明需要進行翻轉

    • 翻轉次數加一,ans++
    • 判斷i+K是否超出A數組長度,超出則失敗返回-1
    • 對flip位進行翻轉
    • 對hint[i+K]位進行翻轉
class Solution {
public:
	int minKBitFlips(vector<int>& A, int K) {
		int n = A.size();
		vector<int> hint(n, 0);
		int ans = 0, flip = 0;

		for (int i = 0; i < n; ++i) {
			flip ^= hint[i];

			if (A[i] == flip) {
				ans++;
				if (i + K > n)
					return -1;
				flip ^= 1;
				if (i + K < n)
					hint[i + K] ^= 1;
			}
		}
		return ans;
	}
};

使用改進算法對重疊的部分進行索引記錄後,程序運行時間大大減少,爲112ms,縮短了近50倍。果然還是別人的飯吃起來香~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章