[Algorithm Series] Prefix sum

insert image description here

foreword

The prefix sum algorithm is a common optimization technique used to speed up certain problems involving the sum of consecutive subarrays or subsequences. It is based on a simple but powerful idea, by calculating and storing the prefix sum of the array in advance, so that the sum of any interval can be quickly obtained in subsequent queries.

In many algorithm problems, we need to frequently query the sum of subarrays, such as the largest subarray sum, the average value of consecutive subarrays, etc. The traditional method is to traverse the array and calculate the sum of the required intervals at each query, which will lead to high time complexity.

The prefix sum algorithm calculates the prefix sum of each position by preprocessing the array, and saves it in an additional array. In this way, when querying, we only need to simply subtract the two prefix sums to obtain the sum of the required subarrays, thereby reducing the query time to a constant complexity of O(1).

What is a prefix sum algorithm

Prefix Sum Algorithm (Prefix Sum Algorithm) is an algorithm for efficiently calculating the prefix sum of arrays. The prefix sum is the sum of all elements before and including a certain position in the array.

The basic idea of ​​the prefix sum algorithm is to traverse the array once, calculate the prefix sum of each position, and store it in a new array. The sum of any subarray can then be quickly calculated by querying the elements in the new array.

Specific steps are as follows:

  1. Create a new array prefixSum with the same length as the original array.
  2. Initialize prefixSum[0] as the first element of the original array.
  3. Starting from the second element of the original array, calculate prefixSum[i] = prefixSum[i-1] + nums[i] sequentially, where nums is the original array.
  4. After completion, each element in the prefixSum array is the sum of all elements before the corresponding position.

Through the prefix sum algorithm, the sum of any subarray can be calculated in O(1) time complexity. For example, to calculate the subarray sum from position i to position j in the original array, just calculate prefixSum[j] - prefixSum[i-1]. If i is 0, return prefixSum[j] directly.

The prefix sum algorithm is very useful in solving some problems related to subarray sums, such as finding the number of subarray sums equal to the target value, finding the largest subarray sum, and finding the longest continuous subarray sum, etc.

1. [Template] prefix and

https://www.nowcoder.com/practice/acead2f4c28c401889915da98ecdc6bf?tpId=230&tqId=2021480&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

1.1 Topic Requirements

insert image description here

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) {
    
     // 注意 while 处理多个 case
            int a = in.nextInt();
            int b = in.nextInt();
            System.out.println(a + b);
        }
    }
}

1.2 Ideas for doing the questions

According to the idea of ​​brute force solution, each time you query the sum between l and r, you need to traverse the array once, then the time complexity of this is O(q*n), because more repeated calculations are performed, resulting in Less time efficient, so is there a way to reduce double counting? The answer is yes, this is the idea of ​​the prefix sum. In fact, it is a bit similar to the operation of entering the window in the sliding window that I shared with you earlier. Every time you enter the window to update data, you only need to add the sum of the numbers that have been calculated before. The data that enters the window gets the sum of all elements before the current position. Through this idea, double counting is greatly reduced.

Then, when finding the sum of elements between l and r, you only need to subtract the sum of all elements before l from the sum of all elements before r and r.

insert image description here
And careful people may find out: Why do both l and r start from 1 instead of 0? Because if the subscript starts from 0, the sum of the elements between 0 and -1 needs to be calculated, but this range is illegal, so starting the array from 1 can prevent this from happening.

When starting to calculate the sum of elements between l and r, you can first perform a preprocessing on the array to find the prefix sum before each position in the array.
insert image description here

1.3 Java code implementation

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) {
    
     // 注意 while 处理多个 case
            int n = in.nextInt(),q = in.nextInt();
            int[] arr = new int[n + 1];
            for(int i = 1; i <= n; i++) arr[i] = in.nextInt();
            //创建一个同样大小的前缀和数组,并且因为是多个元素的和,
            //可能会超出int所能表示的最大范围,这里用long来表示
            long[] dp = new long[n + 1]; 
            for(int i = 1; i <= n; i++) {
    
    
                dp[i] = dp[i-1] + arr[i];
            }
            for(int i = 0; i < q; i++) {
    
    
                int l = in.nextInt(),r = in.nextInt();
                System.out.println(dp[r] - dp[l-1]);
            }
        } 
    }
}

insert image description here

2. [Template] Two-dimensional prefix sum

https://www.nowcoder.com/practice/99eb8040d116414ea3296467ce81cbbc?tpId=230&tqId=2023819&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

2.1 Topic Requirements

insert image description here

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) {
    
     // 注意 while 处理多个 case
            int a = in.nextInt();
            int b = in.nextInt();
            System.out.println(a + b);
        }
    }
}

