陣列刷題筆記4:長度最小的子陣列
阿新 • • 發佈:2022-05-11
長度最小的子陣列
209. 長度最小的子陣列
給定一個含有 n 個正整數的陣列和一個正整數 s ,找出該陣列中滿足其和 ≥ s 的長度最小的 連續 子陣列,並返回其長度。如果不存在符合條件的子陣列,返回 0。
方法1:暴力解法
-
思路:使用兩個for迴圈,不斷尋找符合條件的子序列。
-
複雜度:
- 時間複雜度:O(N^2),兩個for迴圈
- 空間複雜度:O(1)
方法2:滑動視窗
- 思路:
- 視窗是滿足大小的長度最小的連續子陣列
- 當視窗值大於s,起點指標移動
- 視窗的結束位置是遍歷陣列的指標,視窗的起始位置為陣列的起始位置
- 複雜度:
- 時間複雜度:O(N),每個元素在滑動窗後進來操作一次,出去操作一次,每個元素都是被被操作兩次,所以時間複雜度是 2 × n 也就是O(n)
- 空間複雜度:O(1)
class Solution { public int minSubArrayLen(int target, int[] nums) { int n = nums.length; int result = n + 1;//設定為大於陣列長度 int sum = 0; //滑動視窗內陣列的和 int i = 0; //起點指標起始位置 int subLength = 0; //滑動視窗的長度 for (int j = 0; j < n; j++) { sum += nums[j]; //如果和大於目標,開始移動起點位置 while (sum >= target) { subLength = j - i + 1; //計算視窗長度 result = result < subLength ? result : subLength; //對比修改當前的視窗長度 sum -= nums[i++]; //移動起點,修改視窗和 } } //判斷結果有沒有改變,如果沒有,不存在該改變長度的子陣列,返回0;有則返回結果 return result == n + 1 ? 0 : result; } }
904. 水果成籃
你正在探訪一家農場,農場從左到右種植了一排果樹。這些樹用一個整數陣列 fruits 表示,其中 fruits[i] 是第 i 棵樹上的水果 種類 。
你想要儘可能多地收集水果。然而,農場的主人設定了一些嚴格的規矩,你必須按照要求採摘水果:
- 你只有 兩個 籃子,並且每個籃子只能裝 單一型別 的水果。每個籃子能夠裝的水果總量沒有限制。
- 你可以選擇任意一棵樹開始採摘,你必須從 每棵 樹(包括開始採摘的樹)上 恰好摘一個水果 。採摘的水果應當符合籃子中的水果型別。每採摘一次,你將會向右移動到下一棵樹,並繼續採摘。
- 一旦你走到某棵樹前,但水果不符合籃子的水果型別,那麼就必須停止採摘。
給你一個整數陣列 fruits ,返回你可以收集的水果的 最大 數目。
滑動視窗法(思路簡單,比較暴力)
- 設定n1, n2為陣列最開始兩個不重複的數
- 滑動到為不n1 n2的數,記錄上一個視窗長度,進行對比
- 重新賦值n1為視窗尾部指向的前一個數(上一次視窗中的其中一個數),n2為當前視窗尾部指向的數(新數)
- 將起始指標從結束指標向前遍歷為第一個n1的位置
- 繼續滑動視窗尾部,重複操作
- 結尾沒改變時沒進入迴圈,最後計算一次視窗長度
class Solution {
public int totalFruit(int[] fruits) {
//使用滑動視窗找最長不重複的兩個數字
int n = fruits.length;
if (n <= 2) return n; //長度小於2時,直接返回結果
int result = 2; //結果
int i = 0; //起始指標
int subLength = 0; //滑動視窗的長度
int n1 = fruits[0]; //設定兩個籃子裡的數
//給n2賦值為與第一個數不同的第一個數
int n2 = 0;
for (int j = 0; j < n; j++) {
if (fruits[j] != n1) {
n2 = fruits[j];
break;
}
}
//開始滑動視窗
//滑動到為不n1 n2的數,記錄上一個視窗長度,進行對比
//重新賦值n1為視窗尾部指向的前一個數(上一次視窗中的其中一個數),n2為當前視窗尾部指向的數(新數)
//將起始指標從結束指標向前遍歷為第一個n1的位置
//繼續滑動視窗尾部,重複操作
for (int j = 0; j < n; j++) {
while ((fruits[j] != n1) && (fruits[j] != n2)) {
subLength = j - i; //此時j指向下一個不連續的元素,因此不需要+1
result = result > subLength ? result : subLength; //改變長度
n1 = fruits[j - 1]; //重新賦值n1為不同數的前一個數
n2 = fruits[j]; //重新賦值n2為不同的數
//將i向前遍歷移動到第一個n1值的位置
i = j - 1;
while (fruits[i - 1] == fruits[i]) {
i--;
}
}
}
//結尾沒改變時沒進入迴圈,最後計算一次視窗長度
subLength = n - i; //陣列長度為末尾j指向的下一個元素,不需要再+1
result = result > subLength ? result : subLength;
return result;
}
}
滑動視窗(計算水果種類,出現頻次)
- 思路:滑動視窗系列中的計數問題,freq陣列記錄現在視窗中兩種水果的數目,count記錄現在視窗中有多少種不同的水果,如果count<=2,那麼right指標可以一直向右移動,直到count>2,當count>2時,說明視窗中的水果種類超過2,為了減少水果種類,left指標就要向右移動,移動的同時還要更新freq陣列,最後取最大值即可。
class Solution {
public int totalFruit(int[] fruits) {
int n = fruits.length;
if (n <= 2) return n;
int total = 2;
int left = 0;
int count = 0; // 計算籃中種類數
int[] fruitFrequence = new int[n]; // 計算籃中每種水果出現的次數。 因為提示中說明了水果的種類數是有限的 0 <= fruits[i] < fruits.length
for (int right = 0; right < n; right++) {
fruitFrequence[fruits[right]] += 1; // 入籃
if (fruitFrequence[fruits[right]] == 1) count+=1; //等於1說明第一次入籃,count需要加1
while (count > 2) { // 籃中超過兩種水果
fruitFrequence[fruits[left]] -= 1; // 因為下邊還要使用left下標,所以先不要移動
if (fruitFrequence[fruits[left]] == 0) count-=1; // 等於0說明籃中已經沒有fruits[left]水果,count減1
left += 1; // 移動left
}
total = Math.max(total, right - left + 1); // 取視窗最大值
}
return total;
}
}
76. 最小覆蓋子串(困難)
給你一個字串 s 、一個字串 t 。返回 s 中涵蓋 t 所有字元的最小子串。如果 s 中不存在涵蓋 t 所有字元的子串,則返回空字串 "" 。
注意:
對於 t 中重複字元,我們尋找的子字串中該字元數量必須不少於 t 中該字元數量。
如果 s 中存在這樣的子串,我們保證它是唯一的答案。
滑動視窗法(陣列)
- 右指標移動,直到包含t內的所有元素
- 計算長度和起點
- 左指標移動,如果有更小的區間更新長度和起點
- 直到區間不包含t,移動右指標,重複以上步驟
理解:
在遍歷字串的過程中,非目標字元的數量會被減到-1,因此增加count的計數。
當遍歷到目標字元時,目標字元原本為0,計數器count+1,在執行後字元對應的值大於0,在進入下一次迴圈時,值減為一,並減少計數器的值,從而進入視窗的迴圈。
class Solution {
public String minWindow(String s, String t) {
//右指標移動,直到包含t內的所有元素
//計算長度和起點
//左指標移動,如果有更小的區間更新長度和起點
//直到區間不包含t,移動右指標,重複以上步驟
//如果s的長度小於t的長度,那s必然無法包含t的所有元素
//如果s與t相等,那結果為s或t
if (s.length() < t.length()) {
return "";
} else if (s.equals(t)) {
return s;
}
int left = 0; //左指標
int right = 0; //右指標
int count = t.length(); //記錄未包含元素個數
int windowLength = s.length() + 1; //視窗長度
int start = 0; //視窗起點
//記錄t中各個元素個數的陣列(由英文字母組成,因此由字元所對應的數字的下標進行計數
int[] map = new int[128];
for (char c : t.toCharArray()) map[c]++;
while (right < s.length()) {
if (map[s.charAt(right++)]-- > 0) {
count--;
}
while (count == 0) {
if (right - left < windowLength) {
start = left;
windowLength = right - left;
}
if (map[s.charAt(left++)]++ == 0) {
count++;
}
}
}
if (windowLength != s.length() + 1) {
return s.substring(start, start + windowLength);
}
return "";
}
}