美文网首页
未排序数组中累加和为给定值的最长子数组系列问题

未排序数组中累加和为给定值的最长子数组系列问题

作者: futurehau | 来源:发表于2016-08-06 19:08 被阅读958次

题目1

给定一个无需数组arr,其中元素可正,可负,可0,给定一个整数k。求arr的所有子数组中累加和为k的最长子数组的长度。

思路

首先注意到这里元素可正可负可0,所以之前的两个指针的方法不再有效,这里需要使用一种全新的方法,联想到之前说过的数组问题可以考虑必须以j结尾的情况下怎么怎么样,所以这个题按这种思路进行下去。
假设我们能得到以必须以位置j结尾的满足条件的最长子序列,那么我们从这些最长中找到一个最大即为所求,那么怎么求解以位置j结尾的累加和为k的最长子序列呢?我们假设
sum[j]为从0累加到j的和,那么以位置j结尾的满足条件的最长子序列的开头就应该是从0开始第一次出现累加和为sum[j]-k的位置i,也就是sum[i]+target=sum[j],这里sum代表从开始的累加和。

算法流程

  1. 记录一个全局最大res,记录一个sum表示从0位置一直加到当前位置的累加和,使用一个map来记录前边遍历过程中出现过的累加和。
  2. 遍历数组,对于当前位置i,首先更新sum,然后看map中有没有sum-k这个键,如果有,则取出这个键对应的值j,表明(i,j]这一小段子序列的和就为k,用这个k去更新我们的res。遍历结束之后res就为我们的最大。如果没有,这把这个位置和sum放入map中。
  3. 需要特别注意的是首先要把(0,-1)放进map中。比如arr=[5,-1,-1,-1],k=5,如果不放入(0,-1),在考察位置0的时候,其实刚好这个和是满足条件的,但是被忽略了。

算法原理

这里的map记录的是累加和第一次出现某个值的位置。我们在遍历到j的时候,知道sum[j]=0,1,2,,,j的这些元素的累加和,sum[i]为0,1,,,,i这些数的累加和。考察位置j,我们需要找到第一次累加和为sum[j]-k的位置,如果找到了这个位置,那么就有i+1,i+2,...j这些元素的累加和为k,也就求得了以位置j结尾的累加和为k的最长子序列。我们遍历了数组一遍,所有位置结尾的,如果有满足条件的子序列,都求出来了,在这些中的最长的即为所求。

代码

  public static int maxLength(int[] arr, int k) {
        if(arr==null||arr.length==0)
            return 0;
        int sum=0;
        int res=0;
        Map<Integer,Integer> map=new HashMap<Integer,Integer>();
        map.put(0,-1);
        for(int i=0;i<arr.length;i++){
            sum+=arr[i];
            if(map.containsKey(sum-k)){
                res=Math.max(i-map.get(sum-k),res);
            }
            if(!map.containsKey(sum))
                map.put(sum,i);
        }
        return res;
    }

题目拓展

1. 给定一个无需数组arr,其中元素可正,可负,可0,求arr中所####有正数与负数个数相等的最长子数组长度。

可以这样想,原数组中正数就变为1,负数就变为-1,0就为0,另k等于0,调用上述方法即可。

2. 给定一个无需数组arr,其中元素只能为1或0,求arr所有子数组中0和1个数相等的最长子数组的长度。

1为1,0为-1,k为0,调用上述求解和为k的最长子数组算法即可。

题目2

未排序数组中,累加和小于或等于给定值的最长子数组长度。
例如:arr=[3,-2,-4,0,6],k=-2,则累加和小于或等于-2的最长子数组的长度为4。[3,-2,-4,0]

思路

和之前的区别是,这里累加和是小于等于k,那么借助于之前的思路,我们考虑以位置j结尾的累加和小于等于k的最长子数组的长度,我们自然可以想到,需要寻找第一个累加和大于等于k的位置(之前的思路是第一个等于k的位置,我们是借助于一个map来实现的)那么这里怎么办呢?由于我们只关心第一个大于某个值的位置,所以我们可以借助一个阶梯上升的辅助数组h来实现,h[i]表示从开始位置到当前位置的最大累加和。这样在寻找第一个大于某个值得位置时,我们只需要二分来搜索即可。

arr:  3  2  -3  2  -2  6  -3  1
sum:  3  5   2  4   2  8   5  6
h:    3  5   5  5   5  8   8  8 

算法流程&原理

使用一个辅助数组h,h[i]表示从开始到i位置的数组的子数组的最大和(不包括i位置)。然后遍历数组,二分查找第一个大于sum-k的位置,找到则更新,找不到继续。
之前一直不明白为什么h需要h[0]为0,然后长度为len+1,现在终于想明白了,因为我们求得第一个大于等于sum-k的位置,之前的方法h[i]包含了i位置的和,所以

3 2 -1 2 4  5  6 -1   5
3 5  4 6 10 15 21 20 25
3 5  5 6 10 15 21 21 25

比如说现在计算21这个位置,假设k为16,那么就需要求第一个大于等于5的位置i,并且这个位置我们是不能要的,这样i+1...j位置的和才会小于等于16。假设我们h[i]记录的就是0...i的这个递增的数组的话,比如上边h[1]表示加到h[1]最大和为5,我们不应该要h[1]而应该从h[2]开始,但是假设某些情况下h[2]也不满足条件,这就很麻烦了(3 2 0 2 4 5 6 -1 5),所以我们让h[i]为[0,i)的,不包含位置i,我们找到一个位置后,可以知道这个位置的前面是大于等于5的,这个位置就一定是满足条件的。

代码

    public static int maxLength(int[] arr, int k) {
        if(arr==null||arr.length==0)
            return 0;
        int[] h=new int[arr.length+1];
        h[0]=0;
        int sum=arr[0];
        for(int i=1;i<arr.length;i++){
            sum+=arr[i];
            h[i+1]=Math.max(sum,h[i]);
        }
        int res=0;
        int len;
        int index;
        sum=0;
        for(int i=0;i<arr.length;i++){
            sum+=arr[i];
            index=firstBiggerOrEqual(h,sum-k);
            len=index==-1?0:i-index+1;
            res=Math.max(res,len);
        }
        return res;
    }
    public static int firstBiggerOrEqual(int[] h,int num){
        int left=0;
        int right=h.length-1;
        int res=-1;
        while(left<=right){
            int mid=(left+right)/2;
            if(h[mid]>=num){
                res=mid;
                right=mid-1;
            }
            else{
                left=mid+1;
            }
        }
        return res;
    }

相关文章

网友评论

      本文标题:未排序数组中累加和为给定值的最长子数组系列问题

      本文链接:https://www.haomeiwen.com/subject/wnwksttx.html