2.2 Ideas for doing the questions

The idea of ​​two-dimensional prefix sum and one-dimensional prefix sum is basically the same, except that one is a one-dimensional array and the other is a two-dimensional array, and some processing details are different.

insert image description here

When finding dp[i][j], the dp array can be divided into four parts: A, B, C, and D. Part A is the matrix from position 0, 0 to position i - 1, j - 1 sum of elements. dp[i][j] = the sum of elements between A + B + C + D, but here the elements between B and C are not easy to sum, so we can use (A + B) + (
A + C) + D - A to find the value of dp[i][j].

Then the sum of the matrix between (x1,y1) and (x2,y2), we can use dp[x2][y2] - (A + B) - (A + C) + A, which is dp[x2] [y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1].

insert image description here

2.3 Java code implementation

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) {
    
     // 注意 while 处理多个 case
            int n = in.nextInt(),m = in.nextInt(),q = in.nextInt();
            int[][] arr = new int[n + 1][m + 1];
            for(int i = 1; i <= n; i++) {
    
    
                for(int j = 1; j <= m; j++) {
    
    
                    arr[i][j] = in.nextInt();
                }
            }
            //构造二维前缀和数组
            long[][] dp = new long[n + 1][m + 1];
            for(int i = 1; i <= n; i++) {
    
    
                for(int j = 1; j <= m; j++) {
    
    
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 1];
                }
            }
            for(int i = 0; i < q; i++) {
    
    
                int x1 = in.nextInt(),y1 = in.nextInt(),x2 = in.nextInt(),y2 = in.nextInt();
                System.out.println(dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1 - 1][y1 - 1]);
            }
        }
    }
}

insert image description here

3. Find the center subscript of the array

https://leetcode.cn/problems/find-pivot-index/description/

3.1 Topic Requirements

Given an integer array nums, please calculate the center index of the array.

The array center subscript is a subscript of the array, and the sum of all elements on the left is equal to the sum of all elements on the right.

If the center subscript is at the leftmost end of the array, the sum of the numbers on the left is considered to be 0, since there are no elements to the left of the subscript. This also applies to the center subscript at the far right of the array.

If the array has multiple center subscripts, the one closest to the left should be returned. Returns -1 if the array does not have a central subscript.

Example 1:

输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

Example 2:

输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。

Example 3:

输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。

hint:

  • 1 <= nums.length <= 104
  • -1000 <= nums[i] <= 1000
class Solution {
    
    
    public int pivotIndex(int[] nums) {
    
    

    }
}

3.2 Ideas for doing the questions

This topic requires us to find an element. The sum of all the elements on the left of this element is equal to the sum of all the elements on the right of the element. Through the previous two topics, we should have a little idea to do this topic.

Because what is required is the sum of the left part and the front part of an element, we can use two arrays to record the prefix sum and suffix sum of each element in the array respectively. The prefix sum array inserts data from front to back, and the suffix sum The array inserts data from the back to the front, and the first data of the prefix sum is 0, and the last data of the suffix sum is 0. Finally, traverse the array again to determine whether the prefix sum at a certain position in the array is equal to the suffix sum.

3.3 Java code implementation

class Solution {
    
    
    public int pivotIndex(int[] nums) {
    
    
        int n = nums.length;
        int[] f = new int[n];  //前缀和数组
        int[] g = new int[n];  //后缀和数组
        for(int i = 1; i < n; i++) {
    
    
            f[i] = f[i-1] + nums[i - 1];
        }
        for(int i = n - 2; i >= 0; i--) {
    
    
            g[i] = g[i + 1] + nums[i + 1];
        }
        for(int i = 0; i < n; i++) {
    
    
            if(f[i] == g[i]) return i;
        }

        return -1;
    }
}

insert image description here

4. Product of an array other than itself

https://leetcode.cn/problems/product-of-array-except-self/

4.1 Topic Requirements

Given an integer array nums, return the array answer, where answer[i] is equal to the product of all elements in nums except nums[i].

The title data guarantees that the product of all prefix elements and suffixes of any element in the array nums is within the range of 32-bit integers.

Please don't use division and solve this problem in O(n) time complexity.

Example 1:

输入: nums = [1,2,3,4]
输出: [24,12,8,6]

Example 2:

输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]

hint:

  • 2 <= nums.length <= 105
  • -30 <= nums[i] <= 30
  • Ensure that the product of all prefix elements and suffixes of any element in the array nums is within the range of 32-bit integers
class Solution {
    
    
    public int[] productExceptSelf(int[] nums) {
    
    

    }
}

