当前位置: 首页 > news >正文

hot100-子串-JS

一、560.和为k的子串

560. 和为 K 的子数组

 

提示

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 

子数组是数组中元素的连续非空序列。

示例 1:

输入:nums = [1,1,1], k = 2
输出:2

示例 2:

输入:nums = [1,2,3], k = 3
输出:2

提示:

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107

1.暴力枚举法

暴力枚举的基本思路是通过两层循环来枚举所有可能的子数组,然后计算每个子数组的和,判断其是否等于目标值 k,如果相等则将计数器加 1。

/*** @param {number[]} nums* @param {number} k* @return {number}*/
var subarraySum = function (nums, k) {let count = 0;for (let i = 0; i < nums.length; i++) {let sum = 0;for (let j = i; j < nums.length; j++) {sum += nums[j];if (sum === k) {count++}}}return count;
};

2.使用前缀和与哈希表(推荐)

问题分析

给定一个整数数组 nums 和一个整数 k,要找出数组中和为 k 的子数组(连续非空元素序列)的个数。例如,对于数组 [1, 1, 1] 和 k = 2,子数组 [1, 1] 满足和为 2,所以结果是 2

前缀和的引入

前缀和是指从数组开头到当前位置的所有元素的和。假设我们有一个数组 nums = [a0, a1, a2, ..., an],那么:

  • 前缀和 prefixSum[0] = a0
  • 前缀和 prefixSum[1] = a0 + a1
  • 前缀和 prefixSum[2] = a0 + a1 + a2
  • 以此类推,prefixSum[i] = a0 + a1 + ... + ai

对于计算子数组和,有一个重要的性质:如果我们要找从索引 j 到索引 ij <= i)的子数组和,那么这个子数组和 subarraySum(j, i) 就等于 prefixSum[i] - prefixSum[j - 1](当 j > 0 时),如果 j = 0,则 subarraySum(j, i) = prefixSum[i]

算法思路推导

我们的目标是找到满足 subarraySum(j, i) = k 的子数组个数。根据上面的前缀和性质,subarraySum(j, i) = prefixSum[i] - prefixSum[j - 1] = k,可以变形为 prefixSum[j - 1] = prefixSum[i] - k

这意味着,如果我们知道所有前缀和的值以及它们出现的次数,那么对于当前计算得到的前缀和 prefixSum[i],我们只需要检查之前是否出现过 prefixSum[i] - k 这个前缀和。如果出现过,那么就说明存在一个子数组的和为 k,并且出现次数就是 prefixSum[i] - k 出现的次数。

哈希表的作用

为了高效地存储和查询前缀和及其出现的次数,我们使用哈希表(在 JavaScript 中是 Map 对象)。具体步骤如下:

  1. 初始化​:

    • 创建一个空的 Map 对象 map,用于存储前缀和及其出现的次数。
    • 初始化 map.set(0, 1),这是因为前缀和为 0 的情况是存在的(对应空数组,空数组的和为 0),出现次数为 1
    • 初始化变量 sum = 0 来记录当前的前缀和,ans = 0 来记录和为 k 的子数组的个数。
  2. 遍历数组​:

    • 对于数组中的每个元素 num,将其加到当前前缀和 sum 中,即 sum += num
    • 检查 map 中是否存在 sum - k。如果存在,说明存在一个子数组的和为 k,将 map.get(sum - k)的值加到 ans 中。这是因为 sum - (sum - k) = kmap.get(sum - k) 表示之前出现过 sum - k 这个前缀和的次数,也就意味着有这么多组子数组的和为 k
    • 将当前前缀和 sum 及其出现的次数存入 map 中。如果 sum 已经存在于 map 中,将其出现次数加 1;否则,将其出现次数设为 1
  3. 返回结果​:

    • 遍历结束后,ans 中存储的就是和为 k 的子数组的个数,将其返回。

示例分析

