程序员代码面试指南第二版 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;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值