水果成籃
你正在探訪一家農場,農場從左到右種植了一排果樹。這些樹用一個整數數組 fruits 表示,其中 fruits[i] 是第 i 棵樹上的水果 種類 。
你想要盡可能多地收集水果。然而,農場的主人設定了一些嚴格的規矩,你必須按照要求采摘水果:
你只有 兩個 籃子,并且每個籃子只能裝 單一類型 的水果。每個籃子能夠裝的水果總量沒有限制。
你可以選擇任意一棵樹開始采摘,你必須從 每棵 樹(包括開始采摘的樹)上 恰好摘一個水果 。采摘的水果應當符合籃子中的水果類型。每采摘一次,你將會向右移動到下一棵樹,并繼續采摘。
一旦你走到某棵樹前,但水果不符合籃子的水果類型,那么就必須停止采摘。
給你一個整數數組 fruits ,返回你可以收集的水果的 最大 數目。
示例 1:
輸入:fruits = [1,2,1]
輸出:3
解釋:可以采摘全部 3 棵樹。
示例 2:
輸入:fruits = [0,1,2,2]
輸出:3
解釋:可以采摘 [1,2,2] 這三棵樹。
如果從第一棵樹開始采摘,則只能采摘 [0,1] 這兩棵樹。
示例 3:
輸入:fruits = [1,2,3,2,2]
輸出:4
解釋:可以采摘 [2,3,2,2] 這四棵樹。
如果從第一棵樹開始采摘,則只能采摘 [1,2] 這兩棵樹。
示例 4:
輸入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
輸出:5
解釋:可以采摘 [1,2,1,1,2] 這五棵樹。
提示:
1 <= fruits.length <= 105
0 <= fruits[i] < fruits.length
解題思路:
1. 使用兩個指針left和right表示窗口的左右邊界,初始時都指向數組的起始位置。
2. 使用一個哈希表(或者數組,因為題目說明fruits[i]的范圍,但為了通用性,這里用哈希表)來記錄當前窗口內各種水果的出現次數。
3. 右指針right不斷向右移動,將遇到的水果加入窗口(即更新哈希表)。
4. 當窗口內水果種類超過2種時,移動左指針left,直到窗口內水果種類減少到2種(每次移動左指針,將左指針指向的水果從哈希表中移除,如果該水果數量減為0,則從哈希表中刪除該種類)。
5. 在移動過程中,記錄窗口的最大長度(即right-left+1),并更新最大長度。
具體步驟
初始化left=0, maxLen=0
使用一個HashMap來記錄當前窗口內每種水果的出現次數,也可以使用數組(因為fruits[i] < fruits.length,所以數組長度可以設為fruits.length,但為了通用,這里用HashMap)
遍歷right從0到len-1:
將fruits[right]加入HashMap(計數+1)
當HashMap的大小(即不同水果的種類)超過2時,開始移動左指針:
將fruits[left]從HashMap中移除(計數-1,如果減到0,則刪除該鍵)
left++
此時窗口內的水果種類不超過2,更新最大長度:maxLen = max(maxLen, right-left+1)
但是,注意:在移動左指針的過程中,我們只需要移除直到窗口內水果種類重新變為2即可。因此,在移動左指針時,使用while循環直到HashMap大小<=2。
然而,實際上,由于每次只增加一個元素,因此當種類超過2時,我們只需要移動一次左指針(但是注意,可能一次移動并不夠,因為可能移除了一個水果后,窗口內仍然有兩種以上?實際上,由于我們每次移除一個,所以當種類超過2時,我們一直移動左指針直到種類等于2)。因此,內層使用while循環。
優化:由于我們使用HashMap記錄每個水果出現的次數,當移除left位置的水果時,如果該水果在窗口內出現的次數減到0,那么我們就從HashMap中移除這個鍵,這樣HashMap的大小就是當前窗口內的水果種類數。
代碼實現
public static int totalFruit(int[] fruits) {int n = fruits.length;// 頻率數組:記錄當前窗口中每種水果出現的次數// 數組大小設為n,因為水果種類范圍是[0, n)int[] freq = new int[n];// 左指針:標記窗口起始位置int left = 0;// 最大長度:記錄滿足條件的最大窗口長度int maxLen = 0;// 種類計數:記錄當前窗口中的水果種類數int count = 0;// 右指針遍歷整個數組for (int right = 0; right < n; right++) {// 如果當前水果在窗口中首次出現,增加種類計數if (freq[fruits[right]] == 0) {count++;}// 增加當前水果的頻率計數freq[fruits[right]]++;// 當窗口中水果種類超過2種時,需要縮小窗口while (count > 2) {// 減少左指針指向的水果的頻率計數freq[fruits[left]]--;// 如果某種水果頻率降為0,減少種類計數if (freq[fruits[left]] == 0) {count--;}// 左指針右移,縮小窗口left++;}// 更新最大窗口長度maxLen = Math.max(maxLen, right - left + 1);}return maxLen;}
代碼解析: 初始化數據結構:
freq 數組:記錄當前窗口中每種水果出現的次數(索引對應水果種類)
left 指針:標記滑動窗口的起始位置
maxLen:記錄滿足條件的最長子數組長度
count:記錄當前窗口中的水果種類數
滑動窗口處理:
右指針擴展:right 指針從左到右遍歷數組
新種類檢測:當遇到窗口中未出現的水果種類時,增加種類計數 count
頻率更新:增加當前水果在頻率數組中的計數
窗口收縮:當種類數超過2時,移動左指針縮小窗口:
減少左指針指向水果的頻率計數
如果某種水果頻率降為0,減少種類計數
左指針右移
更新最大長度:每次循環后計算當前窗口長度并更新最大值
算法特性:
時間復雜度:O(n) - 每個元素最多被訪問兩次(右指針和左指針各一次)
空間復雜度:O(n) - 使用頻率數組存儲計數信息
最優性:滑動窗口確保在單次遍歷中高效找到最優解
算法原理:
該問題本質是尋找最多包含兩種不同元素的最長連續子數組。滑動窗口技術通過以下步驟解決:
窗口擴展:右指針不斷向右移動,擴展窗口范圍
狀態維護:使用頻率數組實時跟蹤窗口內各元素數量
種類控制:當窗口內元素種類超過2時,左指針向右移動收縮窗口
結果更新:每次窗口調整后,記錄滿足條件的最大窗口長度
這種方法保證了在O(n)時間內找到最優解,尤其適合處理大規模數據(題目中數組長度可達10^5),是解決此類問題的最優方案。