子數組相關題目:前綴和技巧

前綴和介紹

  • 假設有數組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中元素對應的下標
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章