4.2 Ideas for doing the questions

This topic is basically similar to the idea of ​​finding the central subscript of the array above, except that the operation of judging the equality of the prefix sum and the suffix sum is replaced by the product of the prefix product and the suffix product. I won’t introduce too much here, and you can directly look at the code. .

4.3 Java code implementation

class Solution {
    
    
    public int[] productExceptSelf(int[] nums) {
    
    
        int n = nums.length;
        int[] f = new int[n];
        int[] g = new int[n];
        //这里需要注意前缀积的第一个元素和后缀积的最后一个元素要初始化为1,因为是乘法
        f[0] = 1;
        g[n-1] = 1;
        for(int i = 1; i < n; i++) {
    
    
            f[i] = f[i - 1] * nums[i - 1];
        }
        for(int i = n-2; i >= 0; i--) {
    
    
            g[i] = g[i + 1] * nums[i + 1];
        }
        int[] ret = new int[n];
        for(int i = 0; i < n; i++) {
    
    
            ret[i] = f[i] * g[i];
        }

        return ret;
    }
}

insert image description here

5. The subarray whose sum is k

https://leetcode.cn/problems/subarray-sum-equals-k/description/

5.1 Topic Requirements

Given an integer array nums and an integer k, please count and return the number of continuous subarrays whose sum is k in this array.

Example 1:

输入:nums = [1,1,1], k = 2
输出:2

Example 2:

输入:nums = [1,2,3], k = 3
输出:2

hint:

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107
class Solution {
    
    
    public int subarraySum(int[] nums, int k) {
    
    

    }
}

5.2 Ideas for doing the questions

Or let's take a look at how the brute force solution is solved? Traverse the array, start with each element of the array, and then check whether the sum of the subarrays starting with this element is k. The time complexity of the brute force solution is O(N*2).

Many people may first think of sliding windows when they see this topic, but we need to look carefully at the topic. To use sliding windows, we need to ensure that the window is monotonic. The topic here does not say that the arrays are all non-negative or non-integer, so the window cannot be guaranteed. Monotonicity, sliding windows cannot be used.

To change the way of thinking, since it is looking for a sub-array whose sum is k, we can still use the idea of ​​prefix sum to find the prefix sum of this position before a certain position in the array - k = the prefix sum of a certain position before this position , and this topic is asking for the number of sub-arrays, we can count them with the hash table. What is stored in the hash table is the prefix sum of a certain position and the number of occurrences of the prefix sum.

It is slightly different from the idea of ​​violent solution. When constructing the prefix and array, we do not use each element in the array as the starting position, but each element as the ending position, which is more suitable for our prefix and thinking. .

insert image description here

5.3 Java code implementation

class Solution {
    
    
    public int subarraySum(int[] nums, int k) {
    
    
        Map<Integer,Integer> map = new HashMap<>();
        //为了防止从0开始到某一位置的子数组的和为k,所以提前放入一个前缀和为0的键值对
        map.put(0,1);  
        int n = nums.length;
        int ret = 0,sum = 0;
        for(int i = 0; i < n; i++) {
    
    
            sum += nums[i];
            ret += map.getOrDefault(sum-k,0);
            map.put(sum,map.getOrDefault(sum,0) + 1);
        }

        return ret;
    }
}

insert image description here

6. And subarrays divisible by k

https://leetcode.cn/problems/subarray-sums-divisible-by-k/description/

6.1 Topic Requirements

Given an integer array nums and an integer k , returns the number of (contiguous, non-empty) subarrays whose sum is divisible by k.

A subarray is a contiguous part of an array.

Example 1:

输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

Example 2:

输入: nums = [5], k = 9
输出: 0

hint:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • 2 <= k <= 104
class Solution {
    
    
    public int subarraysDivByK(int[] nums, int k) {
    
    

    }
}

6.2 Ideas for doing the questions

Before doing this topic, we need to know two additional knowledge points:

  1. co-theorem
  • If (a - b) % n == 0, then we can get a conclusion: a % n == b % n. In words, if the difference between the subtraction of two numbers is divisible by n, then the results of the two numbers modulo n are the same.
    insert image description here
  1. If the negative number is moduloed in c++ and Java, the result will be a negative number, so the result of the negative modulo needs to be corrected (a % n + n) % n (a is a negative number)

After knowing these two theorems, the idea of ​​this topic is similar to the idea of ​​sub-arrays with the sum of k above.
insert image description here

6.3 Java code implementation

