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;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/904968.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

01背包类问题

文章目录 [模版]01背包1. 第一问: 背包不一定能装满(1) 状态表示(2) 状态转移方程(3) 初始化(4) 填表顺序(5) 返回值 2. 第二问: 背包恰好装满3. 空间优化 416.分割等和子集1. 状态表示2. 状态转移方程3. 初始化4. 填表顺序5. 返回值 [494. 目标和](https://leetcode.cn/proble…

解锁 DevOps 新境界 :使用 Flux 进行 GitOps 现场演示 – 自动化您的 Kubernetes 部署

前言 GitOps 是实现持续部署的云原生方式。它的名字来源于标准且占主导地位的版本控制系统 Git。GitOps 的 Git 在某种程度上类似于 Kubernetes 的 etcd&#xff0c;但更进一步&#xff0c;因为 etcd 本身不保存版本历史记录。毋庸置疑&#xff0c;任何源代码管理服务&#xf…

将Docker镜像变为可执行文件?体验docker2exe带来的便捷!

在现代软件开发中,容器化技术极大地改变了应用程序部署和管理的方式。Docker,作为领先的容器化平台,已经成为开发者不可或缺的工具。然而,对于不熟悉Docker的用户来说,接触和运行Docker镜像可能会是一个复杂的过程。为了解决这一问题,docker2exe项目应运而生。它提供了一…

IBM BAW(原BPM升级版)使用教程第八讲

续前篇&#xff01; 一、流程开发功能模块使用逻辑和顺序 前面我们已经对 流程、用户界面、公开的自动化服务、服务、事件、团队、数据、性能、文件各个模块进行了详细讲解&#xff0c;现在统一进行全面统一讲解。 在 IBM Business Automation Workflow (BAW) 中&#xff0c;…

针对共享内存和上述windows消息机制 在C++ 和qt之间的案例 进行详细举例说明

针对共享内存和上述windows消息机制 在C++ 和qt之间的案例 进行详细举例说明 以下是关于在 C++ 和 Qt 中使用共享内存(QSharedMemory)和 Windows 消息机制(SendMessage / PostMessage)进行跨线程或跨进程通信的详细示例。 🧩 使用 QSharedMemory 进行进程间通信(Qt 示例…

jetson orin nano super AI模型部署之路(十)使用frp配置内网穿透,随时随地ssh到机器

为什么要内网穿透&#xff1f; 我们使用jetson设备时&#xff0c;一般都是在局域网内的电脑去ssh局域网内的jetson设备&#xff0c;但是这种ssh或者VNC仅限于局域网之间的设备。 如果你出差了&#xff0c;或者不在jetson设备的局域网内&#xff0c;想再去ssh或者VNC我们的jet…

VScode密钥(公钥,私钥)实现免密登录【很细,很全,附带一些没免密登录成功的一些解决方法】

一、 生成SSH密钥对 ssh-keygen 或者 ssh-keygen -t rsa -b 4096区别&#xff1a;-t rsa可以明确表示生成的是 RSA 类型的密钥-b参数将密钥长度设置为 4096 位默认&#xff1a;2048 位密钥不指定-t参数&#xff0c;ssh -keygen默认也可能生成 RSA 密钥【确保本机安装ssh&#…

解释器和基于规则的系统比较

解释器&#xff08;Interpreter&#xff09;和基于规则的系统&#xff08;Rule-Based System&#xff09;是两种不同的软件架构风格&#xff0c;分别适用于不同的应用场景。它们在设计理念、执行机制和适用领域上有显著差异。以下是它们的核心对比&#xff1a; 1. 解释器&#…

DB4S:一个开源跨平台的SQLite数据库管理工具

DB Browser for SQLite&#xff08;DB4S&#xff09;是一款开源、跨平台的 SQLite 数据库管理工具&#xff0c;用于创建、浏览和编辑 SQLite 以及 SQLCipher 数据库文件。 功能特性 DB4S 提供了一个电子表格风格的数据库管理界面&#xff0c;以及一个 SQL 查询工具。DB4S 支持…

printf调试时候正常,运行时打印不出来

问题是在添加了 printf 功能后&#xff0c;程序独立运行时无法正常打印输出&#xff0c;而调试模式下正常。这表明问题可能与 printf 的重定向实现、标准库配置、或编译器相关设置有关。 解决&#xff1a; 原来是使用 Keil/IAR&#xff0c;printf可能需要启用 MicroLIB 或正确…

轻松制作高质量视频,实时生成神器LTX-Video重磅登场!

探索LTX-Video&#xff1a;实时视频生成跨越新高度 在如今这个视觉内容主导的数字时代&#xff0c;视频生成成为推动创意表达的关键。而今天&#xff0c;我们将带您深入探索LTX-Video&#xff0c;一个强大的开源项目&#xff0c;致力于通过尖端技术将视频生成提升到一个全新的…

分布式事务快速入门

分布式事务基本概念 使用分布式事务的场景&#xff1a;分布式场景下的跨数据库事务 分布式事务诞生的理论&#xff1a;CAP和Base 3种一致性&#xff1a; 强一致性 &#xff1a;系统写入了什么&#xff0c;读出来的就是什么。 弱一致性 &#xff1a;不一定可以读取到最新写入…

nvme Unable to change power state from D3cold to D0, device inaccessible

有个thinkpad l15 gen4笔记本&#xff0c;使用较少&#xff0c;有一块三星m2和东芝14t硬盘&#xff0c;想安装飞牛nas系统作为家庭照片库&#xff0c;制作飞牛启动盘&#xff0c;发现安装飞牛需要全盘格式化&#xff0c;电脑本身的系统还是需要保留的&#xff0c;故想到再安装一…

Unity Shaders and Effets Cookbook

目录 作者简介 审稿人简介 前言 我是偏偏 Unity Shaders and Effets Cookbook 第一章&#xff1a;Diffuse Shading - 漫反射着色器 第二章&#xff1a;Using Textures for Effects - 着色器纹理特效的应用 第三章&#xff1a;Making Your Game Shine with Specular - 镜…

部署RocketMQ

部署环境&#xff1a;jdk8以上&#xff0c;Linux系统 下载和安装指令&#xff1a; wget https://archive.apache.org/dist/rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip 显示下载成功&#xff1a; --2025-05-10 11:34:46-- https://archive.apache.org/dist/rocketm…

使用FastAPI和React以及MongoDB构建全栈Web应用04 MongoDB快速入门

一、NoSQL 概述 1.1 了解关系数据库的局限性 Before diving into NoSQL, it’s essential to understand the challenges posed by traditional Relational Database Management Systems (RDBMS). While RDBMS have been the cornerstone of data management for decades, th…

高精度之加减乘除之多解总结(加与减篇)

开篇总述&#xff1a;精度计算的教学比较杂乱&#xff0c;无系统的学习&#xff0c;且存在同法多线的方式进行同一种运算&#xff0c;所以我写此篇的目的只是为了直指本质&#xff0c;不走教科书方式&#xff0c;步骤冗杂。 一&#xff0c;加法 我在此讲两种方法&#xff1a; …

气象大模型光伏功率预测中的应用:从短期,超短期,中长期的实现与开源代码详解

1. 引言 光伏功率预测对于电力系统调度、能源管理和电网稳定性至关重要。随着深度学习技术的发展,大模型(如Transformer、LSTM等)在时间序列预测领域展现出强大能力。本文将详细介绍基于大模型的光伏功率预测方法,涵盖短期(1-6小时)、超短期(15分钟-1小时)和中长期(1天-1周…

玩转Docker(一):基本概念

容器技术是继大数据和云计算之后又一炙手可热的技术&#xff0c;而且未来相当一段时间内都会非常流行。 本文将对其基本概念和基本使用做出介绍。包括容器生态系统、容器的原理、怎样运行第一个容器、容器技术的概念与实践、Docker镜像等等 目录 一. 鸟瞰容器生态系统 1. 容器…

计算机视觉与深度学习 | 基于数字图像处理的裂缝检测与识别系统(matlab代码)

🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅 基于数字图像处理的裂缝检测与识别系统 🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦**系统架构设计****1. 图像预处理**目标:消除噪声+增强裂缝特征**2. 图像分割**目标:提取裂缝区域**3. 特征…