• leetcode hot 100-560. 和为K的子数组


     560. 和为K的子数组

     给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

    示例 1 :

    输入:nums = [1,1,1], k = 2
    输出: 2 , [1,1] 与 [1,1] 为两种不同的情况

    说明 :

    数组的长度为 [1, 20,000]。
    数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

    思路一:暴力法

    双重循环,枚举所有的子数组,判断每个子数组的和是否为k

     1 class Solution {
     2     public int subarraySum(int[] nums, int k) {
     3         // 双重循环
     4         // 枚举所有的子数组,判断每个子数组的和是否为k
     5         int count = 0;
     6         int len = nums.length;
     7         for(int i = 0; i < len; i++){
     8             int sum = 0;
     9             for(int j = i; j < len; j++){
    10                 sum += nums[j];
    11                 count += ((sum == k) ? 1 : 0);
    12             }
    13         }
    14         return count;
    15     }
    16 }

    leetcode 执行用时:1783 ms > 5.08%, 内存消耗:41.7 MB > 5.04%,从执行时间上看这个算法很低效

    复杂度分析:

    时间复杂度:O(n2)。使用双循环枚举了所有的子数组,所以时间复杂度为O(n2)。

    空间复杂度:O(1)。只使用了常数个变量的空间。

    思路二:前缀和 + 哈希

    思路参考:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/he-wei-kde-zi-shu-zu-by-leetcode-solution/ 和 https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/bao-li-jie-fa-qian-zhui-he-qian-zhui-he-you-hua-ja/

    假设有一个前缀和数组,这个数组的每个元素preSum[i]存储了 nums[i] 元素和nums[i]元素之前的所有元素之和,即[0, i]之间的所有元素和,那么如果[i, j]之间的元素元素之和为恰好k, 那么

    preSum[i] - preSum[j] = k 

    所以要判断是否存在以 nums[i] 结尾的子数组的和为 k , 只需要判断是否存在 preSum[j] 即可,对等式进行移项后得到

    preSum[i] - k = preSum[j]
    所以要判断是否存在以 nums[i] 结尾的子数组的和为 k , 只需要判断是否存在 (preSum[i] - k) 即可,所以如果我们创建一个HashMap, map中存储的键是前缀和 、值是该前缀和出现的次数,那么当(preSum[i] - k) 存在于map中时,map.get(preSum[i] - k) 前缀和出现的次数即表示了以 nums[i] 结尾的子数组中和为 k的子数组的数量。让计数器加上这个出现次数即可。
    【优化】因为因为preSum[i]的计算只和preSum[i-1]有关,所以可以不必提前把所有元素的前缀和求出来,不需要维护一个前缀和数组,只需要定义一个preSum然后不断更新迭代即可,可以在遍历数组的同时求出当前元素的前缀和
     1 class Solution {
     2     public int subarraySum(int[] nums, int k) {
     3 
     4         HashMap<Integer, Integer> map = new HashMap<>();
     5         map.put(0, 1);  // 当还没有枚举元素时,前缀和为0,出现了一次
     6         int preSum = 0;
     7         int count = 0;
     8         for(int num : nums){
     9             preSum += num;
    10             if(map.containsKey(preSum - k)){
    11                 count += map.get(preSum - k);
    12             }
    13             map.put(preSum, map.getOrDefault(preSum, 0) + 1);  // 把当前前缀和对应的值加一
    14         }
    15         return count;
    16     }
    17 }
    leetcode 执行用时:26 ms > 49.73%, 内存消耗:40.9 MB > 15.84%, 这个时间消耗小相较于思路一小了很多

    复杂度分析:

    时间复杂度:O(n)。遍历了一次数组,所以时间复杂度为O(n)。
    空间复杂度:O(n)。空间开销主要来自map, map中的元素个数决定了空间复杂度,map中最多有n个键值对,即当所有元素均为正数或者均为负数时,所有前缀和都是一个键值对,此时空间复杂度为O(n)。
  • 相关阅读:
    linux开放防火墙端口方法
    Node.js 构建TCP服务
    React高阶组件
    在create-react-app 中启用装饰器语法
    Redux
    微信小程序组件与组件之间的通讯
    微信小程序网络请求
    微信小程序的页面导航跳转
    sqoop2启动job报错
    sqoop2启动client异常
  • 原文地址:https://www.cnblogs.com/hi3254014978/p/13934020.html
Copyright © 2020-2023  润新知