You have N
bulbs in a row numbered from 1
to N
. Initially, all the bulbs are turned off. We turn on exactly one bulb everyday until all bulbs are on after N
days.
You are given an array bulbs
of length N
where bulbs[i] = x
means that on the (i+1)th
day, we will turn on the bulb at position x
where i
is 0-indexed
and x
is 1-indexed.
Given an integer K
, find out the minimum day number such that there exists two turned on bulbs that have exactly K
bulbs between them that are all turned off.
If there isn't such day, return -1
.
Example 1:
Input: bulbs: [1,3,2] K: 1 Output: 2 Explanation: On the first day: bulbs[0] = 1, first bulb is turned on: [1,0,0] On the second day: bulbs[1] = 3, third bulb is turned on: [1,0,1] On the third day: bulbs[2] = 2, second bulb is turned on: [1,1,1] We return 2 because on the second day, there were two on bulbs with one off bulb between them.
Example 2:
Input: bulbs: [1,2,3] K: 1 Output: -1
Note:
1 <= N <= 20000
1 <= bulbs[i] <= N
bulbs
is a permutation of numbers from1
toN
.0 <= K <= 20000
這道題給了我們這樣一個場景,說是花園里有N個空槽,可以放花,每天放一朵開着的花,而且一旦放了就會一直開下去。不是按順序放花,而是給了我們一個數組 flowers,其中 flowers[i] = x 表示第i天放的花會在位置x。其實題目這里有誤,數組是從0開始的,而天數和位置都是從1開始的,所以正確的應該是第 i+1 天放的花會在位置x。然后給了我們一個整數k,讓我們判斷是否正好有兩朵盛開的花中間有k個空槽,如果有,返回當前天數,否則返回-1。博主剛開始想的是先用暴力破解來做,用一個狀態數組,如果該位置有花為1,無花為0,然后每增加一朵花,就遍歷一下狀態數組,找有沒有連續k個0,結果TLE了。這說明,應該等所有花都放好了,再來找才行,但是這樣僅用0和1的狀態數組是不行的,我們得換個形式。
我們用一個 days 數組,其中 days[i] = t 表示在 i+1 位置上會在第t天放上花,那么如果 days 數組為 [1 3 2],就表示第一個位置會在第一天放上花,第二個位置在第三天放上花,第三個位置在第二天放上花。我們想,在之前的狀態數組中,0表示沒放花,1表示放了花,而 days 數組中的數字表示放花的天數,那么就是說數字大的就是花放的時間晚,那么在當前時間i,所有大於i的是不是也就是可以看作是沒放花呢,這樣問題就迎刃而解了,我們來找一個 k+2 大小的子數組,除了首尾兩個數字,中間的k個數字都要大於首尾兩個數字即可,那么首尾兩個數字中較大的數就是當前的天數。left 和 right 是這個大小為 k+2 的窗口,初始化時 left 為0,right 為 k+1,然后i從0開始遍歷,這里循環的條件時 right 小於n,當窗口的右邊界越界后,循環自然需要停止。如果當 days[i] 小於 days[left],或者 days[i] 小於等於 days[right] 的時候,有兩種情況,一種是i在[left, right]范圍內,說明窗口中有數字小於邊界數字,這不滿足我們之前限定的條件,至於days[i]為何可以等於 days[right],是因為當i遍歷到 right 到位置時,說明中間的數字都是大於左右邊界數的,此時我們要用左右邊界中較大的那個數字更新結果 res。不管i是否等於 right,只要進了這個if條件,說明當前窗口要么是不合題意,要么是遍歷完了,我們此時要重新給 left 和 right 賦值,其中 left 賦值為i,right 賦值為 k+1+i,還是大小為 k+2 的窗口,繼續檢測。最后我們看結果 res,如果還是 INT_MAX,說明無法找到,返回 -1 即可,參見代碼如下:
解法一:
class Solution { public: int kEmptySlots(vector<int>& flowers, int k) { int res = INT_MAX, left = 0, right = k + 1, n = flowers.size(); vector<int> days(n, 0); for (int i = 0; i < n; ++i) days[flowers[i] - 1] = i + 1; for (int i = 0; right < n; ++i) { if (days[i] < days[left] || days[i] <= days[right]) { if (i == right) res = min(res, max(days[left], days[right])); left = i; right = k + 1 + i; } } return (res == INT_MAX) ? -1 : res; } };
下面這種方法用到了 TreeSet 來做,利用其自動排序的特點,然后用 lower_bound 和 upper_bound 進行快速的二分查找。 題目中的 flowers[i] = x 表示第 i+1 天放的花會在位置x。所以我們遍歷 flowers 數組,其實就是按照時間順序進行的,我們取出當前需要放置的位置 cur,然后在集合 TreeSet 中查找第一個大於 cur 的數字,如果存在的話,說明兩者中間點位置都沒有放花,而如果中間正好有k個空位的話,那么當前天數就即為所求。這是當 cur 為左邊界的情況,同樣,我們可以把 cur 當右邊界來檢測,在集合 TreeSet 中查找第一個小於 cur 的數字,如果二者中間有k個空位,也返回當前天數。需要注意的是,C++ 和 Java 中的 upper_bound 和 higher 是相同作用的,但是 lower_bound 和 lower 卻不太一樣。C++ 中的 lower_bound 找的是第一個不小於目標值的數字,所以可能會返回和目標值相同或者大於目標值的數字。只要這個數字不是第一個數字,然后我們往前退一位,就是要求的第一個小於目標值的數字,這相當於 Java 中的 lower 函數,參見代碼如下:
解法二:
class Solution { public: int kEmptySlots(vector<int>& flowers, int k) { set<int> s; for (int i = 0; i < flowers.size(); ++i) { int cur = flowers[i]; auto it = s.upper_bound(cur); if (it != s.end() && *it - cur == k + 1) { return i + 1; } it = s.lower_bound(cur); if (it != s.begin() && cur - *(--it) == k + 1) { return i + 1; } s.insert(cur); } return -1; } };
討論:這道題有一個很好的 follow up,就是改為最后的有k盆連續開花的是哪一天,就是k個連續不空的槽,博主沒有想出特別好的解法,只能采用暴力搜索的解法。比如就像解法一,求出了 days 數組后。我們可以遍歷每個長度為k的子數組,然后找出該子數組中最大的數字,然后找出所有的這些最大數字中的最小的一個,就是所求。或者,我們可以使用類似於合並區間的思想,遍歷 flowers 數組,每遍歷一個數字,如果跟現有的區間連續,就加入當前區間,直到出現某個區間的長度大於等於k了,則當前天數即為所求。如果各位看官大神們有更好的解法,一定要留言告知博主哈~
Github 同步地址:
https://github.com/grandyang/leetcode/issues/683
參考資料:
https://leetcode.com/problems/k-empty-slots/
https://leetcode.com/problems/k-empty-slots/discuss/107931/JavaC%2B%2B-Simple-O(n)-solution
https://leetcode.com/problems/k-empty-slots/discuss/107960/C%2B%2B-ordered-set-Easy-solution