以输入 nums = [1, 1, 1]k = 2 为例:

  • 初始化:map = {0: 1}sum = 0ans = 0
  • 第一次循环:
    • num = 1sum = 0 + 1 = 1
    • 检查 map 中是否有 1 - 2 = -1,没有。
    • map.set(1, 1)(现在 map = {0: 1, 1: 1})。
    • ans 不变,仍为 0
  • 第二次循环:
    • num = 1sum = 1 + 1 = 2
    • 检查 map 中是否有 2 - 2 = 0,有,ans = ans + map.get(0) = 0 + 1 = 1
    • map.set(2, 1)(现在 map = {0: 1, 1: 1, 2: 1})。
  • 第三次循环:
    • num = 1sum = 2 + 1 = 3
    • 检查 map 中是否有 3 - 2 = 1,有,ans = ans + map.get(1) = 1 + 1 = 2
    • map.set(3, 1)(现在 map = {0: 1, 1: 1, 2: 1, 3: 1})。
  • 最终返回 ans = 2,符合预期。

通过这样的方式,使用前缀和与哈希表的方法能够高效地解决“和为 K 的子数组”问题。希望以上解释能帮助你理解这个算法的原理和实现过程。

代码实现

/*** @param {number[]} nums* @param {number} k* @return {number}*/
var subarraySum = function (nums, k) {let map=new Map();// 初始设置为0的元素有1个map.set(0,1);let sum=0;let ans=0;for(let num of nums){// 计算元素的前缀和sum+=num;// 计数,统计前缀和为某个数共有多少个map.set(sum,(map.has(sum)||0)+1)// 判断map中是否有 sum-k 如果存在,说明存在一个子数组的和为 kif(map.has(sum-k)){// 将 map.get(sum - k)的值加到 ans 中ans+=map.get(sum-k);}}return ans;
};

二、239.滑动窗口最大值

239. 滑动窗口最大值

 

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       31 [3  -1  -3] 5  3  6  7       31  3 [-1  -3  5] 3  6  7       51  3  -1 [-3  5  3] 6  7       51  3  -1  -3 [5  3  6] 7       61  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

1.暴力法(超出时间限制)

O(nk)会很慢

/*** @param {number[]} nums* @param {number} k* @return {number[]}*/
function maxSlidingWindow(nums, k) {let result=[]for (let i = 0; i < nums.length - k + 1; i++) {let max = -Infinity;for (let j = i; j < i + k; j++) {max = Math.max(nums[j], max)}result.push(max)}return result
}

2.优化解法(双端队列,时间复杂度 O(n))​

算法思路

  1. 初始化数据结构​:

    • q 数组模拟单调栈,它的作用是存储数组 nums 中元素的下标,并且保证栈内元素对应的 nums 值从栈底到栈顶是单调递减的。这样,栈顶元素对应的 nums 值始终是当前窗口内的最大值(或者候选最大值)。
    • ans 数组用于存储每个滑动窗口的最大值,最终作为函数的返回结果。
  2. 遍历数组过程​:

    • 维护单调性​:在每次将新元素 nums[i] 考虑加入窗口时,通过 while 循环检查栈顶元素。如果当前元素 nums[i] 大于等于栈顶元素对应的 nums 值,就不断弹出栈顶元素。这是因为栈顶元素不可能是当前窗口内的最大值了,通过这样的操作保证了栈的单调性。
    • 加入新元素​:将当前元素的下标 i 压入栈 q 中,以便后续判断该元素是否在窗口内以及作为可能的最大值候选。
    • 检查窗口范围​:检查栈顶元素的下标是否已经不在当前窗口范围内(即小于等于 i - k)。如果是,说明该元素已经随着窗口的滑动移出了当前窗口,需要将其从栈顶弹出,以确保栈中元素对应的下标都在当前窗口内。
    • 记录最大值​:当窗口已经形成(即 i >= k - 1,因为前 k - 1 个元素构不成完整窗口)时,栈顶元素对应的 nums 值就是当前窗口的最大值,将其加入到 ans 数组中。
  3. 返回结果​:遍历完整个数组 nums 后,ans 数组中已经存储了每个滑动窗口的最大值,将其返回。

