欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-02 21:28:01
...

前缀和介绍

  • 假设有数组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中元素对应的下标
相关标签: 算法刷题笔记