子数组相关题目:前缀和技巧

前缀和介绍

  • 假设有数组AA.length = n

  • 则可新建一前缀和数组preSumArray其长度为n+1

  • 前缀和数组定义如下:

    • preSumArray[0] = 0
    • preSumArray[1] = A[0]
    • preSumArray[2] = A[0] +A[1]
    • ............
    • preSumArray[i] = A[0] + A[1] + ........ + A[i-1]
    • .........................
    • preSumArray[n] = A[0] + A[1] + ........ + A[n-1]
  • 前缀和数组有如下性质

    • sum(i~j) = preSumArray[j+1] - preSumArray[i]
    • preSumArray[j] - preSumArray[i] = Sum(i~(j - 1))
  • 这个也很容易证明嘛:

    • preSumArray[j+1] = A[0] + A[1] + ..+ A[i-1] + ... + A[j]
    • preSumArray[i] = A[0] + A[1]+ .... + A[i-1]
    • 二者之差等于:
    • A[i] + A[i+1] + .... + A[j] = sum(i~j)
  • 显然,构造前缀和数组的时间和空间复杂度均为O(n)

LeetCode53-Maximum Subarray

题意

  • 给定数组,求解此数组中连续子数组之和最大的,返回这个最大值

思路

  • 先求解该数组的前缀和数组
  • 然后遍历前缀和数组
  • 分别维护一个最大值和最小值,因为最大减最小的和最大嘛,这样遍历完成后得到一段连续数组和的最大值

代码

  public int maxSubArray(int[] nums) {

        if (nums == null || nums.length == 0){
            return 0;
        }
        //0. calc the preSumArray
        int n = nums.length;
        int[] preSumArray = new int[n+1];
        preSumArray[0] = 0;
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += nums[i];
            preSumArray[i+1] = sum;
        }
        //1. find the max  sum of the SubArray in the nums
        //1.1 最大和的子数组一定等于 最大值减去最小值
        int maxSum = Integer.MIN_VALUE;
        int minSum = 0;
        //1.2 遍历preSumArray
        for (int i = 1; i < preSumArray.length; i++) {
            maxSum = Math.max(maxSum, preSumArray[i] - minSum);
            minSum = Math.min(minSum, preSumArray[i]);
        }
        return maxSum;
    }

LintCode44-Minimun Subarray

题意

  • 和上一题完全一样,只不过这一题求最小值

思路

  • 可以先把给定数组的元素取反得到新数组
  • 然后利用上一题的做法对新数组求最大值
  • 然后返回最大值的相反数

代码

  public static int minSubArray(List<Integer> nums) {

        //0.先对给定list的元素取反
        int[] A = new int[nums.size()];
        int index = 0;
        for (Integer num : nums) {
            A[index++] = -num;
        }
        //1.然后对数组A,求解连续子数组和的最大值
        //1.1 依然采用前缀和技巧求解
        int[] preSumArray = new int[nums.size() + 1];
        preSumArray[0] = 0;
        int sum = 0;
        for (int i = 1; i < preSumArray.length; i++) {
            sum += A[i-1];
            preSumArray[i] = sum;
        }
        //1.2 根据前缀和求解最大值
        int maxSum = Integer.MIN_VALUE;
        int minSum = 0;
        for (int i = 1; i < preSumArray.length; i++) {
            maxSum = Math.max(maxSum, preSumArray[i] - minSum);
            minSum = Math.min(minSum, preSumArray[i]);
        }
        //2. 最大对最大值取反,返回
        return -maxSum;
    }

LintCode138-Subarray Sum

题意

给定数组,求解和为0的子数组,返回子数组的左右端点的下标

思路

  • 求出该数组的前缀和数组
  • 在前缀和数组中找到两个元素相等的两个数即可
  • 然后返回其下标
  • 关键在于前缀和数组是无序的,如何才能找到两个相同的数,并同时记录下标信息呢?
  • 在数组相关的题目中,经常需要把index和value绑定在一起,有两种方式可以处理
    • 利用map
    • 自定义一个类Pair,定义两个成员变量,分别是indexvalue

代码

public List<Integer> subarraySum(int[] nums) {

        List<Integer> res = new ArrayList<>();
        if (nums == null || nums.length == 0){
            return res;
        }
        //0. 依然计算前缀和,但是需要把前缀和index关联起来
        //0.1 这是为了后面排序后依然有前缀和的信息
        int n = nums.length;
        int sum = 0;
        //0.2 sum -> index
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0, 0);
        for (int i = 0; i < n ; i++) {
            sum += nums[i];
            //1. 在前缀和数组建立的过程中去完成查找,有效的解决了重复的sum问题
            if (map.containsKey(sum)){
                res.add(map.get(sum));
                res.add(i);
                return res;
            }
            map.put(sum, i+1);
        }
        return res;
    }

LintCode139-Subarray Sum Closest

题意

  • 给定数组,找出和与0最接近的子数组,返回子数组两端的下标

思路

  • 和上题基本一致
  • 仍然先求出前缀和数组
  • 然后对前缀和数组排序
  • 然后依次找前缀和数组中相邻两个数的差,记录两数之差最小的
  • 然后记录下这两个数的下标
  • 这里又需要把value和index绑定在一起,而且还需要对value排序,所以这里使用Pair类

代码

class LintCode139 {

    class Pair{

        int index;
        int preSum;
        public Pair(int index, int preSum){
            this.index = index;
            this.preSum = preSum;
        }
    }
    public int[] subarraySumClosest(int[] nums) {

        int[] res = new int[2];
        if (nums == null || nums.length == 0){
            return null;
        }
        //0. 依然计算前缀和,但是需要把前缀和index关联起来
        //0.1 这是为了后面排序后依然有前缀和的信息
        int n = nums.length;
        int sum = 0;
        Pair[] pairs = new Pair[n + 1];
        pairs[0] = new Pair(0, 0);
        //0.2 注意这里按照前缀和数组的下标在进行初始化
        for (int i = 1; i < pairs.length; i++) {
            sum += nums[i-1];
            pairs[i] = new Pair(i, sum);
        }
        //1. 对pairs按照preSum排序
        Arrays.sort(pairs,new Comparator<Pair>(){
            @Override
            public int compare(Pair o1, Pair o2) {
                return o1.preSum - o2.preSum;
            }
        });
        //2. 现在是升序排序,那么差最小的二者一定是相邻的二者
        int min  = Integer.MAX_VALUE;
        for (int i = 0; i < n - 1; i++) {
            int curMin = pairs[i+1].preSum - pairs[i].preSum;
            if (curMin < min){
                min = curMin;
                //2.1记录index
                if (pairs[i+1].index < pairs[i].index){
                    res[0] = pairs[i+1].index;
                    //2.2 -1是因为 preSum[j] - preSum[i] 表示的是 nums中 index = i 到 index = j - 1的和
                    //    这里的index显然应该是nums中的index,所以需要-1
                    res[1] = pairs[i].index - 1;
                }else {
                    res[0] = pairs[i].index;
                    res[1] = pairs[i+1].index - 1;
                }
            }
        }
        return res;
    }


}

细节

  • preSum[j] - preSum[i] 表示的是 numsindex = i 到 index = j - 1的和
  • 所以最后求解的时候,index较大者需要-1才是nums中元素对应的下标
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章