力扣 Hot 100 题解 (js版)更新ing

news2025/1/29 6:33:25

🚩哈希表

1. 两数之和

Code:

暴力法

复杂度分析:

  • 时间复杂度: ∗ O ( N 2 ) ∗ *O(N^2)* O(N2),其中 N 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。
  • 空间复杂度:O(1)。
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    let len = nums.length
    for (let i = 0; i < len; i++) {
        for (let j = i + 1; j < len; j++) {
            if (nums[i] + nums[j] === target) {
                return [i, j]
            }
        }
    }
};

哈希表

复杂度分析:

  • 时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。
  • 空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    // 查找表
    let map = new Map()
    for(let i = 0; i < nums.length; i++) {
        if(map.has(target - nums[i])) {
            return [map.get(target - nums[i]), i]
        }
        map.set(nums[i], i)
    }
    return []
};



49. 字母异位词分组

题目 “字母异位词分组” 的目标是将一组字符串按照字母异位词(即字符串由相同的字母组成,字母顺序不同)分组。

我们可以通过以下思路解决:

思路:

  1. 特征值:排序法
    • 字母异位词具有一个共同特点:它们的字母排序后会得到相同的结果。
    • 例如:"eat""tea" 排序后都是 "aet"
    • 将每个字符串的排序结果作为键,将具有相同排序结果的字符串分到同一组。
  2. 数据结构
    • 使用一个哈希表(Map),键是排序后的字符串,值是一个数组,存储具有相同特征值的字符串。
  3. 步骤
    • 遍历字符串数组,对每个字符串排序。
    • 将排序后的字符串作为键存入哈希表,值是对应的字符串数组。
    • 遍历完成后,哈希表的值即为分组结果。

Code:

复杂度分析

  1. 时间复杂度
    • 对每个字符串进行排序的时间复杂度为 O ( k log ⁡ k ) O(k \log k) O(klogk),其中 k k k 是字符串的平均长度。
    • 遍历数组的时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是字符串数组的长度。
    • 总时间复杂度为 O ( n ⋅ k log ⁡ k ) O(n \cdot k \log k) O(nklogk)
  2. 空间复杂度
    • 使用了一个哈希表来存储分组结果,空间复杂度为 O ( n ⋅ k ) O(n \cdot k) O(nk),其中 n n n 是字符串数组的长度, k k k 是字符串的平均长度。
/**
 * 字母异位词分组
 * @param {string[]} strs
 * @return {string[][]}
 */
function groupAnagrams(strs) {
    const map = new Map(); // 创建一个哈希表
    for (const str of strs) {
        // 对字符串排序后作为特征值
        const sortedStr = str.split('').sort().join('');
        // 如果哈希表中没有该特征值,则初始化为一个数组
        if (!map.has(sortedStr)) {
            map.set(sortedStr, []);
        }
        // 将字符串加入对应的组
        map.get(sortedStr).push(str);
    }
    // 返回所有分组的数组
    return Array.from(map.values());
}

// 测试用例
const strs = ["eat", "tea", "tan", "ate", "nat", "bat"];
console.log(groupAnagrams(strs));

运行结果

对于输入 ["eat", "tea", "tan", "ate", "nat", "bat"],输出为:

[
  ["eat", "tea", "ate"],
  ["tan", "nat"],
  ["bat"]
]

扩展(优化排序法)

如果希望优化排序的时间,可以考虑使用字符计数代替排序,形成一个唯一的特征值,例如:

  • 对于 "eat",记录字符频率为 "1#1#1#0#0#...",用这种方式构建键。



✅ 128. 最长连续序列

题目 128:最长连续序列

给定一个未排序的整数数组,找出其中最长连续元素序列的长度。要求算法的时间复杂度为 O ( n ) O(n) O(n)


解题思路:

要实现 O ( n ) O(n) O(n) 的时间复杂度,我们可以利用哈希表(Set)来快速检查连续的数字是否存在:

  1. 将所有数字存入哈希表(Set
    • 使用哈希表存储数组中的所有数字,便于快速查找某个数字是否存在。
  2. 遍历数组,寻找序列的起点
    • 对每个数字 num,检查是否存在 num - 1
    • 如果不存在 num - 1,说明 num 是一个序列的起点。
    • num 开始,依次检查 num + 1 是否存在,计算序列的长度。
  3. 记录最长序列的长度
    • 在遍历过程中,更新最长序列长度。

Code:

复杂度分析

  1. 时间复杂度
    • 将数组转换为哈希表的时间复杂度是 O ( n ) O(n) O(n)
    • 遍历每个数字,并检查哈希表的操作为 O ( 1 ) O(1) O(1)
    • 每个序列中的数字最多只被遍历一次,因此整体时间复杂度为 O ( n ) O(n) O(n)
  2. 空间复杂度
    • 需要额外的哈希表存储所有数字,空间复杂度为 O ( n ) O(n) O(n)
/**
 * 最长连续序列
 * @param {number[]} nums
 * @return {number}
 */
function longestConsecutive(nums) {
    // 将数组转化为哈希集合
    const numSet = new Set(nums);
    let maxLength = 0;

    // 遍历数组中的每个数字
    for (const num of numSet) {
        // 如果 num 是序列的起点(num - 1 不存在)
        if (!numSet.has(num - 1)) {
            let currentNum = num;
            let currentLength = 1;

            // 找到当前序列的长度
            while (numSet.has(currentNum + 1)) {
                currentNum += 1;
                currentLength += 1;
            }

            // 更新最大长度
            maxLength = Math.max(maxLength, currentLength);
        }
    }

    return maxLength;
}

// 测试用例
const nums = [100, 4, 200, 1, 3, 2];
console.log(longestConsecutive(nums)); // 输出: 4 (最长序列是 [1, 2, 3, 4])




🚩 双指针

283. 移动零

暴力法

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度:O(1)

这段代码的时间复杂度是 ( O(n^2) ),这是由于 splice 操作引起的。为了优化,应该避免在循环中频繁操作数组结构,比如可以采用额外的空间存储非零元素,最后再重组数组,这样可以将时间复杂度降低到 ( O(n) )。

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    let len = nums.length
    for (let i = 0; i < len; i++) {
        if (nums[i] === 0) {
            nums.splice(i, 1)
            nums.push(0)
            i--
            len--
        }
    }
};


指针法

思路:

  • 从左到右遍历 nums[i]。

同时维护另一个下标 index(初始值为 0),并保证下标区间 [ index, i−1] 都是空位,且 index 指向最左边的空位。每次遇到 nums[i] =0 的情况,就把 nums[i] 移动到最左边的空位上,也就是交换 nums[i] 和 nums[index]。交换后把 index和 i 都加一,从而使【[index,i−1] 都是空位】这一性质仍然成立。如果 nums[i]=0,无需交换,只把 i 加一。

示例 1 的 nums=[0,1,0,3,12],计算过程如下(下划线表示交换的两个数):

在这里插入图片描述

Code:

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    let index = 0
    let len = nums.length
    for (let i = 0; i < len; i++) {
        if (nums[i] !== 0) {
            // 交换非0元素到前面
            [nums[index], nums[i]] = [nums[i], nums[index]]
            index++
        }
    }
};


11. 盛最多水的容器

问题描述:

给定一个数组 height,其中 height[i] 代表一个点的高度,表示横坐标为 i 处的高度。找出两个线段,使得它们和 x 轴所围成的容器能够容纳最多的水。返回这个容器能够容纳的水的最大值。

例子:

输入: height = [1,8,6,2,5,4,8,3,7]
输出: 49
解释: 最终盛水容器的高度为 7(索引 1 和 8 之间的最小高度),宽度为 8(索引 8 减去索引 1),所以最大水量为 7 * 8 = 49。

题目分析:

这是一个经典的 容器问题,要求找出两个垂直的线段,它们与 x 轴围成的容器能够容纳最多的水。显然,容器的面积是由这两个线段之间的距离(宽度)和两条线段的高度中的较小值决定的。

  • 宽度:由两个指针之间的距离决定,假设两个指针分别指向数组的索引 leftright,那么宽度为 right - left
  • 高度:由较短的线段的高度决定,即 Math.min(height[left], height[right])

解题思路:

我们可以使用 双指针 方法来解决这个问题,以下是详细的步骤:

  1. 初始化:设置两个指针,left 指向数组的第一个元素,right 指向数组的最后一个元素。
  2. 计算当前容器的水量:每次计算当前两个指针之间容器的水量,即 Math.min(height[left], height[right]) * (right - left)
  3. 更新最大水量:记录当前最大水量,并更新最大值。
  4. 移动指针:为了找到可能更大的水量,每次移动指向较小高度的指针。
    • 如果 height[left] < height[right],说明移动左指针可能会找到更大的水量,因为左指针所在的高度较小。
    • 否则,移动右指针。

Code:

复杂度分析:

  • 时间复杂度 O ( n ) O(n) O(n) ,我们使用双指针,每次移动一个指针,最多遍历一次数组,因此时间复杂度为 O ( n ) O(n) O(n) ,其中 n 是数组的长度。
  • 空间复杂度 O ( 1 ) O(1) O(1) ,我们只使用了常量级的额外空间。