示例演示

给定的示例 nums = [1, 3, -1, -3, 5, 3, 6, 7]k = 3 为例,详细演示如何计算滑动窗口最大值的:

初始化

  • 初始化 q 为一个空数组,用于模拟单调栈,存储数组元素的下标;初始化 ans 为一个空数组,用于存储每个滑动窗口的最大值。

  • i 初始化为 0,开始遍历数组 nums

第一次循环 (i = 0)

  • nums[0] = 1,此时 q 为空,将 i = 0 压入 q,即 q = [0]

  • 因为 q 只有一个元素,它的下标 0 满足 0 >= 0 - 3(此时窗口还未完全形成,这里条件看似不成立但后续会处理),不执行 q.shift()

  • 由于 i = 0 < 3 - 1,窗口还未形成,不执行 ans.push(nums[q[0]])

第二次循环 (i = 1)

  • nums[1] = 3,因为 3 >= nums[q[q.length - 1]](即 3 >= nums[0]nums[0] = 1),执行 q.pop(),此时 q = [1]

  • 将 i = 1 压入 qq = [1]

  • 因为 q[0] = 11 < 1 - 3 不成立,不执行 q.shift()

  • 由于 i = 1 < 3 - 1,窗口还未形成,不执行 ans.push(nums[q[0]])

第三次循环 (i = 2)

  • nums[2] = -1,因为 -1 < nums[q[q.length - 1]](即 -1 < nums[1]nums[1] = 3),不执行 q.pop()

  • 将 i = 2 压入 qq = [1, 2]

  • 因为 q[0] = 11 < 2 - 3 不成立,不执行 q.shift()

  • 由于 i = 2 >= 3 - 1,窗口已经形成,执行 ans.push(nums[q[0]]),即 ans = [3]

第四次循环 (i = 3)

  • nums[3] = -3,因为 -3 < nums[q[q.length - 1]](即 -3 < nums[2]nums[2] = 3),不执行 q.pop()

  • 将 i = 3 压入 qq = [1, 2, 3]

  • 因为 q[0] = 11 < 3 - 3 不成立,不执行 q.shift()

  • 由于 i = 3 >= 3 - 1,窗口已经形成,执行 ans.push(nums[q[0]]),即 ans = [3, 3]

第五次循环 (i = 4)

  • nums[4] = 5,因为 5 >= nums[q[q.length - 1]](即 5 >= nums[3]nums[3] = -3),执行 q.pop(),此时 q = [1, 2]

  • 因为 5 >= nums[q[q.length - 1]](即 5 >= nums[2]nums[2] = 3),再执行 q.pop(),此时 q = [1]

  • 将 i = 4 压入 qq = [1]

  • 因为 q[0] = 11 < 4 - 3 不成立,不执行 q.shift()

  • 由于 i = 4 >= 3 - 1,窗口已经形成,执行 ans.push(nums[q[0]]),即 ans = [3, 3, 5]

第六次循环 (i = 5)

  • nums[5] = 3,因为 3 >= nums[q[q.length - 1]](即 3 >= nums[1]nums[1] = 1),执行 q.pop(),此时 q = [5]

  • 将 i = 5 压入 qq = [5]

  • 因为 q[0] = 55 < 5 - 3 不成立,不执行 q.shift()

  • 由于 i = 5 >= 3 - 1,窗口已经形成,执行 ans.push(nums[q[0]]),即 ans = [3, 3, 5, 5]

第七次循环 (i = 6)

  • nums[6] = 6,因为 6 >= nums[q[q.length - 1]](即 6 >= nums[5]nums[5] = 3),执行 q.pop(),此时 q = [6]

  • 将 i = 6 压入 qq = [6]

  • 因为 q[0] = 66 < 6 - 3 不成立,不执行 q.shift()

  • 由于 i = 6 >= 3 - 1,窗口已经形成,执行 ans.push(nums[q[0]]),即 ans = [3, 3, 5, 5, 6]

