前言
本文是基于labuladong算法秘籍的学习总结,是算法总结中的数组与链表篇章,其他篇章以及资料下载在算法学习总结目录中下载,传送门:算法学习总结
本人是算法菜鸟一枚,通过在学习过程中总结来提升自我,共同进步,有理解不当的地方欢迎大家指正!
一、前缀和理解
前缀和技巧适⽤于快速、频繁地计算⼀个索引区间内的元素之和。前缀和可用于一维及多维数组。
二、原理与代码
2.1 一维数组的前缀和
以典型例题为例,展示一维数组前缀和的构造思路:
给定一个整数数组 nums
,求出数组从索引 i
到 j
(i ≤ j
)范围内元素的总和,包含 i
、j
两点。
class NumArray {
private int[] preSum;
public NumArray(int[] nums) {
preSum = new int[nums.length+1];
// preSum[0] = 0; 便于累加
for(int i = 1;i<preSum.length;i++){
preSum[i] = preSum[i-1]+nums[i-1];
}
}
public int sumRange(int left, int right) {
return preSum[right+1]-preSum[left];
}
}
值得注意地方有以下几点:
1、preSum前缀和数组要比原始数组的长度+1
2、构造函数中用于累加的for循环中的i在很多地方也能看到从0开始,若从0开始则如下:
for(int i = 0;i<preSum.length;i++){
preSum[i+1] = preSum[i]+nums[i];
}
2.2 二维数组的前缀和
类似一维数组的前缀和思路,二维数组仅仅在构造前缀和中增加了写固定的计算的步骤,近似于在一维前缀和数组的for循环中使用了不同计算步骤,同样以典型例题讲解原理:
sumRegion([2,1,4,3])返回的元素和为8。
对于二维数组,我们同样会构造一个preSum,只是在for循环中构造的计算步骤稍区别于一维,具体计算步骤思路如下:
想计算红⾊的这个⼦矩阵的元素之和,可以⽤绿⾊矩阵减去蓝⾊矩阵减去橙⾊矩阵最后加上粉⾊矩阵,⽽绿蓝橙粉这四个矩阵有⼀个共同的特点,左上⻆是 (0, 0) 原点。
代码如下:
class NumMatrix {
// preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和
private int[][] preSum;
public NumMatrix(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
if (m == 0 || n == 0) return;
// 构造前缀和矩阵
preSum = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 计算每个矩阵 [0, 0, i, j] 的元素和
preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] + matrix[i- 1][j - 1] - preSum[i-1][j-1];
}
}
}
// 计算⼦矩阵 [x1, y1, x2, y2] 的元素和
public int sumRegion(int x1, int y1, int x2, int y2) {
// ⽬标矩阵之和由四个相邻矩阵运算获得
return preSum[x2+1][y2+1] - preSum[x1][y2+1] - preSum[x2+1][y1] + preSum[x1][y1];
}
}
三、典型例题
基于以上的知识可以解决Leetcode中的如下题目:
303. 区域和检索 - 数组不可变(中等)
304. ⼆维区域和检索 - 矩阵不可变(中等)
560. 和为K的⼦数组(中等)
四、补充知识
这里将对本节中涉及的一些基础知识做一些补充和拓展
1、构造函数
在前缀和的算法题中,题目经常会要求我们实现一个前缀和的类,这个类将被如下方式调用:
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* int param_1 = obj.sumRange(left,right);
*/
因此,要记得实现一个有参构造函数,因此在此拓展一下构造函数的相关知识:
1)每个方法必然有一个构造方法,有参或无参,若没有提供有参,则会自动构造一个无参。
2)创建一个子类对象的实例的时候,必先调用父类的无参构造函数(默认构造函数),除非子类显示指定super。若父类的无参为private或在有有参情况下,没有显示定义无参,或者子类没有显示指定继承父类构造方法,编译会报错。
因此,解决方法一般是,在子类super指定,或者在父类显示添加无参构造方法。
2、修饰符范围
上面的构造函数中提到了修饰符,因此,补充一下修饰符的基础。
还有final、和abstract修饰符:
final修饰的类不能被继承,没有子类。
abstract修饰的类不能被实例化,必须被子类继承。类只要有一个抽象方法就必定是抽象类,但抽象类不一定要有抽象方法。
3、二维数组
二维数组可以理解为是一维数组,只不过他的各处的元素是特殊元素—–一维数组
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
//定义一个整型数组:3行4列int a[][] = new int[3][4];
//获取行数---3行int lenY = a.length;
//获取列数---4列int lenX = a[0].length;