[LeetCode] Minimum Number of K Consecutive Bit Flips 連續K位翻轉的最小次數



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.html

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:ui

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].code

Example 3:htm

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]blog

Note:隊列

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



這道題博主最早在某家公司的 OA 上看到過(OA 出 Hard 題,也真是給跪了~),給了一個只有0和1的數組,又給了一個數字K,說是每次能夠翻轉任意連續K個位置的數字,問咱們多少步能夠把全部的0都翻轉爲1。這是道挺有意思的題目,背景能夠隨便換,好比什麼烙煎餅啊,翻煎雞蛋啊,都是同樣的問題。注意咱們翻轉的時候,一旦選定了起始點,那麼只能翻連着的K個位置,因爲沒法影響到前面的0的,因此須要在遇到0的時候,就進行翻轉,這就是明顯的貪婪算法的特徵,這要一遇到0,就立馬翻連續K個位置,好比對於數組 [0,1,0],K = 2,遇到位置0上的0,翻轉,變爲 [1,0,0],此時遇到位置1上的0,翻轉,變爲 [1,1,1],操做完成。想法有了,因而就吭哧吭哧的寫好了代碼,交給 OJ 大人審閱,結果被駁回,Time Limit Exceeded,什麼鬼?那麼仔細想一下吧,上面的這種解法到底哪裏最費時,固然是翻轉K個位置了,若K很是大,並且若須要翻轉的位置不少的話,將很是的不高效。因此當務之急就是想辦法代替真實的翻轉,最簡單的方法就是記錄起始翻轉的位置,咱們使用一個長度相同的數組 isFlipped,其中 isFlipped[i] 表示在原數組i位置上進行了K個連續翻轉。如今雖然知道了其實翻轉的位置,可是因爲是連續翻轉K個,以後的位置也被翻轉,好比 [0,1,0] 在位置0翻轉後,變爲了 [1,0,0],那麼位置1就從以前的1變爲0了,此時位置1也須要翻轉了,那麼咱們怎麼知道非起始翻轉位置的數字當前是0仍是1呢,這時就要引入另外一個變量 curFlipped 了,表示當前位置的數字跟原數組相比是否被翻轉了。因此一旦咱們決定對當前位置進行翻轉,那麼須要將 isFlipped[i] 標記爲1,而且翻轉 curFlipped,方法是'異或'上1,舉個例子來講對於數組 [0,1,0,1], K=3 來講,當咱們對位置0進行翻轉,數組變爲 [1,0,1,0],那麼在位置1的時候,curFlipped 是1,表示此時0相對於原來的1是翻轉了,那麼若咱們在爲1時再次進行翻轉,數組變爲 [1,1,0,1],此時 curFlipped 變爲0了,代表位置2上的0對於原數組的位置2上的0來講沒有翻轉(雖然實際上咱們翻轉了兩次),那麼咱們怎麼知道某個位置應不該該翻轉呢?須要根據 curFlipped 的值和原數組 A[i] 的值進行比較來判斷,此時有兩種狀況:ip

  • 當 curFlipped 爲0,表示沒有翻轉,且原數組 A[i] 爲0,此時就須要翻轉i位置。

  • 當 curFlipped 爲1,表示翻轉過了,而原數組 A[i] 爲1,表示雖然原來是1,可是當前位置受以前翻轉的影響變成了0,此時就須要翻轉回來。

仔細觀察上面兩種狀況能夠發現 curFlipped 和 A[i] 同奇同偶的時候就須要翻轉,那麼兩種狀況能夠合成一個表達式,curFlipped%2 等於 A[i] 時翻轉。還須要弄清楚當何時就沒法翻轉了,當 i+K 大於n的時候,好比 [1,1,0,1], k=3 時,在位置2的時候 i+k>n(2+3>4)了,表示沒法翻轉了,直接返回 -1 便可。最後須要注意的是,curFlipped 受影響的位置只有在大小爲K的窗口中,一旦超過了,是須要還原 curFlipped 的狀態的。好比 [0,0,0,1], K=2 時,在位置0翻轉後變爲 [1,1,0,1],那麼到位置2的時候,因爲已經不受位置0翻轉的影響了,此時的 curFlipped 應該變回0,因此只要'異或'上 isFlipped[0] 進行翻轉,因此咱們在循環開始前,首先檢測 i>=K 是否成立,成立的話就讓 curFlipped '異或'上 isFlipped[i-K] 進行狀態還原,即使是位置 i-K 未進行過翻轉,'異或'上0也不會改變 curFLipped 的狀態,參見代碼以下:



