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

大白话拆解力扣算法 HOT 100 - 哈希/双指针/滑动窗口

哈希

1. 两数之和 - 力扣(LeetCode)

题目大意:你手里有一堆数字(比如 [2,7,11,15]),还有一个目标值(比如 9)。

你要在这堆数字里,找出两个数,让它们加起来正好等于目标值。

然后,你要返回这两个数在数组里的位置(下标)。

注意:不能用同一个位置的数两次,比如同一个数不能加自己两次(除非它出现了两次)。

// 暴力双重循环
class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {// 1.外层循环:i 从第一个数开始,一直到倒数第二个数for (int i = 0; i < nums.size(); i++) {// 2.内层循环:j 从 i 后面那个数开始,一直到最后一个数// 这样就不会重复使用同一个元素,也不会重复检查for (int j = i + 1; j < nums.size(); j++) {// 3.判断 nums[i] 和 nums[j] 加起来是不是等于目标值if (nums[i] + nums[j] == target) {// 4.如果是,直接返回这两个数的下标return {i,j};}}}// 5.反之返回一个空数组return {};}
};

这道题很简单,直接暴力循环就可以解。

49. 字母异位词分组 - 力扣(LeetCode)

题目大意:你有一堆单词,比如:["eat", "tea", "tan", "ate", "nat", "bat"]

现在你要把这些单词按“字母异位词”分组。

就是两个词,它们的字母一模一样,只是顺序不同。

比如 "eat""tea":都是 e、a、t,只是排列不同 → 是异位词

我们要把所有“字母长得一样”的词放在一起。

我们来分组:

  • "eat""tea""ate" → 都是 a、e、t → 一组
  • "tan""nat" → 都是 a、n、t → 一组
  • "bat" → 只有它自己有 b、a、t → 单独一组

所以输出就是:

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

这里的顺序可以是任意的。

class Solution {
public:vector<vector<string>> groupAnagrams(vector<string>& strs) {// 1.创建存储字典unordered_map<string, vector<string>> mp;// 2.遍历单词// s 是当前单词,比如 "eat"for (string& s : strs) {// 2.1 复制一份当前单词,准备当钥匙string key = s;// 2.2 把钥匙里的字母按顺序排好// 比如 "eat" → 排序后变成 "aet"sort(key.begin(), key.end());// 2.3 把原始单词 s 放进对应钥匙的组里// 如果 key 是 "aet",就把 "eat" 加到 "aet" 这个组里mp[key].push_back(s);}// 3.把所有的组取出来,放进结果数组vector<vector<string>> res;// 4.遍历字典里的每一组for (auto& p : mp) {// 4.1 p 是一个键值对,p.second 是这一组的所有单词(比如 {"eat","tea"})// // 把这一组加到结果里res.push_back(p.second);}return res;}
};

怎么判断两个词是不是“字母异位词”?

把它们的字母字母表顺序重新排列,变成“标准形式”。

比如:

  • "eat" → 排序后 → "aet"
  • "tea" → 排序后 → "aet"
  • "ate" → 排序后 → "aet"

它们都变成 "aet" → 说明是一伙的!

所以我们可以用这个排序后的字符串当“钥匙”,把所有“钥匙”一样的词归到一组。

数据结构选择:unordered_map

我们用一个“字典”来存:

key:排序后的单词(比如 "aet"

value:所有原始单词组成的列表(比如 ["eat", "tea", "ate"]

128. 最长连续序列 - 力扣(LeetCode)

题目大意:

给你一个乱序的整数数组,比如:[100,4,200,1,3,2]

你要找出最长的“数字连续序列”有多长。

什么叫“数字连续序列”?
就是像 1,2,3,4 这样,一个比一个多1的数列。
注意:这些数在原数组里不需要挨着,只要它们存在就行!

class Solution {
public:int longestConsecutive(vector<int>& nums) {// 1.特判:数组为空if (nums.empty()) return 0;// 2.排序数组sort(nums.begin(), nums.end());// 3.去重nums.erase(unique(nums.begin(), nums.end()), nums.end());// 4.找最长序列// res -> 记录当前最长长度  len -> 当前连续序列的长度int res = 1, len = 1;// 5.从第二个数开始,一个个往前比for (int i = 1; i < nums.size(); i++) {// 5.1 判断当前数是否比前一个数大1if (nums[i] == nums[i-1] + 1) {// 5.2 连续序列长度 +1len++;} else {// 5.3 连续序列长度不变len = 1;}// 5.4 每次都更新一下“历史最长长度”res = max(res, len);}return res;}
};

这道题的思路是双指针:算法详细讲解:基础算法 - 位运算/双指针算法-CSDN博客

双指针

283. 移动零 - 力扣(LeetCode)

题目大意:

你有一个数组,比如:[0,1,0,3,12],把所有的 0 都移到数组最后面。其他不是 0 的数字保持原来的顺序不变,而且不能新建数组,必须在原来的数组上直接改。

比如:[0,1,0,3,12]    ->    [1,3,12,0,0]

class Solution {
public:void moveZeroes(vector<int>& nums) {// 思路:直接将非0数字左移,再填充剩下的格子为0// 1.定义计数值(统计非0个数)int cur = 0;// 2.遍历处理数组for (int i = 0; i < nums.size(); i++) {// 3.如果索引指向的元素不为0(为0就直接跳过)if (nums[i] != 0) {// 3.1 就依次放在数组左边nums[cur] = nums[i];// 3.2 计数值自增cur++;}}// 4.将数组右边空出的空间填充为0for (int i = cur; i < nums.size(); i++) {nums[i] = 0;}}
};

11. 盛最多水的容器 - 力扣(LeetCode)

题目大意:

你有一排竖直的柱子(想象成水杯),每个柱子的高度不同。

比如:[1,8,6,2,5,4,8,3,7],你要找两个柱子,让它们和地面围成一个“容器”,能装最多的水。

注意:不能倾斜容器,水的高度取决于较矮的那个柱子。

输入:[1,8,6,2,5,4,8,3,7]

  • 选第1个和最后一个柱子(高度分别是1和7)→ 能装 8 * 1 = 8 的水
  • 选第2个和倒数第2个柱子(高度都是8)→ 能装 7 * 3 = 21 的水
  • 最后发现:选第2个和最后一个柱子 → 能装 7 * 7 = 49 的水(最终答案)

class Solution {
public:int maxArea(vector<int>& height) {// 1.定义最大水量int res = 0;// 2.左边指针 i 从最左边开始int i = 0;// 3.右边指针 j 从最右边开始int j = height.size() - 1;// 4.当左边指针还没碰到右边指针时,继续循环while (i < j) {// 4.1 计算装的水量int area = (j - i) * min(height[i], height[j]);// 4.2 更新最大水量res = max(res, area);// 4.3 如果左边柱子较矮,移动左边指针 iif (height[i] < height[j]) {i++;// 4.4 如果右边柱子较矮,移动左边指针 j} else {j--;}}return res;}
};

    这道题使用双指针法。

    • 一开始,左边指针 i 在最左边,右边指针 j 在最右边。
    • 计算当前这两个柱子能装多少水。
    • 然后移动较矮的那个柱子的指针,因为:
      • 移动较高的柱子,宽度变小了,但高度不会变高,肯定装不了更多水。
      • 移动较矮的柱子,有可能找到更高的柱子,从而装更多水。

    15. 三数之和 - 力扣(LeetCode)

    题目大意:

    你有一个整数数组,比如:[-1,0,1,2,-1,-4]

    你要找出所有三个数的组合,满足:

    1. 三个数加起来等于 0;
    2. 三个数不能是同一个位置的(但值可以重复);
    3. 答案中不能有重复的三元组(比如不能有两个 [-1,0,1]);
    4. 返回所有符合条件的三元组列表。
    class Solution {
    public:vector<vector<int>> threeSum(vector<int>& nums) {// 1.结果数组(满足条件的三元组)vector<vector<int>> result;// 2.排序数组sort(nums.begin(), nums.end());// 3.遍历数组,固定第一个数 nums[i]for (int i = 0; i < nums.size(); i++) {// 3.1 如果当前数已经大于0,后面的数更大,三数之和不可能为0if (nums[i] > 0) {return result;}// 3.2去重:如果当前数和前一个数一样if (i > 0 && nums[i] == nums[i - 1]) {// 跳过,避免重复三元组continue;}// 4.定义两个指针:// left:从 i+1 开始(当前数右边第一个)// right:从最后一个数开始int left = i + 1;int right = nums.size() - 1;// 5.双指针向中间移动,找另外两个数while(right > left) {// 5.1 和太大了,说明右边的数太大,往左移if (nums[i] + nums[right] + nums[left] > 0) right--;// 5.2 和太小了,说明左边的数太小,往右移else if (nums[i] + nums[right] + nums[left] < 0) left++;else {// 5.3 找到三数之和为0result.push_back(vector<int>{nums[i], nums[left], nums[right]});// 5.4 去重:跳过相同的 left 值,避免重复三元组while (right > left && nums[right] == nums[right - 1]) right--;// 5.5 去重:跳过相同的 right 值while (right > left && nums[left] == nums[left + 1]) left++;// 5.6 正常移动指针,继续找下一组right--;left++;}}}return result;}
    };

      不能使用暴力的三层循环,很慢而且去重麻烦。我们可以:固定一个数,然后用“双指针”找另外两个数,让它们三者之和为0。而且为了方便处理,我们先把数组排序。

      42. 接雨水 - 力扣(LeetCode)

      题目大意:

      你有一排柱子(想象成高楼),每个柱子的高度不同。

      比如:[0,1,0,2,1,0,1,3,2,1,2,1]

      下雨后,这些柱子之间会形成“水坑”,能接住雨水。你要计算这些柱子最多能接多少雨水。

      注意:雨水只能在两个高柱子之间的低洼处积存。

      输入:[0,1,0,2,1,0,1,3,2,1,2,1]

      • 第一个柱子高度为 0,不能积水。
      • 第二个柱子高度为 1,右边有更高的柱子(如高度为 2 的柱子),可以积水。
      • 最终能接的雨水量是 6 单位。
      class Solution {
      public:int trap(vector<int>& height) {// 1.定义变量 ans 来记录总积水int ans = 0;// 2.定义一个栈,用来存放柱子的索引stack<int> st;// 3.遍历每个柱子for (int i = 0; i < height.size(); i++) {// 4.如果栈不为空,且当前柱子高度大于栈顶柱子高度while (!st.empty() && height[st.top()] < height[i]) {// 4.1 取出栈顶柱子的索引int cur = st.top();st.pop();// 4.2 如果栈为空,说明没有左边的柱子,无法形成水坑if (st.empty()) break;// 4.3 确定左边柱子的索引int l = st.top();// 4.4 确定右边柱子的索引(即当前柱子)int r = i;// 4.5 计算水坑的高度int h = min(height[r], height[l]) - height[cur];// 4.6 计算水坑的宽度int w = r - l - 1;// 4.7 计算积水,并累加到 ans 中ans += h * w;}// 5.将当前柱子的索引压入栈中st.push(i);}// 6.返回总积水return ans;}
      };

        这道题使用单调栈来解决:

        • 维护一个栈,存放柱子的索引。
        • 当遇到比栈顶高的柱子时,说明形成了一个“水坑”,可以计算积水。
        • 积水量取决于左右两边较高的柱子和当前柱子的高度差。

        滑动窗口

        3. 无重复字符的最长子串 - 力扣(LeetCode)

        题目大意:

        给你一个字符串,比如 "abcabcbb",你要找里面最长的一段连续字符,而且这一段里不能有重复的字母

        比如:

        • "abcabcbb" 中,"abc" 是一个没有重复字母的连续子串,长度是 3。
        • "bbbbb" 全是 b,那最长不重复的只能是 "b",长度是 1。
        • "pwwkew",中间 "wke" 是不重复的,长度是 3(注意 "pwke" 虽然不重复,但它不是连续拿的,中间跳了,所以不算子串)。

        我们要找的就是这个最长的、不重复的、连续的一段长度

        class Solution {
        public:int lengthOfLongestSubstring(string s) {// 1.记每个字符最后一次出现的位置unordered_map<char, int> mp;// 2.定义窗口左边界,最开始在0int left = 0;// 3.定义最大长度,最开始是0int max_len = 0;// 4.从0开始往右滑for (int i = 0; i < s.size(); i++) {// 4.1 看看当前这个字符 s[i] 之前有没有出现过,并且是不是在当前窗口里if (mp.find(s[i]) != mp.end() && mp[s[i]] >= left) {// 4.2 如果这个字符出现过,而且位置在窗口内(>= left),说明重复了// 那就把左边界移到“上次出现的位置”的下一个,避开重复left = mp[s[i]] + 1;}// 4.3 不管怎样,更新这个字符的最新位置为 imp[s[i]] = i;// 4.4 当前窗口长度是 i-left+1,更新最大值max_len = max(max_len, i - left + 1);}return max_len;}
        };

        这道题使用滑动窗口解决.

        想象你有一扇窗户,可以盖住字符串中的一部分。这扇窗户只能左右滑动,不能跳。

        我们让这扇窗户从左往右滑,保证窗户里的字符都不重复,同时记录窗户最长的时候有多宽

        这扇窗户有两个边界:

        • 左边框left):窗户的起点
        • 右边框i):窗户的终点,随着我们遍历字符串向右移动

        我们用一扇会滑动的窗户,从左到右扫字符串,用一个小本本记每个字母最后出现的位置。如果发现新来的字母已经在窗户里了,就把窗户左边推到“避开这个重复”的位置,然后一直记录窗户最长有多宽。

        438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

        题目大意:

        给你两个字符串:一个长字符串s,一个短字符串p;我们要在 s 中找出所有和 p 是“字母异位词”的子串,并返回这些子串的起始位置。异位词就是两个字符串包含的字母种类和数量完全一样,只是顺序不同。比如:"abc" 和 "cba" 是异位词,"ab" 和 "ba" 是异位词。

        class Solution {
        public:vector<int> findAnagrams(string s, string p) {// 1.使用vector存储异位词子串的起始索引vector<int> result;// 2.长子串与短子串的长度int n = s.size(), m = p.size();// 3.s比p还短,无效if (n < m) return result;// need[0] 表示 'a' 出现几次// need[1] 表示 'b' 出现几次// ……以此类推共26个小写字母// 4.统计 p 中每个字母出现的次数vector<int> need(26, 0);// 5.统计当前窗口中每个字母出现的次数vector<int> window(26, 0); // 6.第一步:统计 p 中每个字母出现多少次for (char c : p) {// 6.1 字符转数字need[c - 'a']++;}// 7.第二步:初始化窗口,先把s的前m个字符放进窗口for (int i = 0; i < m; i++) {window[s[i] - 'a']++;}// 8.检查第一个窗口是不是异位词if (window == need) {// 8.1 起始位置是 0result.push_back(0);}// 9.第三步:开始滑动窗口// i 是当前要加入窗口的字符下标// // 窗口范围是 [i-m+1, i],长度为 mfor (int i = m; i < n; i++) {// 9.1 窗口向右滑动:// 9.1.1 把最左边的字符踢出去(它要移出窗口了)window[s[i] - 'a']++;// 9.1.2 把当前字符加进来(右边新进来的)window[s[i - m] - 'a']--;// 9.2 现在窗口是 [i-m+1 到 i],检查是否和 p 是异位词if (window == need) {// 9.2.1 当前窗口的起始位置result.push_back(i - m + 1);}}return result;}
        };

        此题的大致思路为:

        1. 统计 p 中每个字母出现的次数(记作 need
        2. 在 s 上维护一个长度等于 p 的“窗口”,也统计这个窗口里每个字母的次数(记作 window
        3. 每次滑动窗口时,去掉左边的字符,加上右边的新字符
        4. 如果某次 window 和 need 完全一样 → 说明当前窗口是一个异位词,记录起始位置
        http://www.dtcms.com/a/355203.html

        相关文章:

      • Mac Pro M4芯片 安装 VMware Fusion 和 windows
      • Vue Router 路由守卫详解与面试指南
      • 实体门店怎么利用小程序做好分销
      • 目标检测领域基本概念
      • 【Python】QT(PySide2、PyQt5):Qt Designer,VS Code使用designer,可能的报错
      • 发那科机器人弧焊电源气体省气装置
      • esp32c2 at 请问通过HTTPS进行OTA升级的AT命令流程有吗?
      • 专项智能练习(多媒体概述)
      • 如果已经安装了electron的一个版本,再次使用命令npm install electron不指定electron版本时,会下载安装新版本么?
      • VS2022+QT6.7+Multimedia(捕获Windows音频数据,生成实时频谱)
      • Day16_【机器学习建模流程】
      • Python备份实战专栏第2/6篇:30分钟搭建企业级API认证系统,安全性吊打90%的方案
      • R语言贝叶斯方法在生态环境领域中的高阶技术应用
      • Mac 开发环境与配置操作速查表
      • 基于Vue2+elementUi实现树形 横向 合并 table不规则表格
      • 华为S5720S重置密码
      • 前沿技术观察:从AI 时代到量子计算的下一站
      • 智能物联网(AIoT)核心技术落地路径与企业数字化转型适配方案
      • 如何通俗的理解操作系统的IO多路复用
      • H5 本地跨域设置
      • “帕萨特B5钳盘式制动器结构设计三维PROE模型7张CAD图纸PDF图“
      • UE5.5模型导入FBX强制x轴向前Force Front XAxis
      • 上线问题——Mac系统下如何获取鸿蒙APP证书公钥和MD5指纹
      • 密码管理中
      • 多线程 【详解】| Java 学习日志 | 第 14 天
      • Ansys Icepak AEDT 中的后处理脚本
      • 护网面经总结(三)
      • 三维细节呈现核心技术:法线、凹凸与置换贴图全解析与应用指南
      • 物业满意度调查数据分析——从 “数据杂乱” 到 “精准改进” 的落地经验(满意度调查问卷)
      • Linux系统资源分配算法在VPS云服务器调优-性能优化全指南