/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let left = 0;          // 初始化左指针
    let right = height.length - 1;  // 初始化右指针
    let maxArea = 0;       // 记录最大水量

    while (left < right) {
        // 计算当前容器的水量
        const currentArea = Math.min(height[left], height[right]) * (right - left);
        // 更新最大水量
        maxArea = Math.max(maxArea, currentArea);

        // 移动指针,移动较小的那个指针
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }

    return maxArea;
};

代码解释:

  1. 初始化
    • left = 0:指向数组的第一个元素。
    • right = height.length - 1:指向数组的最后一个元素。
    • maxArea = 0:记录当前的最大水量。
  2. 循环
    • left 小于 right 的情况下,计算当前容器的水量。
    • currentArea = Math.min(height[left], height[right]) * (right - left):计算当前水量,Math.min(height[left], height[right]) 是容器的高度,right - left 是容器的宽度。
    • 使用 Math.max(maxArea, currentArea) 更新最大水量。
  3. 移动指针
    • 如果 height[left] < height[right],移动左指针 left++,否则移动右指针 right--
    • 移动指针的目的是寻找可能的更高的容器边界,以增加水量。
  4. 返回最大水量:最终,返回 maxArea,即找到的最大容器水量。
    总结:
  • 使用 双指针 方法能高效地解决这个问题,通过每次移动较小的指针来尝试找到更大的水容积。
  • 时间复杂度为 ( O(n) ),空间复杂度为 ( O(1) ),这使得这个方法非常高效。


15. 三数之和

问题描述:

给定一个包含 n 个整数的数组 nums,判断数组中是否存在三个元素 nums[i]nums[j]nums[k](满足 i != ji != kj != k),使得它们的和为零。如果存在,返回所有不重复的三元组。

示例 1

输入: nums = [-1, 0, 1, 2, -1, -4]
输出: [[-1, -1, 2], [-1, 0, 1]]

示例 2

输入: nums = []
输出: []

示例 3

输入: nums = [0, 0, 0]
输出: [[0, 0, 0]]

解题思路:

这个问题通常是使用 排序双指针 来优化寻找三元组的过程。其基本思路如下:

  1. 排序数组:首先,我们将输入数组进行排序。这是为了便于利用双指针方法减少不必要的计算。
  2. 固定一个元素:通过循环遍历数组,固定一个元素 nums[i],然后在剩余的部分使用双指针查找其余两个元素,使得三者和为零。
  3. 双指针法:对于每次固定的 nums[i],设置两个指针 leftright,分别指向当前元素后面的第一个元素和数组的最后一个元素。通过移动 leftright 来找到符合条件的两个数。
  4. 去重:由于数组可能包含重复元素,我们需要避免重复的三元组,可以通过跳过重复元素来去重。

Code:

复杂度分析:

  • 时间复杂度
    • 排序时间复杂度为 ( O ( n log ⁡ n ) O(n \log n) O(nlogn)
    • 遍历每个元素时,内部的双指针查找两个数的时间复杂度为 ( O ( n ) O(n) O(n)
    • 因此,总的时间复杂度是 O ( n 2 O(n^2 O(n2)。
  • 空间复杂度
    • 由于使用了一个额外的数组来存储结果,空间复杂度是 ( O ( n ) O(n) O(n),其中 n n n 是数组的长度。
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    let result = [];
    // 1. 排序数组
    nums.sort((a, b) => a - b);

    // 2. 遍历数组,固定一个元素,利用双指针查找另外两个数
    for (let i = 0; i < nums.length - 2; i++) {
        // 2.1 跳过重复的元素
        if (i > 0 && nums[i] === nums[i - 1]) {
            continue;
        }

        let left = i + 1, right = nums.length - 1;

        while (left < right) {
            let sum = nums[i] + nums[left] + nums[right];

            if (sum === 0) {
                // 找到符合条件的三元组
                result.push([nums[i], nums[left], nums[right]]);

                // 跳过重复的元素
                while (left < right && nums[left] === nums[left + 1]) left++;
                while (left < right && nums[right] === nums[right - 1]) right--;

                // 移动指针
                left++;
                right--;
            } else if (sum < 0) {
                // 和小于 0,说明需要增大 sum,左指针右移
                left++;
            } else {
                // 和大于 0,说明需要减小 sum,右指针左移
                right--;
            }
        }
    }

    return result;
};

代码解析:

  1. 排序数组

    nums.sort((a, b) => a - b);
    
    

    排序数组的目的是为了后续使用双指针可以更容易地处理和为零的情况。如果没有排序,我们就不能有效地利用双指针来加速查找。

  2. 遍历数组并固定一个元素

    for (let i = 0; i < nums.length - 2; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) {
            continue;
        }
    
    

    我们遍历数组中的每个元素作为固定的第一个元素 nums[i]。在遍历过程中,如果遇到重复的元素,就跳过,以避免重复计算相同的三元组。

  3. 双指针查找其余两个元素

    let left = i + 1, right = nums.length - 1;
    while (left < right) {
        let sum = nums[i] + nums[left] + nums[right];
    
    

    对于每个固定的元素 nums[i],我们通过 leftright 两个指针来查找剩下的两个元素。初始时,left 指向 i+1right 指向数组的最后一个元素。

  4. 判断三元组和的大小并调整指针

    • 如果三者和 sum 为 0,说明找到了符合条件的三元组,将其加入结果数组。
    • 如果 sum < 0,说明和太小,需要增大和,因此移动 left 指针。
    • 如果 sum > 0,说明和太大,需要减小和,因此移动 right 指针。
  5. 去重处理
    在找到一个三元组后,我们需要跳过重复的元素,以避免结果中包含重复的三元组:

    while (left < right && nums[left] === nums[left + 1]) left++;
    while (left < right && nums[right] === nums[right - 1]) right--;
    
    
  6. 返回结果
    最终,我们返回所有符合条件的三元组。

总结:

  • 使用排序和双指针方法是解决 三数之和 问题的常见方法。
  • 排序可以帮助我们减少不必要的计算,使得时间复杂度从 (O(n^3)) 降低到 (O(n^2))。
  • 通过跳过重复元素,可以避免结果中出现重复的三元组。

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

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

相关文章

DeepSeek-R1:性能对标 OpenAI,开源助力 AI 生态发展

DeepSeek-R1&#xff1a;性能对标 OpenAI&#xff0c;开源助力 AI 生态发展 在人工智能领域&#xff0c;大模型的竞争一直备受关注。最近&#xff0c;DeepSeek 团队发布了 DeepSeek-R1 模型&#xff0c;并开源了模型权重&#xff0c;这一举动无疑为 AI 领域带来了新的活力。今…

CY T 4 BB 5 CEB Q 1 A EE GS MCAL配置 - MCU组件

1、ResourceM 配置 选择芯片信号: 2、MCU 配置 2.1 General配置 1) McuDevErrorDetect: - 启用或禁用MCU驱动程序模块的开发错误通知功能。 - 注意:采用DET错误检测机制作为安全机制(故障检测)时,不能禁用开发错误检测。2) McuGetRamStateApi - enable/disable th…

校园商铺管理系统设计与实现(代码+数据库+LW)

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自…

【JavaWeb学习Day13】

Tlias智能学习系统 需求&#xff1a; 部门管理&#xff1a;查询、新增、修改、删除 员工管理&#xff1a;查询、新增、修改、删除和文件上传 报表统计 登录认证 日志管理 班级、学员管理&#xff08;实战内容&#xff09; 部门管理&#xff1a; 01准备工作 开发规范-…

springboot使用tomcat浅析

springboot使用tomcat浅析 关于外部tomcat maven pom配置 // 打包时jar包改为war包 <packaging>war</packaging>// 内嵌的tomcat的scope标签影响范围设置为provided&#xff0c;只在编译和测试时有效&#xff0c;打包时不带入 <dependency><groupId>…

如何使用CRM数据分析优化销售和客户关系?

嘿&#xff0c;大家好&#xff01;你有没有想过为什么有些公司在市场上如鱼得水&#xff0c;而另一些却在苦苦挣扎&#xff1f;答案可能就藏在他们的销售策略和客户关系管理&#xff08;CRM&#xff09;系统里。今天我们要聊的就是如何通过有效的 CRM 数据分析来提升你的销售额…

Qt 控件与布局管理

1. Qt 控件的父子继承关系 在 Qt 中&#xff0c;继承自 QWidget 的类&#xff0c;通常会在构造函数中接收一个 parent 参数。 这个参数用于指定当前空间的父控件&#xff0c;从而建立控件间的父子关系。 当一个控件被设置为另一控件的子控件时&#xff0c;它会自动成为该父控…

电力场效应晶体管(电力 MOSFET),全控型器件

电力场效应晶体管&#xff08;Power MOSFET&#xff09;属于全控型器件是一种电压触发的电力电子器件&#xff0c;一种载流子导电&#xff08;单极性器件&#xff09;一个器件是由一个个小的mosfet组成以下是相关介绍&#xff1a; 工作原理&#xff08;栅极电压控制漏极电流&a…

一文讲解Java中的重载、重写及里氏替换原则

提到重载和重写&#xff0c;Java小白应该都不陌生&#xff0c;接下来就通过这篇文章来一起回顾复习下吧&#xff01; 重载和重写有什么区别呢&#xff1f; 如果一个类有多个名字相同但参数不同的方法&#xff0c;我们通常称这些方法为方法重载Overload。如果方法的功能是一样…

Pandas基础02(DataFrame创建/索引/切片/属性/方法/层次化索引)

DataFrame数据结构 DataFrame 是一个二维表格的数据结构&#xff0c;类似于数据库中的表格或 Excel 工作表。它由多个 Series 组成&#xff0c;每个 Series 共享相同的索引。DataFrame 可以看作是具有列名和行索引的二维数组。设计初衷是将Series的使用场景从一维拓展到多维。…

Meta-CoT:通过元链式思考增强大型语言模型的推理能力

大型语言模型&#xff08;LLMs&#xff09;在处理复杂推理任务时面临挑战&#xff0c;这突显了其在模拟人类认知中的不足。尽管 LLMs 擅长生成连贯文本和解决简单问题&#xff0c;但在需要逻辑推理、迭代方法和结果验证的复杂任务&#xff08;如高级数学问题和抽象问题解决&…

【时时三省】(C语言基础)二进制输入输出

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 二进制输入 用fread可以读取fwrite输入的内容 字符串以文本的形式写进去的时候&#xff0c;和以二进制写进去的内容是一样的 整数和浮点型以二进制写进去是不一样的 二进制输出 fwrite 字…

【go语言】数组和切片

一、数组 1.1 什么是数组 数组是一组数&#xff1a;数组需要是相同类型的数据的集合&#xff1b;数组是需要定义大小的&#xff1b;数组一旦定义了大小是不可以改变的。 1.2 数组的声明 数组和其他变量定义没有什么区别&#xff0c;唯一的就是这个是一组数&#xff0c;需要给…

SQL-leetcode—1179. 重新格式化部门表

1179. 重新格式化部门表 表 Department&#xff1a; ---------------------- | Column Name | Type | ---------------------- | id | int | | revenue | int | | month | varchar | ---------------------- 在 SQL 中&#xff0c;(id, month) 是表的联合主键。 这个表格有关…

k8s简介,k8s环境搭建

目录 K8s简介环境搭建和准备工作修改主机名&#xff08;所有节点&#xff09;配置静态IP&#xff08;所有节点&#xff09;关闭防火墙和seLinux&#xff0c;清除iptables规则&#xff08;所有节点&#xff09;关闭交换分区&#xff08;所有节点&#xff09;修改/etc/hosts文件&…

基于微信小程序的网上订餐管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

Java使用FFM API调用SDL

首发于Enaium的个人博客 首先我们需要创建一个Gradle项目&#xff0c;之后设置项目的JDK版本&#xff0c;设置为22及以上版本。 plugins {kotlin("jvm") version "2.1.0" }group "cn.enaium" version "1.0-SNAPSHOT"repositories {…

【数据结构】深入解析:构建父子节点树形数据结构并返回前端

树形数据结构列表 一、前言二、测试数据生成三、树形代码3.1、获取根节点3.2、遍历根节点&#xff0c;递归获取所有子节点3.3、排序3.4、完整代码 一、前言 返回前端VO对象中&#xff0c;有列情况列表展示需要带树形结构&#xff0c;例如基于RBAC权限模型中的菜单返回&#xf…

JAVA 使用反射比较对象属性的变化,记录修改日志。使用注解【策略模式】,来进行不同属性枚举值到中英文描述的切换,支持前端国际化。

1.首先定义一个接口&#xff0c;接口中有两个方法&#xff0c;分别是将属性转换成英文描述和中文描述。 其实就是将数据库中记录的 0 1 &#xff0c;转换成后面的描述 这边定义了中文转换为默认方法&#xff0c;是因为有些属性不需要进行中文转换&#xff0c;或者该属性的枚举…

基于模糊PID的孵化箱温度控制系统(论文+源码)

1系统方案设计 本课题为基于模糊PID的孵化箱温度控制系统&#xff0c;其以STM32最小系统与模糊PID控制器为控制核心。系统主要包括数据采集模块、处理器模块、电机控制模块。 数据采集模块由温度传感器构成&#xff0c;通过温度传感器感应温度变化&#xff0c;获得待处理的数据…