陣列累加和問題三連
陣列累加和問題三連
第一題
題目:
給定一個全是正數的陣列arr,一個目標數字target,求陣列中滿足和為target的最長子陣列的長度
思路:
這是很簡單的題目,用雙指標和視窗就可以解決。具體見程式碼
程式碼:
public static int getMaxLength(int[] arr, int K) {
if (arr == null || arr.length == 0 || K <= 0) {
return 0;
}
int left = 0;
int right = 0;
int sum = arr[0];
int len = 0;
while (right < arr.length) {
if (sum == K) {
len = Math.max(len, right - left + 1);
sum -= arr[left++];
} else if (sum < K) {
right++;
if (right == arr.length) {
break;
}
sum += arr[right];
} else {
sum -= arr[left++];
}
}
return len;
}
第二題
題目:
給定一個陣列arr,陣列中整數,負數和0都有,一個目標數字target,求陣列中滿足和為target的最長子陣列的長度
思路
對於這種陣列問題,要求某個S問題的解,一般來說有兩種解決思路
- 以陣列元素i作為起始位置時,S問題的解。遍歷所有的解,能得到最終答案
- 以陣列元素i作為終止位置時,S問題的解。遍歷所有的解,能得到最終答案
對於這個題,採用的第二種思路。
對於陣列元素a[j]來說,要求其作為子陣列最後一個元素時,滿足條件的子陣列的最大長度,對於這個問題來說,那麼需要找到一個a[i],使得sum(a[i],…,a[j])=target,同時,這個i要儘可能的小,這樣才能滿足子陣列最長。對於每一個a[j],都這樣做,最後遍歷結果,就可以找到最終的結果。
按照上面的思路,感覺時間複雜度是O(N^2)的。這裡可以繼續優化,我們可以在O(n)的時間內求出陣列的字首和S,如果想要得到一個最小的i,使得sum(a[i],…,a[j])=target,那麼就相當於找到一個i,使得S[i] = S[j] - target。當然,如果找不到滿足條件的i,說明以a[j]結尾的子陣列沒有滿足條件的,直接遍歷下一個就可以了。
時間複雜度為O(N),空間複雜度也是O(N)
程式碼
public int maxSubArrayLen(int[] nums, int k) {
// Write your code here
if (nums == null || nums.length == 0) {
return 0;
}
//用Map儲存出現過的字首和及其對應的索引
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(0, -1); // 需要提前壓入一個鍵值對<0,-1>
//如果不壓入的話,當從0到j正好滿足條件時,Map中找不到字首為0對應的索引,
//所以會導致答案不對
int len = 0;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (map.containsKey(sum - k)) {
len = Math.max(i - map.get(sum - k), len);
}
if (!map.containsKey(sum)) { //對於同樣的字首和,只儲存第一次出現的位置,因為要保 證子陣列最長
map.put(sum, i);
}
}
return len;
}
變體
題目:給定一個陣列arr,稱1和2的個數相同的子陣列為合格的子陣列,求arr中合格的子陣列的最大長度
思路:
首先對陣列進行一次遍歷,對於每個元素,如果為1,則不做處理;如果為2,則變為-1;如果為其他數,則設定為0。那麼問題就轉化為,求arr中,滿足目標和為0的子陣列的最大長度。
第三題
題目
給定一個陣列arr,陣列中整數,負數和0都有,一個目標數字target,求陣列中滿足和小於等於target的最長子陣列的長度
思路
首先定義概念,定義兩個陣列,分別是
- MinSum[]:MinSum[i]代表著子陣列以i位置元素開頭,能取得的最小累加和
- MinSumEnd[]:MinSumEnd[i]代表著子陣列以i位置元素開頭,能取得最小累加和的子陣列的結束位置
MinSum[i]表示從i開始往後,所有子陣列的最小的累加和,MinSumEnd[i]代表著對應的結束位置,這兩個資料是非常有用的。
下面開始主流程:從陣列的index位置開始,設index = 0 ,利用上面兩個陣列,求出0位置作為子陣列開頭所能達到的滿足條件的子陣列的最大長度。很簡單,如果MinSum[0]<=target,那麼可以得到末尾索引j(j=MinSumEnd[i]),此時sum = MinSum[0],接下來計算sum +MinSum[j+1],如果sum<=target,則sum = sum +MinSum[j+1],並且得到新的末尾位置k(k=MinSumEnd[j+1]);繼續執行上面的操作直到 sum +MinSum[k+1] >target 時,停止,此時記錄0位置作為子陣列的最大長度(滿足條件的子陣列),記此時的子陣列末尾位置為p;
然後,sum = sum - arr[0],也就是將0位置元素從sum中減去,此時sum為從1到p位置所有元素的和,如果sum + MinSum[p+1] <=target,則sum = sum + MinSum[p+1],p = MinSumEnd[p+1],繼續迴圈;如果sum + MinSum[p+1] > target,說明以1為開頭的,能達到的滿足條件的子陣列,最大長度已經找到了,嘗試更新這個全域性的最大長度,然後將index++,重複上述過程。
這個演算法的精髓在於,在找到0位置開頭的滿足條件的最長子陣列之後,將0位置刪除以後,以1位置開始找滿足長度的子陣列時,不需要從頭開始找。假設從0開始的滿足條件的最大子陣列的長度是p+1,也就是到索引p位置終止(這就是說0到p的sum值,加上MinSum[p+1]的值,大於target),那麼當1為子陣列開頭元素時,如果1到p的sum值,加上MinSum[p+1]的值還是大於target的,說明1作為開始位置的,能達到滿足條件的子陣列中,長度不會超過p,這是比從0開始的滿足條件的最大子陣列的長度要小的,所以1開頭的子陣列就沒有繼續討論的意義了。
這個演算法的關鍵在於,除了第一次遍歷,後面的遍歷,都不需要從頭開始找,刪除了對那些不可能成為答案的結果的遍歷,所以可以達到O(N)
程式碼
public static int maxLengthAwesome(int[] arr, int k) {
if (arr == null || arr.length == 0) {
return 0;
}
int[] minSums = new int[arr.length];
int[] minSumEnds = new int[arr.length];
//初始化兩個關鍵陣列
minSums[arr.length - 1] = arr[arr.length - 1];
minSumEnds[arr.length - 1] = arr.length - 1;
for (int i = arr.length - 2; i >= 0; i--) {
if (minSums[i + 1] < 0) {
minSums[i] = arr[i] + minSums[i + 1];
minSumEnds[i] = minSumEnds[i + 1];
} else {
minSums[i] = arr[i];
minSumEnds[i] = i;
}
}
int end = 0;
int sum = 0;
int res = 0;
// i是視窗的最左的位置,end擴出來的最右有效塊兒的最後一個位置的,再下一個位置
// end也是下一塊兒的開始位置
// 視窗:[i~end)
for (int i = 0; i < arr.length; i++) {
// while迴圈結束之後:
// 1) 如果以i開頭的情況下,累加和<=k的最長子陣列是arr[i..end-1],看看這個子陣列長度能不能更新res;
// 2) 如果以i開頭的情況下,累加和<=k的最長子陣列比arr[i..end-1]短,更新還是不更新res都不會影響最終結果;
while (end < arr.length && sum + minSums[end] <= k) {
sum += minSums[end];
end = minSumEnds[end]+1;
}
res = Math.max(res, end - i);
if (end > i) { // 視窗內還有數 [i~end) [4,4)
sum -= arr[i];
} else { // 這是需要特殊考慮的一點,視窗內已經沒有數了,
//說明從i開頭的所有子陣列累加和都不可能<=k,這時end=i了,讓end= i+1,下一輪開始時
//i也會加1,就自動跳到下一個數去遍歷了
end = i + 1;
}
}
return res;
}
程式碼的迴圈中,兩個遍歷,i和end,都是從0跑到n,所以時間複雜度是O(N).
對於程式碼中end=i的情況,可以看下面這個例子
[-7,3,3,3,3,3,…],target=0
從0位置開始,可以最多到3位置,此時end = 3,i=0,sum = sum - (-7),i++
當i=1時,sum = 6 > target ,sum = sum-3,i++,
i=2時,sum = 3 >target,sum = sum-3,i++ ,
i=3時,sum = 0 =target(此時沒有元素在視窗中了,但是end仍然擴不動,此時的end==i,所以讓end = i+1,end變成4,下一輪i++也會變成4,就會跳過3位置的元素,繼續遍歷了)
相關文章
- 楊輝三角與陣列遞迴累加陣列遞迴
- 第四章:多維陣列和矩陣 ------------- 4.7 子陣列最大累加和陣列矩陣
- 最大連續子陣列和求解問題(C語言)陣列C語言
- 陣列和指標的問題陣列指標
- vue 陣列和物件渲染問題Vue陣列物件
- 子陣列異或和問題陣列
- 第四章:多維陣列和矩陣 ------------- 4.8 子矩陣的最大累加和陣列矩陣
- 雙指標查詢陣列的連續規律子陣列問題指標陣列
- 陣列和列表的轉換問題陣列
- 最大子陣列和問題的解陣列
- php陣列問題PHP陣列
- 陣列和連結串列陣列
- 棧和佇列:滑動視窗和陣列變樹問題佇列陣列
- 子陣列的最大異或和問題陣列
- 陣列的最小不可組成和問題陣列
- JavaScript 中 for in 迴圈和陣列的問題JavaScript陣列
- 金幣陣列問題陣列
- java陣列的問題Java陣列
- 面試題:陣列按列排序的問題面試題陣列排序
- 矩陣連乘問題矩陣
- 三,列表和陣列(筆記)陣列筆記
- js陣列全排列問題JS陣列
- leetcode題解(陣列問題)LeetCode陣列
- (三)陣列陣列
- 【陣列】1550. 存在連續三個奇數的陣列(簡單)陣列
- 陣列效能問題分析總結陣列
- Python陣列中求和問題Python陣列
- 陣列元素迴圈右移問題陣列
- [基礎問題] 陣列賦值陣列賦值
- 求子陣列的最大和問題陣列
- 陣列和連結串列的區別陣列
- 陣列、連結串列、堆疊和佇列學習陣列佇列
- 最大連續子陣列和(最大子段和)陣列
- 資料結構之連結串列與陣列(1):陣列和連結串列的簡介資料結構陣列
- 回覆網友問題,關於一個數值和字串一起累加的問題!字串
- 陣列匹配-連線陣列
- JS中的陣列複製問題JS陣列
- C++陣列預設初值問題C++陣列