0
点赞
收藏
分享

微信扫一扫

程序员代码面试指南第二版 126.未排序数组中累加和小于或等于给定值的最长子数组长度


​​welcome to my blog​​

程序员代码面试指南第二版 126.未排序数组中累加和小于或等于给定值的最长子数组长度

题目描述

给定一个无序数组arr,其中元素可正、可负、可0。给定一个整数k,求arr所有的子数组中累加和小于或等于k的最长子数组长度
例如:arr = [3, -2, -4, 0, 6], k = -2. 相加和小于等于-2的最长子数组为{3, -2, -4, 0},所以结果返回4

[要求]
时间复杂度为O(n)O(n),空间复杂度为O(n)O(n)

输入描述:
第一行两个整数N, k。N表示数组长度,k的定义已在题目描述中给出
第二行N个整数表示数组内的数

输出描述:
输出一个整数表示答案

示例1

输入
5 -2
3 -2 -4 0 6

输出
4

第一次做; 核心维护两个数组,具体见注释; 时间复杂度O(N); 这个方法很难, 优先掌握O(NlogN)的算法

import java.util.Scanner;
import java.util.HashMap;

public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String[] str = sc.nextLine().split(" ");
int n = Integer.parseInt(str[0]);
int k = Integer.parseInt(str[1]);
str = sc.nextLine().split(" ");
int[] arr = new int[n];
for(int i=0; i<n; i++){
arr[i] = Integer.parseInt(str[i]);
}
//
//minSum[i]表示以arr[i]开头的所有子数组中,能够得到的最小累加和是多少
int[] minSums = new int[n];
//minSumEnds[i]表示以arr[i]开头的所有子数组中,最小累加和对应的子数组的右边界是什么
int[] minSumEnds = new int [n];
minSums[n-1] = arr[n-1];
minSumEnds[n-1] = n-1;
//根据minSums[i+1]是否大于等于0分成两种情况讨论
//超级细节: minSums[i+1]==0时,minSums[i]没有考虑minSums[i+1], 其实可以考虑, 后面的内循环while次数会减少
for(int i=n-2; i>=0; i--){
if(minSums[i+1]>=0){
minSums[i] = arr[i];
minSumEnds[i] = i;
}
else{
minSums[i] = arr[i] + minSums[i+1];
minSumEnds[i] = minSumEnds[i+1];
}
}
//
//end表示窗口最右位置的下一个位置; sum表示子数组arr[i,end-1]的累加和
int end=0, sum=0, len=0;
//i是窗口的最左位置
for(int i=0; i<n; i++){
//更新窗口
while(end<n && sum + minSums[end] <= k){
sum = sum + minSums[end];
end = minSumEnds[end]+1;
}
len = Math.max(len, end-i);
//到这里已经判断完以arr[i]开头的所有子数组的情况, 下面该考虑一arr[i+1]开头的所有子数组中能否扩大窗口;
//更新sum; 因为sum是子数组arr[i,end-1]的累加和,所以需要确保end-1-i>=0,也就是end>i
if(end>i)
sum = sum - arr[i];
else
end = i + 1;
}
System.out.print(len);
}
}

第一次做; 哈希表前缀和+二分查找法; 时间复杂度O(NlogN)

import java.util.Scanner;
import java.util.HashMap;

public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String[] str = sc.nextLine().split(" ");
int n = Integer.parseInt(str[0]);
int k = Integer.parseInt(str[1]);
str = sc.nextLine().split(" ");
int[] arr = new int[n];
for(int i=0; i<n; i++){
arr[i] = Integer.parseInt(str[i]);
}
//
/*
这次不是找最早出现的sum-k了, 而是找最早出现的并且大于等于sum-k的值
如何找到最早出现的并且大于等于sum-k的值?需要借助累加和数组acc和最大值数组max
acc[i]表示arr前i个数的累加和; 注意:acc[0]=0; acc中的值都会记录到哈希表中
max[i]表示子数组acc[0,i]的最大值; 注意:max中的元素是非严格递增的, 可以利用二分查找法, 二分到死
*/
//核心:max[0]=0, max数组的长度必须是n+1,并且让max[0]=0, 否则无法表示arr[0,i]这个子数组,例子[3,-2,-4,0,6],k=2
int[] max = new int[n+1];
//记录累加和
int tmp=0;
for(int i=1; i<=n; i++){
tmp = tmp + arr[i-1];
max[i] = Math.max(tmp, max[i-1]);
}
//哈希表前缀和
HashMap<Integer, Integer> map = new HashMap<>();
//核心:加入(0,-1)才能记录子数组arr[0,i]
map.put(0,-1);
//sum表示子数组arr[0,i]的值
int sum=0,len=0;
for(int i=0; i<n; i++){
sum = sum + arr[i];
//获取最早出现的大于等于sum-k的值
tmp = getEqualOrGreaterThan(max,sum-k);
//如果tmp<sum-k说明max中不存在大于等于sum-k的值
if(tmp>=sum-k && map.containsKey(tmp)){
len = Math.max(len, i-map.get(tmp));
}
if(!map.containsKey(sum)){
map.put(sum, i);
}
}
System.out.print(len);
}
//max数组是递增的; 功能:在max数组中找到第一个大于等于a的值
public static int getEqualOrGreaterThan(int[] max, int a){
//如果找不到大于等于a的值,那就返回一个小于a的值表示没有找到
int res = a-1;
int left=0, right=max.length-1, mid;
//二分到死;在max中找到第一个大于等于a的值; 循环条件不包含等于,会出现死循环,如{1,5,7,9,9,9,10}找第一个大于等于6的数
while(left<right){
mid = left + ((right-left)>>1);
if(max[mid]>=a){
right = mid;
}
else{
left = mid+1;
}
}
return max[left] >= a ? max[left] : a-1;
}
}


举报

相关推荐

0 条评论