前綴和

  • 前綴和
  • 二維前綴和
  • 尋找數組的中心下標
  • 除自身以外數組的乘積
  • 和為k的子數組
  • 和可被K整除的子數組
  • 連續數組
  • 矩陣區域和

前綴和

Java前綴和算法題目練習 - 實踐_數組

題目解析:在一個數組中查詢起對應區間的和,會查詢多次
算法思想:暴力解法:每次查詢都進行一次遍歷,時間複雜度O(n*m)
前綴和解法:新定義一個數組,每一個下標存放的值是要查詢數組的前下標對應值的和,這樣我們在訪問起某一個區間的時候,直接利用這個數組就非常快速

Java前綴和算法題目練習 - 實踐_數組_02


Java前綴和算法題目練習 - 實踐_數組_03

import java.util.Scanner;
// 注意類名必須為 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[] arr = new int[n+1];//此時我們讓其下標從1開始,這樣可以省略邊界問題考慮
arr[0] = 0;
for(int i = 1;i <= n;i++){
arr[i] = in.nextInt();
}
//題目意思就是給了我們長度是n的數組,但是會有m查詢
//1.首先定義一個新的數組,其每一個下標對應的值是其從arr1開始到這個下標值的和
long[] dp = new long[n+1];
for(int i = 1;i <= n;i++){
dp[i] = dp[i - 1] + arr[i];
}
while(m > 0){
int l = in.nextInt();
int r = in.nextInt();
System.out.println(dp[r] - dp[l - 1]);
m--;
}
}
}

時間複雜度:O(n + m)
空間複雜度:O(n)

二維前綴和

Java前綴和算法題目練習 - 實踐_前綴和_04

題目解析:和上一題一樣,只不過這裏變成了二維數組,要求的是對應子矩陣元素之和
前綴和:先定義一個新數組dp,其下標對應的值是原arr 數組[1,1] ~ [i, j]下標的和,此時還是讓其下標從1開始這樣就不用考慮下標越界情況

Java前綴和算法題目練習 - 實踐_子數組_05


Java前綴和算法題目練習 - 實踐_前綴和_06

import java.util.Scanner;
// 注意類名必須為 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的區別
int n = in.nextInt();
int m = in.nextInt();
int q = in.nextInt();
//下標從1開始
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];
}
}
//使用前綴和數組
while(q > 0){
int x1 = in.nextInt();
int y1 = in.nextInt();
int x2 = in.nextInt();
int y2 = in.nextInt();
System.out.println(dp[x2][y2] -dp[x1-1][y2]-dp[x2][y1-1] + dp[x1-1][y1-1]);
q--;
}
}
}

時間複雜度:O(n*m + q)
空間複雜度:O(n * m)

尋找數組的中心下標

Java前綴和算法題目練習 - 實踐_子數組_07

題目解析:找到一個下標,讓其數組左邊元素的和等於其右邊元素的和
前綴和算法:由於暴力解法每次都要進行遍歷多次,這樣時間複雜度高
因此我們可以使用f和g兩個數組,一個每一個下標用來放其前綴和,一個用來放後綴和,最後遍歷一遍判斷其f[i] == g[i]即可

Java前綴和算法題目練習 - 實踐_子數組_08

class Solution {
public int pivotIndex(int[] nums) {
int n = nums.length;
int[] f = new int[n];
int[] g = new int[n];
//此時f要從左向右填寫,因為後一個要根據前一個填寫
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;
}
}

時間複雜度:O(n)
空間複雜度:O(n)

除自身以外數組的乘積

Java前綴和算法題目練習 - 實踐_前綴和_09

題目解析:返回一個answer數組,裏面answer[i]表示的是nums數組中除了num[i]下標的值,其他所有元素的積,返回這個answer數組
前綴積:f表示前綴積,g表示後綴積

Java前綴和算法題目練習 - 實踐_前綴和_10

class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
//一個前綴積和一個後綴積
int[] f = new int[n];
int[] g = new int[n];
int[] ret = new int[n];
f[0] = 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];
}
for(int i = 0 ; i<n;i++){
ret[i] = f[i]*g[i];
}
return ret;
}
}

和為k的子數組

Java前綴和算法題目練習 - 實踐_前綴和_11

題目解析:數組中找子數組和為k的個數
前綴和:有一個前綴和數組,這樣每次我們只要在[ 0 , i-1]找出和為nums[i] - k即可,
但是如果sum[i] == k,此時就要從 [0,-1]中找0,因此可以先將<0,1>放入hash中

Java前綴和算法題目練習 - 實踐_前綴和_12

