我們刷leetcod時總是遇到各種各樣的子數組和的問題,這類問題一般都是可以通過構建一個前綴和數組,以O(N^2)的時間複雜度求解,但這並不是最優解。
目錄
問題一:和爲K的數組
問題描述:
給定一整型數組nums和一整數k,找到該數組中和爲k的子數組的個數。
input : nums = [1,2,3,-3,-2,-1,0] k = 0
output: 5 分別爲[3,-3], [2,3,-3,-2], [1,2,3,-3,-2,-1], [1,2,3,-3,-2,-1,0] , [0]
解法一:
依次以每個元素作爲頭結點,以其之後元素作爲尾結點,判斷此時的子數組和是否爲k。由於判斷該子數組的和的複雜度可以使用前綴和數組由O(N)降到O(1),實現代碼如下:
public int subarraySum(int[] nums, int k) {
int[] sums = new int[nums.length + 1];
for(int i = 0; i < nums.length; i++){
sums[i + 1] = sums[i] + nums[i];
}
int result = 0;
for(int start = 1; start < sums.length; start++){
for(int end = start; end < sums.length; end++){
if(sums[end] - sums[start - 1] == k){
result++;
}
}
}
return result;
}
tip:計算前綴和數組時,可以多給一位,sums[i] 爲從開頭到i-1位置之和,這樣做的好處是後面在處理以第一個節點開頭的子數組時不用特殊處理。
解法二:
對於前綴和數組還可以進行如下優化,使用一HashMap結構,key爲遍歷過程中的前綴和,value爲當前前綴和出現的次數,因此遍歷過程中便可以在map中尋找(sum - k)的值是否出現過,若存在則證明以當前結點結尾有map.get(sum - k)個子數組。時間複雜度降爲O(N)。
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();// k = sum ,value = 次數
int sum = 0;
int result = 0;
map.put(0, 1);
for(int num : nums){
sum += num;
if(map.containsKey(sum - k)){
result += map.get(sum - k);
}
map.put(sum, map.containsKey(sum) ? map.get(sum) + 1 : 1);
}
return result;
}
問題二:和等於k的最長/短子數組
問題描述:
給定一整型數組nums及一整數k,找到nums中最長/短的和等於k的子數組,不存在返回-1。本題求解的是最長的子數組,最短的同理。
input : nums = [1,2,3,-3,-2,-1,0] k = 0
output:7 而不能是這些[3,-3], [2,3,-3,-2], [1,2,3,-3,-2,-1], [0]
解決方案:
如之前介紹,該問題依然可以使用前綴和數組,將所有滿足條件的子數組都求解出來,返回其中最大的。如此還是O(N^2)的時間複雜度,該解法就不加贅述了。
遍歷過程找到當前結點爲尾結點的滿足條件的最長子數組。
該問題依然可以使用一HashMap來優化,key存儲的是遍歷過程中的前綴和,不同的是value存儲的是該前綴和最早出現的位置。由於找的是最長的,由於後面再次出現該sum時,得到滿足條件的子數組總是比前面的更短。實現代碼如下:
public static int maxSubArraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int sum = 0;
int result = -1;
for(int i = 0; i < nums.length; i++) {
sum += nums[i];
if(map.containsKey(sum - k)) {
result = Math.max(result,
i - map.get(sum - k)); // 從map.get(sum - k) + 1 到 i爲子數組
}else {
map.put(sum, i);
}
}
return result;
}
問題三:和爲k的整數倍的數組
題目描述:
給定一非負的整型數組,以及一整數k。判斷該數組中是否存在一子數組使得組數組的和等於k的整數倍。要求子數組長度大於2。
input : nums = [23,2,4,6,7], k = 6
output : true
解決方案:
該問題的一般解法還是可以通過構建前綴和數組遍歷出所有的子數組,以O(N^2)的時間複雜度求解,不過需要注意的是,該問題中要求k的整數倍,因此不可避免的需要對k取餘,因此對於k=0的情況需要特殊處理.。
想要使用HashMap優化,需要確定key的含義。
sum1 % k == sum2 % k => (sum2 - sum1) % k == 0
不妨設 sum1 = k * m + p, sum2 = k * n + p,則(sum2 - sum1) % k =(n - m) * k % k ==0
因此可以使用當前前綴和對k取餘的餘數作爲key,遍歷過程,若出現當前前綴和的餘數之前出現過,則說明中間這一段對k取餘等於0。由於題目還要求子數組的長度至少爲2,因此將當前的下標作爲value。
此外上述所提的下標應該爲該餘數第一次出現的下標。例如案例[ 1 7 7] k = 7就可以說明問題。實現代碼如下:
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
if(k == 0){
return checkZeroOption(nums);
}
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum += nums[i];
if(map.containsKey(sum % k)){
if(i - map.get(sum % k) > 1){
return true;
}
}else{
map.put(sum % k, i);
}
}
return false;
}
// 處理 k=0情況
public boolean checkZeroOption(int[] nums){
for(int i = 1; i < nums.length; i++){
if(nums[i - 1] == 0 && nums[i] == 0){
return true;
}
}
return false;
}
}