class Solution {
    
    
    public int subarraysDivByK(int[] nums, int k) {
    
    
        Map<Integer,Integer> map = new HashMap<>();
        //同样为了防止从0开始到某一位置的子数组的乘积都能被k整除
        map.put(0 % k,1);
        int n = nums.length;
        int sum = 0,ret = 0;
        for(int i = 0; i < n; i++) {
    
    
            sum += nums[i];
            int r = (sum % k + k) % k;
            ret += map.getOrDefault(r,0);
            map.put(r,map.getOrDefault(r,0) + 1);
        }

        return ret;
    }
}

insert image description here

7. Continuous array

https://leetcode.cn/problems/contiguous-array/description/

7.1 Topic Requirements

Given a binary array nums , find the longest contiguous subarray containing the same number of 0s and 1s, and return the length of the subarray.

Example 1:

输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

Example 2:

输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

hint:

  • 1 <= nums.length <= 105
  • nums[i] is either 0 or 1
class Solution {
    
    
    public int findMaxLength(int[] nums) {
    
    

    }
}

7.2 Ideas for doing the questions

Because there are only binary numbers in the array, that is, 0 and 1, we can treat 0 as -1. When the number of 0 and 1 in the sub-array is the same, the sum of the sub-array is 0. So this topic is equivalent to finding the longest subarray with a sum of 0. So what we store in the hash table is the prefix sum and the subscript of the array.

7.3 Java code implementation

class Solution {
    
    
    public int findMaxLength(int[] nums) {
    
    
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,-1);
        int n = nums.length;
        int ret = 0,sum = 0;
        for(int i = 0; i < n; i++) {
    
    
            sum += (nums[i] == 0 ? -1 : 1);
            if(map.containsKey(sum)) ret = Math.max(ret,i-map.getOrDefault(sum,0));
            else map.put(sum,i);
        }

        return ret;
    }
}

insert image description here

8. Matrix area and

https://leetcode.cn/problems/matrix-block-sum/description/

8.1 Topic Requirements

Given an mxn matrix mat and an integer k, please return a matrix answer, where each answer[i][j] is the sum of all elements mat[r][c] that satisfy the following conditions:

  • i - k <= r <= i + k,
  • j - k <= c <= j + k 且
  • (r, c) is inside the matrix.

Example 1:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[12,21,16],[27,45,33],[24,39,28]]

Example 2:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2
输出:[[45,45,45],[45,45,45],[45,45,45]]

hint:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n, k <= 100
  • 1 <= mat[i][j] <= 100
class Solution {
    
    
    public int[][] matrixBlockSum(int[][] mat, int k) {
    
    

    }
}

8.3 Ideas for solving questions

This topic is similar to the [template] two-dimensional prefix sum above, but this topic requires us to find the corresponding matrix.
insert image description here
It should be noted that both r and c in the question start from 0, that is to say, there may be cases where the array is out of bounds. The focus of this question is to deal with the case where the subscript is out of bounds. So how to deal with the subscript out of bounds? It's very simple, doesn't the previous one start with subscript 1? When we build the prefix and array, we can also start the array subscript with 1, and then pay attention to the subscript mapping relationship when filling the data in the final result array.

8.3 Java code implementation

class Solution {
    
    
    public int[][] matrixBlockSum(int[][] mat, int k) {
    
    
        int n = mat.length;
        int m = mat[0].length;
        int[][] dp = new int[n + 1][m + 1];
        for(int i = 1; i <= n; i++) {
    
    
            for(int j = 1; j <= m; j++) {
    
    
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + mat[i - 1][j - 1] - dp[i - 1][j - 1];
            }
        }
        
        int[][] ret = new int[n][m];
        for(int i = 0; i < n; i++) {
    
    
            for(int j = 0; j < m; j++) {
    
    
            //处理下标越界问题,并且解决了下标的映射关系
                int x1 = Math.max(0,i-k) + 1,y1 = Math.max(0,j - k) + 1;
                int x2 = Math.min(n - 1,i + k) + 1,y2 = Math.min(m - 1,j  + k) + 1;
                ret[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
            }
        }

        return ret;
    }
}

insert image description here

Summarize

Through the prefix sum algorithm, we can calculate the element sum of any interval in O(1) time complexity. This is very useful when dealing with large-scale data and can greatly improve computational efficiency.

To sum up, the prefix sum algorithm is an efficient way to calculate the sum of array intervals. By calculating the prefix sum of the array, it can get the element sum of any interval within the time complexity of O(1). In practical applications, the prefix sum algorithm is often used to solve array interval and related problems, such as the maximum sum of subarrays, the average value of subarrays, etc. By mastering prefixes and algorithms, we can solve such problems more efficiently.

Guess you like

Origin blog.csdn.net/m0_73888323/article/details/132511286