解法一:

class Solution {
public:
    int minKBitFlips(vector<int>& A, int K) {
        int res = 0, n = A.size(), curFlipped = 0;
        vector<int> isFlipped(n);
        for (int i = 0; i < n; ++i) {
            if (i >= K) curFlipped ^= isFlipped[i - K];
            if (curFlipped % 2 == A[i]) {
                if (i + K > n) return -1;
                isFlipped[i] = 1;
                curFlipped ^= 1;
                ++res;
            }
        }
        return res;
    }
};



根據上面的分析,對於每一個位置i,咱們其實只關心 i-K 位置是不是起始翻轉的位置,以前什麼狀況並不 care,那麼就不必用整個數組來保存全部的起始翻轉位置,只須要維護一個大小爲K的窗口,將在此窗口內的起始翻轉位置保存便可,超出範圍的就扔掉。那麼咱們可使用一個 queue 來保存窗口內的起始位置,在遍歷的過程當中,首先檢測隊首元素是否超出了範圍,是的話就扔掉,不然就接着判斷當前位置是否須要翻轉,判斷的方法是看此窗口中起始翻轉的個數是否跟 A[i] 同奇同偶,推導方式跟上面解法中相似,這裏就不過多講解了,以後就判斷所剩位置是否還有K個,沒有就返回 -1。而後將當前位置加入隊列,結果 res 自增1便可,參見代碼以下:



解法二:

class Solution {
public:
    int minKBitFlips(vector<int>& A, int K) {
        int res = 0, n = A.size();
        queue<int> q;
        for (int i = 0; i < n; ++i) {
            if (!q.empty() && i >= (q.front() + K)) q.pop();
            if (q.size() % 2 == A[i]) {
                if (i + K > n) return -1;
                q.push(i);
                ++res;
            }
        }
        return res;
    }
};



咱們還能夠進一步的優化空間,連隊列 queue 都不須要,直接在原數組上修改,從而達到標記的目的,在解法一中,咱們是新建了一個數組,對於起始翻轉位置標記爲1,其他爲0。這裏因爲原數組已經使用了0和1,咱們能夠對於起始位置,將 A[i] 加上2,這樣起始翻轉位置的值就成了2或者3,跟原來的0和1就區分開了,那麼只要將 A[i] 除以2,根據商是1仍是0,就能夠知道該位置是不是起始翻轉位置,其他的地方基本相同,不過這裏的 flipped 更新並無用異或,而是直接用的加減,爲了 diversity 也是拼了,參見代碼以下:



解法三:

class Solution {
public:
    int minKBitFlips(vector<int>& A, int K) {
        int res = 0, n = A.size(), flipped = 0;
        for (int i = 0; i < n; ++i) {
            if (i >= K) flipped -= A[i - K] / 2;
            if (flipped % 2 == A[i]) {
                if (i + K > n) return -1;
                A[i] += 2;
                ++flipped;
                ++res;
            }
        }
        return res;
    }
};



相似題目:

Bulb Switcher



參考資料:

https://leetcode.com/problems/minimum-number-of-k-consecutive-bit-flips/

https://leetcode.com/problems/minimum-number-of-k-consecutive-bit-flips/discuss/239284/C%2B%2B-greedy-stack-and-O(1)-memory

https://leetcode.com/problems/minimum-number-of-k-consecutive-bit-flips/discuss/239117/Java-O(n)-Sliding-Window-Solution-using-Queue

https://leetcode.com/problems/minimum-number-of-k-consecutive-bit-flips/discuss/238609/JavaC%2B%2BPython-One-Pass-and-O(1)-Space



LeetCode All in One 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索