第八次循环 (i = 7)

  • nums[7] = 7,因为 7 >= nums[q[q.length - 1]](即 7 >= nums[6]nums[6] = 6),执行 q.pop(),此时 q = [7]

  • 将 i = 7 压入 qq = [7]

  • 因为 q[0] = 77 < 7 - 3 不成立,不执行 q.shift()

  • 由于 i = 7 >= 3 - 1,窗口已经形成,执行 ans.push(nums[q[0]]),即 ans = [3, 3, 5, 5, 6, 7]

循环结束

遍历完整个数组 nums 后,ans 数组中存储了每个滑动窗口的最大值,最终返回 ans = [3, 3, 5, 5, 6, 7]

代码实现

var maxSlidingWindow = function(nums, k) {let q = []; // 用于模拟单调栈,存储数组元素的下标let ans = []; // 用于存储每个滑动窗口的最大值for (let i = 0; i < nums.length; i++) {// 当栈不为空,并且当前元素大于等于栈顶元素对应的nums值时while (q.length > 0 && nums[i] >= nums[q[q.length - 1]]) {q.pop(); // 弹出栈顶元素,因为它不可能是当前窗口内的最大值了}q.push(i); // 将当前元素的下标压入栈中// 如果栈顶元素的下标已经不在当前窗口范围内(即小于等于i - k)if (q[0] <= i - k) {q.shift(); // 弹出栈顶元素,因为它已经不在当前窗口内了}// 当窗口已经形成(即i >= k - 1,因为前k - 1个元素构不成完整窗口)if (i >= k - 1) {ans.push(nums[q[0]]); // 将栈顶元素对应的nums值(也就是当前窗口的最大值)加入到结果数组ans中}}return ans;
};

相关文章:

  • 17.Excel:实用的 VBA 自动化程序
  • 嵌入式Web服务器lighttpd交叉编译详解
  • 8.2.CICD自动化
  • 青藏高原七大河流源区径流深、蒸散发数据集(TPRED)
  • 远程调试---在电脑上devtools调试运行在手机上的应用
  • 在 Excel 中有效筛选重复元素
  • 365打卡第R8周: RNN实现阿尔茨海默病诊断
  • Jmeter中的Json提取器如何使用?
  • CH579 CH573 CH582 CH592 蓝牙主机(Central)实例应用讲解
  • 生产级AI/ML特征存储平台:Feast全面使用指南 — Use Cases Third party integrations FAQ
  • TransmittableThreadLocal:穿透线程边界的上下文传递艺术
  • PostgreSQL 的 pg_advisory_lock_shared 函数
  • 机器学习 day01
  • 【金仓数据库征文】金融行业中的国产化数据库替代应用实践
  • 抖音视频上传功能测试全维度拆解——从基础功能到隐藏缺陷的深度挖掘
  • 【25软考网工】第六章(2)信息加密技术
  • 机器视觉光源的特点及选择应用
  • springboot3+vue3融合项目实战-大事件文章管理系统-更新用户信息
  • [亲测搭建可用]LoliMeow主题二次元风博客WordPress主题模板
  • 基于GF域的多进制QC-LDPC误码率matlab仿真,译码采用EMS算法
  • 印巴开始互袭军事目标,专家:冲突“螺旋式升级”后果或不可控
  • 市自规局公告收回新校区建设用地,宿迁学院:需变更建设主体
  • 洲际酒店:今年第一季度全球酒店平均客房收入同比增长3.3%
  • 国家主席习近平同普京总统举行大范围会谈
  • 司法部谈民营经济促进法:对违规异地执法问题作出禁止性规定
  • 马克思主义理论研究教学名师系列访谈|曾瑞明:想通了才可能认准,认准了才能做好