累加值為k的最長子陣列,累加值不大於k的最長子陣列
問題描述1:給定一個數組,值全部是正數,請返回累加值為給定值 k 的最長子陣列的長度。
解題思路:
一開始 l 和 r 均指向陣列第一個數,sum=arr[ 0 ]。
如果k>sum,(不知道為什麼寫sum小於(<)k就是沒法顯示後面的,只能這麼寫了(┬_┬)很醉)那麼 r 向右移動,sum加上 arr[ r ]。如果sum>k,那麼sum去掉此時 l 的值,並且 l 右移。重複上述過程直至 r 到達陣列尾部。
如果sum==k ,那麼記錄下此時的長度,如果比maxlength大,那麼更新maxlength,並且sum減去arr [ l ],l 向右移動。
程式碼如下:
public static int len1(int[] arr, int k) {
int len=0;
int l=0,r=0;
int sum=arr[0];
while(r<arr.length){
if (sum==k) {
len=Math.max(len, r-l+1);
sum-=arr[l++];//如果sum等於k,sum的值減去l位置的值,l右移
}else if (sum<k) {
r++;
if (r==arr.length-1) {
break;
}
sum+=arr[r];
}else {
sum-=arr[l++];
}
}
return len;
}
問題描述2:給定一個數組,值可以為正數負數和0,請返回累加和為給定值 K 的最長子陣列長度。
解題思路:
假設在長度為n的陣列arr中,整個陣列的和為sum,要求出以n-1結尾的累加和為K的最長子陣列。以上圖為例,如果最長子陣列是從 m+1 到 n-1 的,那麼前面 0 到 m 的和一定是 sum-k ,而且此時 m 位置是陣列中第一次出現累加和為 sum-k 的位置。
可以簡單證明一下:如果在m前面還有累加和為sum-k的情況的位置 p,那麼以n-1結尾累加和為k的最長子陣列的長度就不是m-1到n-1,而應該是p+1到n-1,此時子陣列長度變長,與之前給的條件不相符。所以此時m位置一定是陣列中第一次累加和出現sum-k的位置。
我們可以用一個map來儲存陣列中第一次出現累加和sum和它對應的index的值。
在每一次查詢時,先在map中找是否有存在sum-k的key,(i為出現sum的下標,j為sum-k對應的下標)如果有,那麼此時的子陣列的長度就是 i-j,如果不存在,就將(sum,i)插入map中。
但是按照上面的方法,可能會將0位置跳過,以下面的陣列舉例:
value | 4 | 6 | 7 | 9 | 8 |
---|---|---|---|---|---|
index | 0 | 1 | 2 | 3 | 4 |
如果k=4,在index=0時,sum=4,此時在map中查詢是否存在key=sum-k=0的情況,但是map中為空,查不到,所以就直接跳過了,沒有進行任何處理,maxlength還是0,最後的返回值就是0,可是預期的結果應該是1。所以為了避免這種情況的發生,應該在一開始就講(0,-1)插入map中。
程式碼如下:
public static int len2(int[] arr,int k) {
if(arr==null||k<0||arr.length==0)
return 0;
int len=0;
HashMap<Integer, Integer> map=new HashMap<Integer,Integer>();
map.put(0, -1);
int sum=0;
for(int i=0;i<arr.length;i++){
sum+=arr[i];//求出到i位置的和
if (map.containsKey(sum-k)) {
//如果map中記錄了sum-k這個值對應的index
//更新len的長度
len=Math.max(i-map.get(sum-k), len);
}
if (!map.containsKey(sum)) {
//如果map中沒有記錄sum
//那麼就將sum插入map中
map.put(sum, i);
}
}
return len;
}
問題描述3:給定一個數組,值可以為正數負數和0,請返回累加和不大於k的最長子陣列長度。
解題思路:
分兩步來計算:先計算以每個數開頭的最小累加和以及他們的右邊界,再將累加和累加起來直至大於k。
首先計算以 i 開頭往後的累加和,從陣列尾部開始,舉例如下:
arr_index | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
arr_value | 4 | 3 | -2 | 6 | 7 | -3 | -1 |
min_value | 4 | 1 | -2 | 6 | 3 | -4 | -1 |
min_index | 0 | 2 | 2 | 3 | 6 | 6 | 6 |
其中min_value是最小累加和,min_index是對應的右邊界。從右往左遍歷陣列,如果要求出 i 位置的最小累加和,那麼就要看 i+1 位置的最小累加和,如果min_value[ i+1 ]<0,那麼min_value[ i ]=arr[ i ]+min_value[ i+1 ],min_index[ i ]=min_index[ i+1 ],否則min_value[ i ]=arr[ i ],min_index[ i ]=i。
接下來再從左往右遍歷陣列,如果以 i 開頭的最小和>k,那麼再往後面累加的累加和都>k。
上圖中的sum1,sum2,sum3,sum4等塊中包含至少一個數,從l=0位置開始遍歷,如果s+sum1<=k那麼s+=sum1,記錄下右邊界,繼續往後遍歷,如果s+sum>k,記錄下此時s對應的長度。然後 l 右移,此時在s中減去arr[ l ]的值,繼續向後遍歷,直至結束
參考程式碼如下:
public static int len3(int[] arr,int k) {
int len=0;
int[] sum=new int[arr.length];//以i開頭的最小值
HashMap<Integer, Integer> emap=new HashMap<>();//以i開頭的右邊界(i,右邊界)
sum[arr.length-1]=arr[arr.length-1];
emap.put(arr.length-1, arr.length-1);
for(int i=arr.length-2;i>=0;i--){
if (sum[i+1]<0) {
sum[i]=sum[i+1]+arr[i];
emap.put(i, emap.get(i+1));
}else{
sum[i]=arr[i];
emap.put(i,i);
}
}
int s=0;//最長的和
int end=0;//右邊界
for(int i=0;i<arr.length-1;i++){
while(end<arr.length&&s+sum[end]<=k){
//逐步向後加直至到結尾或者大於k
s+=sum[end];
end=emap.get(end)+1;
}
//下一輪迴圈應該從i+1開始,但是不需要重新計算,直接將s中去掉arr[i]即可
s-=end>i?arr[i]:0;
len=Math.max(len, end-i);
end=Math.max(end, i+1);
}
return len;
}