视频指路
https://www.bilibili.com/video/BV1iq4y117xZ?spm_id_from=333.999.0.0
算法专题精进 掌握前缀和
刚好30分钟,这个视频主要讲了小松在过去一周对前缀和的梳理,是用34寸屏幕录的,所以看起来有点窄,另外不知道为什么感觉有时不时的破音,下次用笔记本录吧~
下文是这期视频的源代码和一些总结,不过需要先看完视频后才能看懂哦
二分
209. 长度最小的子数组
class Solution {
public int minSubArrayLen(int target, int[] nums) {
// 连续子数组 的运算结果
int [] preSum = new int[nums.length + 1];
for(int i = 1 ; i < preSum.length; i++){
preSum[i] = preSum[i - 1] + nums[i - 1];
}
/**
nums [2,3,1,2,4,3]
preSum [0,2,5,6,8,12,15]
preSum[y] - preSum[x] >= target
preSum[x] + target <= preSum[y];
x < y
0 - > len
x = i
// 前缀和递增时,可以使用二分查找
*/
int res = Integer.MAX_VALUE;
for(int i = 0; i < preSum.length; i++ ){
int targetNum = target + preSum[i];
int upBound = Arrays.binarySearch(preSum, targetNum);
if(upBound < 0)upBound = -upBound - 1;
if(upBound < preSum.length)res = Math.min(res, upBound - i);
}
return res == Integer.MAX_VALUE ? 0: res;
}
}
哈希
[523. 连续的子数组和
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int [] preSum = new int[nums.length + 1];
for(int i = 1; i < preSum.length; i++){
preSum[i] = preSum[i - 1] + nums[i - 1];
}
// 同余定理,两个前缀和对k 取余的余数一样 k= 5 6 -> 1 11 - > 1
// 差 是k的倍数
// 记忆化
// [23,2, 4, 6,6 ]
// [0,23,25,29,35,41]
Map<Integer, Integer>map = new HashMap<>();// 余数,i
for(int i = 2; i < preSum.length; i++){
int number = preSum[i - 2] % k;
map.put(number, i - 2);
if(map.containsKey(preSum[i] % k)){
return true;
}
}
return false;
}
}
525. 连续数组
class Solution {
public int findMaxLength(int[] nums) {
// 记录counter 放入map中,如果下次遇到相同的counter,表示在两次counter之间, 0,1相同
// map记录之前的位置
int count = 0;
int maxMumber = 0;
Map<Integer, Integer>map = new HashMap<>();
map.put(count, -1);
for(int i = 0; i < nums.length; i++){
if(nums[i] == 1)count++;
else count --;
if(map.containsKey(count)){
int preIndex = map.get(count);
maxMumber = Math.max(maxMumber, i - preIndex);
}else{
map.put(count, i);
}
}
return maxMumber;
}
}
974. 和可被 K 整除的子数组
同余定理
class Solution {
public int subarraysDivByK(int[] nums, int k) {
// 构造前缀和
int [] preSum = new int[nums.length + 1];
for(int i = 1; i < preSum.length; i++){
preSum[i] = preSum[i - 1] + nums[i - 1];
System.out.print(preSum[i] + " ");
}
System.out.println();
/**
// 面对含负数的数组?前缀和不再单调
[4,5,0,-2,-3,1]
[0,4,9, 9, 7,4,5]
*/
int res = 0;
// 找边界
Map<Integer, Integer>map = new HashMap<>();
map.put(0, 1);// 余数为0 直接先加一次
for(int i = 1; i < preSum.length; i++){
int modulus = (preSum[i] % k + k) % k;
int same = map.getOrDefault(modulus, 0); // 相同余数,证明两个前缀和之差能被5整除
res += same;
map.put(modulus, same + 1);
}
return res;
}
// 5 1
// 0 2
// -2-3 3
}
238. 除自身以外数组的积
class Solution {
public int[] productExceptSelf(int[] nums) {
int [] preSum = new int[nums.length + 1];
int [] oreSum = new int[nums.length + 1];
preSum[0] = 1;
preSum[1] = 1;
oreSum[nums.length] = 1;
oreSum[nums.length - 1] = 1;
for(int i = 1; i < nums.length; i++){
preSum[i + 1] = preSum[i] * nums[i - 1];
}
for(int i = nums.length - 2; i >= 0; i--){
oreSum[i] = oreSum[i + 1] * nums[i + 1];
}
int [] res = new int[nums.length];
for(int i = 0; i < nums.length; i++){
res[i] = preSum[i + 1] * oreSum[i] ;
}
return res;
}
}
2121. 相同元素的间隔之和
class Solution {
public long[] getDistances(int[] arr) {
//前缀,<key,val>表示值为key的前面一个相同的下标为val[0],相同的个数为val[1]
Map<Integer, int[]> m1 = new HashMap<>();
int n = arr.length;
long[] re1 = new long[n];
for (int i = 0; i < n; i++) {
int[] orDefault = m1.getOrDefault(arr[i], new int[2]);
//相同的下标为ordefaule[0],相同了几个为orderfault[1]
if (orDefault[1] != 0) {
re1[i] += re1[orDefault[0]] + (i - orDefault[0]) * orDefault[1];
}
orDefault[0] = i;
orDefault[1]++;
m1.put(arr[i], orDefault);
}
//后缀
Map<Integer, int[]> m2 = new HashMap<>();
long[] re2 = new long[n];
for (int i = n - 1; i >= 0; i--) {
int[] orDefault = m2.getOrDefault(arr[i], new int[2]);
//当其后面有与他下相同的时候。相同的下标为ordefaule[0],相同了几个为orderfault[1]
if (orDefault[1] != 0) {
re2[i] += re2[orDefault[0]] + (orDefault[0] - i) * orDefault[1];
}
orDefault[0] = i;
orDefault[1]++;
m2.put(arr[i], orDefault);
}
long[] re = new long[n];
for (int i = 0; i < n; i++) {
re[i] = re1[i]+re2[i];
}
return re;
}
}
前缀和
涉及到连续子数组,就可以用
可以先直接写框架
如果前缀和单调递增,可以二分求边界
如果不能使用二分,可以使用hash记忆化
后缀同样适用
框架代码
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
// 构建前缀和
int [] preSum = new int[nums.length + 1];
for(int i = 1; i < preSum.length; i++){
preSum[i] = preSum[i - 1] + nums[i - 1];
}
/** 这段注释可以更加直观解题
nums [23, 2, 4, 6, 7]
preSum [0, 23,25,29,35,42]
*/
// 记忆化(非必须)
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
// 遍历,注意i 的初始值需要根据题意变化
for(int i = ?; i < preSum.length; i++){
}
}
}