1. 累加和等於num的最長子數組的長度(有負數)
注:數組元素可爲負數
思路:求子數組問題,就是依次考慮數組中每一位元素之前的數組的情況
比如說arr = {7,3,2,1,1,7,7}
,依次考慮數組範圍(0-i)中,任意元素開始,以arr[i]
結尾的子數組是否符合條件。比如說i=0時,子數組{7}
符合條件;i=4時,子數組{3,2,1,1}
符合條件。
假設遍歷到i位置,數組元素從0加到i的值爲sum,只需要求i之前的數組中,是否出現過和爲sum-num的情況。例如,arr = {9,8,3,2,1,1,7,7},num=7
,當遍歷到i=5的時候,sum=24,此時sum-num=24-7=17。因爲當i=1時,sum=17,所以下標從2到5,數組元素之和肯定爲sum。
public static int maxLength(int[] arr, int k) {
if (arr == null || arr.length == 0) {
return 0;
}
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(0, -1); // important
int len = 0;
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[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;
}
2. 累加和等於num的最長子數組的長度(無負數)
題目;給定一個數組arr, 全是正數; 一個整數aim, 求累加和等於aim的, 最長子數組, 要求額外空間複雜度O(1), 時間複雜度O(N)
思路:可以使用雙指針,一個在左,一個在右,形成一個窗口區域。當窗口中的值小於aim時,窗口右邊界擴大。當窗口中的值大於或等於aim時,由於數組中全爲正數,所以窗口右邊界再擴大,窗口中的值會大於aim。所以此時需要窗口左邊界右移。當窗口右邊界到達數組最後一個元素,且窗口中的值小於aim,返回結果。
數組中有負數和無負數的區別:
窗口擴大,窗口內的累加和一定增加,窗口縮小,窗口內的累加和一定減小。
如有數組arr={7,8,-8},aim=7
,當窗口右邊界移動到0時,窗口內的值已經是7了,這時候窗口會右移。但是,其實最長的子數組爲{7,8,-8}
。所以有負數時,不能用此方法。
public static int getMaxLength(int[] arr, int k) {
if (arr == null || arr.length == 0 || k <= 0) {
return 0;
}
int L = 0;
int R = 0;
int sum = arr[0];
int len = 0;
while (R < arr.length) {
if (sum == k) {
len = Math.max(len, R - L + 1);
sum -= arr[L++];//窗口左邊界右移
} else if (sum < k) {
R++;//當前窗口內的值小於aim,窗口右邊界右移,擴大窗口
if (R == arr.length) {
break;
}
sum += arr[R];
} else {//當前窗口內的值大於aim,窗口左邊界右移
sum -= arr[L++];
}
}
return len;
}
3. 累加和小於等於num的最長子數組的長度
題目:給定一個數組arr, 值可正, 可負, 可0; 一個整數aim, 求累加和小於等於aim的, 最長子數組, 要求時間複雜度O(N)
思考:這一題要求小於等於,且數組元素有負數。就不能使用雙指針來構造滑動窗口了。因爲給定數組中有負數,在窗口擴大,窗口內的值不一定增大;窗口縮小,窗口內的值不一定減小。
思路:設計兩個數組max_sum
和max_sum_index
,長度爲給定數組的長度。數組元素max_sum[i]
表示以arr[i]
開頭,累加和最小的最長子數組的累加和;數組元素max_sum_index[i]
與數組元素max_sum[i]
相對應,表示以arr[i]
開頭,累加和最小的最長子數組的右邊界下標。
通過反向遍歷數組,得到數組max_sum
和max_sum_index
。
例如,有數組arr={6,5,1,7,2,-6,-3,4,3,-2,1},aim=6
,則數組max_sum
和max_sum_index
的情況如下所示
然後根據數組max_sum_index
可將元素組劃分成幾個子數組。
可以將原數組看成如下數組,該數組中肯定不會出現負數,所以可以對其進行滑動窗口操作。
滑動窗口,遍歷過程中,滿足條件的子數組如下(自動捨棄長度更小的子數組組合)
然後分別計算對應的長度即可。第三個組合對應的數組爲{1,7,2,-6,-3,4,3,-2}
,長度爲8。
class Solution_MaxLength3{
public static int maxLengthAwesome(int[] arr, int aim) {
if (arr == null || arr.length == 0) {
return 0;
}
int[] max_sum = new int[arr.length];
int[] max_sum_index = new int[arr.length];
max_sum[arr.length - 1] = arr[arr.length - 1];
max_sum_index[arr.length - 1] = arr.length - 1;
for (int i = arr.length - 2; i >= 0; i--) {
if (max_sum[i + 1] < 0) {
max_sum[i] = arr[i] + max_sum[i + 1];
max_sum_index[i] = max_sum_index[i + 1];
} else {
max_sum[i] = arr[i];
max_sum_index[i] = i;
}
}
int sum = 0;
int len = 0;
int start = 0;//窗口左邊界
int end = 0;//窗口右邊界
int next = 0;//要加進窗口的子數組中的第一個元素下標
while(start < arr.length){
while(next < arr.length && sum + max_sum[next] <= aim){
sum += max_sum[next];
end = max_sum_index[next];
next = end + 1;
}
len = Math.max(len, next - start);
//窗口左邊界右移
if(end > start){
sum -= max_sum[start];
}else{//如果end == start,則有以後sum值爲0
sum = 0;
}
start = max_sum_index[start] + 1;
}
return len;
}
public static void main(String[] args) {
int[] arr = {6,5,1,7,2,-6,-3,4,3,-2,3};
System.out.println(maxLengthAwesome(arr, 6));
}
}