class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer,Integer> hash = new HashMap<>();
  hash.put(0,1);
  int sum = 0;//表示前綴和
  int ret = 0;
  for(int num : nums){
  sum += num;
  ret += hash.getOrDefault(sum - k, 0);//更新結果
  hash.put(sum,hash.getOrDefault(sum,0)+1);//將其放入hash中
  }
  return ret;
  }
  }

時間複雜度:O(n)
空間複雜度:O(n)

和可被K整除的子數組

Java前綴和算法題目練習 - 實踐_子數組_13

題目解析:在一個數組中找出所有子數組和可以整除k的子數組個數
這個題目和上一個求所有和為子數組的個數一樣,一些細節處理方式都差不多,唯一不一樣的是

Java前綴和算法題目練習 - 實踐_數組_14

Java前綴和算法題目練習 - 實踐_前綴和_15

class Solution {
public int subarraysDivByK(int[] nums, int k) {
Map<Integer,Integer> hash = new HashMap<>();
  //剛開始要先將0%k放入進去,也就是<0,1>,防止當
    hash.put(0 % k,1);
    int sum = 0;
    int ret  = 0;
    for(int num : nums){
    sum += num;
    int r = (sum % k + k)%k;
    ret += hash.getOrDefault( r, 0);
    hash.put(r,hash.getOrDefault(r,0)+1);
    }
    return ret;
    }
    }

時間複雜度:O(n)
空間複雜度:O(n)

連續數組

Java前綴和算法題目練習 - 實踐_數組_16

題目解析:找一個最長的子數組其和為0,並且此子數組區間中0和1的個數一樣
題目轉換,我們可以將其中的0看成-1,此時就變成了找出和為0的最長子數組,上面有一個題目是和為k的子數組,此時的k == 0
前綴和 + 哈希表
但是有很多細節不一樣,
1.這裏哈希表存放的是<前綴和,下標>
2.重複的哈希其不用存放,因為這裏要求的是最長子數組
3.長度為i - j
4.<0,-1>,前綴和為0,存放的是-1
5.先使用,後存放入哈希表中

Java前綴和算法題目練習 - 實踐_前綴和_17

class Solution {
public int findMaxLength(int[] nums) {
//此時可以將其0看成-1,此時就變成和為0的最長子數組的求解
Map<Integer,Integer> hash = new HashMap<>();
  //當其[0,i]位置和為0時候,我們要從[0,-1]中找出和為0
  hash.put(0,-1);
  int len = 0;
  int sum = 0;
  for(int i = 0;i < nums.length ;i++){
  if(nums[i] == 0){
  sum -= 1;
  }else{
  sum += 1;
  }
  //從前面找和為sum,那後面和就為0,當然其j越靠前越好
  //因此如果已經有了和為sum,後面的就不用放進去了
  if(hash.containsKey(sum)){
  len = Math.max(len,i - hash.get(sum));
  }else{
  //放入hash中
  hash.put(sum,i);
  }
  }
  return len;
  }
  }

時間複雜度:O(n)
空間複雜度:O(n)

矩陣區域和

Java前綴和算法題目練習 - 實踐_前綴和_18

題目解析:給了一個二維數組,我們要返回一個相同的二維數組,每一個下標對應的值是其原本二維數組距離其為k的元素所圍成的一個矩形中元素之和
前綴和矩陣,上面有一個題目是求(x1,y1) 到 (x2,y2)所圍成矩陣的和就用到了前綴和矩陣
因此這裏可以先求出前綴和數組,在使用的時候直接根據k求出其是那兩個座標 圍成的矩形,有了座標可以使用前綴和矩陣,這樣就求出對應的值了
但是此時要注意其下標是不一樣的,前綴和矩陣下標是從1開始

Java前綴和算法題目練習 - 實踐_子數組_19


Java前綴和算法題目練習 - 實踐_數組_20


Java前綴和算法題目練習 - 實踐_數組_21

class Solution {
public int[][] matrixBlockSum(int[][] mat, int k) {
int m = mat.length;
int n = mat[0].length;
int[][] dp = new int[m+1][n+1];
//構建前綴和其下標從1開始
for(int i = 1;i<= m;i++){
for(int j = 1;j<= n;j++){
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + mat[i-1][j-1];
}
}
int[][] ret = new int[m][n];
//使用的時候下標也要+1這樣才能正常使用前綴和數組
for(int i = 0;i<m;i++){
for(int j = 0;j<n;j++){
int x1 = Math.max(i-k,0) + 1;
int y1 = Math.max(j-k,0) + 1;
int x2 = Math.min(i+k, m -1) + 1;
int y2 = Math.min(j+k, n - 1) + 1;
ret[i][j] = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1];
}
}
return ret;
}
}

時間複雜度:O(m * n)
空間複雜度:O(m * n)