前缀和的解题思想

前缀和思想

前缀和的题目解题思维比较固定,即当我们循环数组到下标N时,需要用到数组前N-1项的计算的结果(这里不一定非要是和,也可能是积等),此时我们就该考虑是否应该通过计算数组循环过程中的累计值的方式简化解题,如此便有了前缀和的解题思想。

了解了思想,下来就该考虑,这个累计的结果我们该通过什么方式保存起来呢?

1.题目明确要求不允许使用额外空间的,直接原地修改数组
2.不限制空间复杂度时,最好额外开辟空间计算,避免数据污染
3.计算时如果每次只需要获取前一次的累计结果,可以通过数组的方式存储每次获取数组末尾元素的值
4.如果每次计算需要获取前几次或更多次的结果进行对比时,推荐哈希表的方式,这样可以压缩时间复杂度

题型一

题目:剑指OfferII010.和为k的子数组 给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。

提示:

1 <= nums.length <= 2 * 10 ^ 4
-1000 <= nums[i] <= 1000
-10 ^ 7 <= k <= 10 ^ 7 示例

示例 1 : 输入:nums = [1,1,1], k = 2 输出: 2 解释: 此题 [1,1] 与 [1,1] 为两种不同的情况

示例 2 : 输入:nums = [1,2,3], k = 3 输出: 2
示例3:输入:nums = [1,1,1,1,-1000,1000], k = 3 输出: 2

总结:让找连续子数组、子串的,如果连续子数组的状态可以通过前缀和中连续子数组的加加减减得到,那么就是用前缀和思想。
题解:本题思想很容易想到两个for循环暴力解法,示例3如下图所示
dawdwa
  很容易看到i=1的一轮内层for循环所做的遍历相加在i=0那一轮其实已经做过一次了,只不过上一轮多计算了一个nums[0],减去nums[0]后的遍历和下一轮一模一样并不完全一样,i=1轮遍历数据个数为5,而i=0轮为6,所以应该将i=0轮的nums[i]出现的次数去掉1,这样保证了i=1轮不会搜索到上一轮第一个数据。所以想到可以把i=0的那一轮数据保存起来;在i=1轮时,保存的数据集体减去一个nums[0]同时nums[0]出现次数-1就可以用了;在i=2轮时,保存的数据集体减去一个nums[0]+nums[1]同时nums[0]+nums[1]出现次数-1就可以用了,以此类推。保存下i=0轮的数据需要On时间复杂度。
  数据保存完毕后(假设使用数组保存),就可以开始查询了,i=0时查询k在数组中存在次数;i=1时,如果还查询k,那么数组元素应集体把nums[0]减去后再查,与其操作一边数组,不如直接查询k+nums[0]在数组存在次数;以此类推,i=2查询k+nums[0]+nums[1]在数组出现次数。
  而查询一个元素在数组中出现次数需要On时间复杂度,所以用数组存储并不是一个好的选择,所以可以使用哈希表存储,这样只需O1时间复杂度。
代码如下:

package leetcode.offer;

import com.sun.jdi.Value;

import java.security.Key;
import java.util.HashMap;

public class T10adv {
    public static void main(String[] args) {
        System.out.println(new T10adv().subarraySum(new int[]{1,2,3},3));
    }

    public int subarraySum(int[] nums, int k) {
        HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
        int pre_sum=0,total=0;
        for (int i = 0; i < nums.length; i++) {//保存一轮以0开头,不同结尾的前缀和
            pre_sum+=nums[i];
            if(hashMap.get(pre_sum)==null)hashMap.put(pre_sum,1);
            else hashMap.computeIfPresent(pre_sum,(Key, value)->value+1);
        }
        total+=hashMap.get(k)==null?0:hashMap.get(k);//查询k出现次数
        pre_sum=k;
        for (int i = 0; i < nums.length-1; i++) {
            pre_sum+=nums[i];//第i+1轮
            hashMap.replace(pre_sum-k,hashMap.get(pre_sum-k)-1);//去掉上一轮的第一个数据出现次数
            total+=hashMap.get(pre_sum)==null?0:hashMap.get(pre_sum);
        }
        return total;
    }
}

优化

上述解题思路中可以看出,我们需要预先将i=0轮的前缀和全部保存下来,之后模拟i=1轮时又要去掉又要减去的,非常麻烦,于是想到:能否在计算i=0轮的某一次前缀和时就同时把后面要查找的k,k+nums[0]…k+k+nums[0]+…+一次查询完毕呢?示意图如下:

在计算pre_sum[i]时,同时查询是否有k,k+nums[0]…k+k+nums[0]+…+存在。换种说法,查询是否存在j使得pre_sum[i]-pre_sum[j]==k,即pre_sum[j]=pre_sum[i]-k,有多少个j,就代表本次有多少以j结尾的子数组满足等于k,代码如下:

package leetcode.offer;

import java.util.HashMap;

public class T10Best {
    public static void main(String[] args) {
        System.out.println(new T10Best().subarraySum(new int[]{1,2,3},3));
    }
    public int subarraySum(int[] nums, int k) {
        int pre_sum=0,total=0;
        HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
        map.put(0,1);//pre_sum[j]的j前缀和不包含一个元素时
        for (int i = 0; i < nums.length; i++) {
            pre_sum+=nums[i];
            total+=map.getOrDefault(pre_sum-k,0);//get之后放put,因为这个pre_sum[j]不能是j==i的情况,所以本轮pre_sum[i]get完再放。
            map.put(pre_sum,map.getOrDefault(pre_sum,0)+1);
        }
        return total;
    }
}

题型二

上一题是让求连续子数组出现次数,还有一种题型让求连续子数组最大长度,思路大同小异,只不过记录pre_sum[i]的出现次数就没有意义了,应该转而记录pre_sum[i]对应的数组长度(小的留下,大的舍去)

剑指 Offer II 011. 0 和 1 个数相同的子数组
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:

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

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

package leetcode.offer;

import java.util.HashMap;

/**
 * 给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
 * 右边界扩张后不满足条件时,继续扩张仍有可能满足条件,不能用滑动窗口
 */
public class T11 {
    public static void main(String[] args) {
        System.out.println(new T11().findMaxLength(new int[]{0,1}));
    }
    public int findMaxLength(int[] nums) {
        int pre_sum=0;
        int max=0;//记录最长子数组长度
        HashMap<Integer,Integer> map=new HashMap<>();
        map.put(0,0);
        for (int i = 0; i < nums.length; i++) {
            pre_sum+=(nums[i]==0?-1:1);
            if(map.get(pre_sum)!=null) max=Math.max(max,i+1-map.get(pre_sum));
            map.putIfAbsent(pre_sum,i+1);
        }
        return max;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值