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

LeetCode进阶算法题解详解

LeetCode进阶算法题解详解

本文深入讲解回溯算法、栈、哈希表和树的经典题目,涵盖全排列、有效括号、计算器、单调栈、最近公共祖先等高频面试题,适合有一定算法基础的同学进阶学习。


目录

  1. 回溯算法
  2. 栈的应用
  3. 哈希表技巧
  4. 树的算法

1. 回溯算法

1.1 全排列 II(Permutations II)

题目描述:给定一个可能包含重复数字的序列,返回所有不重复的全排列。

核心思想
回溯算法 = 深度优先搜索 + 剪枝。通过标记已使用元素和去重逻辑,生成所有不重复的排列。

解题思路

  1. 排序:先对数组排序,方便去重

  2. used数组:标记哪些元素已经被使用

  3. 去重关键

    if (i > 0 && nums[i] == nums[i-1] && !used[i-1])continue;
    
    • 当前元素与前一个相同
    • 前一个元素未被使用(说明在同一层已经处理过)
    • 跳过这个元素,避免重复排列
  4. 回溯三步骤

    • 做选择:used[i] = true; path.push_back(nums[i]);
    • 递归:backtracking(...)
    • 撤销选择:path.pop_back(); used[i] = false;

代码实现

class Solution {
private:vector<vector<int>> result; vector<int> path; void backtracking(vector<int>& nums, vector<bool>& used) {// 终止条件:路径长度等于数组长度if (path.size() == nums.size()) {result.push_back(path);return;}// 遍历所有可能的选择for (int i = 0; i < nums.size(); i++) {// 剪枝1:元素已被使用if (used[i]) continue;// 剪枝2:去重逻辑(核心)// 同一层中,相同元素只使用一次if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])continue;// 做选择used[i] = true;path.push_back(nums[i]);// 递归backtracking(nums, used);// 撤销选择(回溯)path.pop_back();used[i] = false;}}public:vector<vector<int>> permuteUnique(vector<int>& nums) {result.clear();path.clear();vector<bool> used(nums.size(), false);// 排序是去重的前提sort(nums.begin(), nums.end());backtracking(nums, used);return result;}
};

去重原理图解

输入: [1, 1, 2]
排序后: [1, 1, 2]第一层:选1(索引0) -> used[0]=true第二层:选1(索引1) -> used[1]=true第三层:选2 -> [1,1,2] ✓选2(索引2) -> [1,2,1] ✓选1(索引1) -> 剪枝!因为 nums[1]==nums[0] 且 !used[0]说明同一层已经用过值为1的元素选2(索引2) -> used[2]=true第二层:选1(索引0) -> [2,1,1] ✓

复杂度分析

  • 时间复杂度:O(n × n!),共n!个排列,每个排列需要O(n)时间构造
  • 空间复杂度:O(n),递归栈深度为n

关键点总结

  1. 排序是去重的基础
  2. used[i-1] == false 表示同层去重
  3. 回溯模板:选择 → 递归 → 撤销

2. 栈的应用

2.1 有效的括号(Valid Parentheses)

题目描述:判断字符串中的括号是否有效配对。

解题思路

  • 使用栈存储左括号
  • 遇到右括号时,检查栈顶是否匹配
  • 最后栈必须为空

代码实现

class Solution {
public:bool isValid(string s) {stack<char> st;for (char ch : s) {// 左括号入栈if (ch == '(' || ch == '[' || ch == '{') {st.push(ch);} // 右括号匹配else if (ch == ')' || ch == ']' || ch == '}') {// 栈空说明没有对应的左括号if (st.empty()) {return false;}char c = st.top();// 检查是否匹配if ((ch == ')' && c != '(') || (ch == ']' && c != '[') ||(ch == '}' && c != '{')) {return false;}st.pop();} // 非括号字符,返回falseelse {return false;}}// 栈必须为空return st.empty();}
};

优化版本(更简洁)

bool isValid(string s) {stack<char> st;unordered_map<char, char> pairs = {{')', '('}, {']', '['}, {'}', '{'}};for (char ch : s) {if (pairs.count(ch)) {// 右括号if (st.empty() || st.top() != pairs[ch]) {return false;}st.pop();} else {// 左括号st.push(ch);}}return st.empty();
}

时间复杂度:O(n)
空间复杂度:O(n)


2.2 基本计算器 II(Basic Calculator II)

题目描述:实现一个基本的计算器,支持加减乘除。

解题思路

  1. 使用栈保存中间结果
  2. 维护一个preSign记录前一个运算符
  3. 遇到新的运算符或到达末尾时,根据preSign处理当前数字:
    • +:直接入栈
    • -:负数入栈
    • *:与栈顶相乘
    • /:与栈顶相除
  4. 最后求栈中所有数字之和

代码实现

class Solution {
public:int calculate(string s) {vector<int> stk;char preSign = '+';  // 初始化为'+'int num = 0;int n = s.length();for (int i = 0; i < n; ++i) {// 构建多位数字if (isdigit(s[i])) {num = num * 10 + (s[i] - '0');}// 遇到运算符或到达末尾,处理前面的数字if ((!isdigit(s[i]) && s[i] != ' ') || i == n - 1) {switch (preSign) {case '+':stk.push_back(num);break;case '-':stk.push_back(-num);break;case '*':stk.back() *= num;break;case '/':stk.back() /= num;break;}preSign = s[i];  // 更新运算符num = 0;         // 重置数字}}// 累加栈中所有元素return accumulate(stk.begin(), stk.end(), 0);}
};

执行过程示例

输入: "3+2*2"i=0: '3' -> num=3
i=1: '+' -> preSign='+', stk=[3], preSign='+', num=0
i=2: '2' -> num=2
i=3: '*' -> preSign='+', stk=[3,2], preSign='*', num=0
i=4: '2' -> num=2, 末尾处理 -> preSign='*', stk=[3,4]结果: 3+4=7

时间复杂度:O(n)
空间复杂度:O(n)

关键点

  • 乘除法立即计算(修改栈顶)
  • 加减法延迟计算(入栈)
  • 处理多位数字和空格

2.3 每日温度(Daily Temperatures)

题目描述:给定温度列表,返回每天需要等待多少天才会有更高温度。

解题思路

  • 使用单调栈(维护递减序列)
  • 栈中存储索引
  • 当前温度大于栈顶温度时,说明找到了更高温度
  • 计算天数差并出栈

代码实现

class Solution {
public:vector<int> dailyTemperatures(vector<int>& temperatures) {stack<int> st;  // 单调栈,存储索引vector<int> result(temperatures.size(), 0);for (int i = 0; i < temperatures.size(); i++) {// 当前温度大于栈顶索引对应的温度while (!st.empty() && temperatures[i] > temperatures[st.top()]) {int idx = st.top();result[idx] = i - idx;  // 计算天数差st.pop();}st.push(i);  // 当前索引入栈}return result;}
};

执行过程示例

输入: [73, 74, 75, 71, 69, 72, 76, 73]i=0: 73 -> st=[0]
i=1: 74>73 -> result[0]=1, st=[1]
i=2: 75>74 -> result[1]=1, st=[2]
i=3: 71<75 -> st=[2,3]
i=4: 69<71 -> st=[2,3,4]
i=5: 72>69>71 -> result[4]=1, result[3]=2, st=[2,5]
i=6: 76>72>75 -> result[5]=1, result[2]=4, st=[6]
i=7: 73<76 -> st=[6,7]结果: [1, 1, 4, 2, 1, 1, 0, 0]

时间复杂度:O(n),每个元素最多入栈出栈一次
空间复杂度:O(n)

单调栈应用场景

  • 下一个更大/更小元素
  • 柱状图最大矩形
  • 接雨水问题

3. 哈希表技巧

3.1 砖墙(Brick Wall)

题目描述:在砖墙中画一条垂直线,使穿过的砖块数量最少。

解题思路

  1. 最少穿过的砖块 = 总行数 - 最多经过的缝隙
  2. 使用哈希表统计每个位置的缝隙数
  3. 找出缝隙最多的位置

关键点

  • 不统计最右边的缝隙(边界)
  • 使用前缀和定位缝隙位置

代码实现

class Solution {
public:int leastBricks(vector<vector<int>>& wall) {unordered_map<int, int> cnt;  // 位置 -> 缝隙数// 遍历每一行for (auto& widths : wall) {int n = widths.size();int sum = 0;  // 当前位置(前缀和)// 统计每个缝隙位置(不包括最后)for (int i = 0; i < n - 1; i++) {sum += widths[i];cnt[sum]++;}}// 找出缝隙最多的位置int maxCnt = 0;for (auto& [pos, c] : cnt) {maxCnt = max(maxCnt, c);}// 总行数 - 最多缝隙数 = 最少穿过砖块数return wall.size() - maxCnt;}
};

图解示例

输入:
[[1,2,2,1],[3,1,2],[1,3,2],[2,4],[3,1,2],[1,3,1,1]]缝隙位置统计:
位置1: 2行有缝隙 (行0, 行2)
位置3: 3行有缝隙 (行1, 行4, 行5)
位置4: 4行有缝隙 (行0, 行2, 行3, 行5) <- 最多
位置5: 2行有缝隙 (行1, 行4)结果: 6 - 4 = 2

时间复杂度:O(n × m),n为行数,m为平均砖块数
空间复杂度:O(k),k为不同缝隙位置数

优化思路

  • 使用哈希表统计频率
  • 前缀和定位缝隙
  • 转化为求最大值问题

4. 树的算法

4.1 二叉树的最近公共祖先(Lowest Common Ancestor)

题目描述:找到二叉树中两个节点的最近公共祖先。

核心思想
后序遍历(左右根),从下往上返回信息。

解题思路

  1. 终止条件

    • 遇到空节点:返回null
    • 遇到p或q:返回当前节点
  2. 递归过程

    • 在左子树中查找
    • 在右子树中查找
  3. 返回逻辑

    • 左右子树都找到:当前节点是LCA
    • 只有一边找到:返回那一边的结果
    • 都没找到:返回null

代码实现

class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {// 终止条件:空节点或找到目标节点if (!root || root == p || root == q) {return root;}// 在左右子树中递归查找TreeNode* left = lowestCommonAncestor(root->left, p, q);TreeNode* right = lowestCommonAncestor(root->right, p, q);// 情况1: 左右子树都找到了,说明p和q分别在两侧if (left && right) {return root;}// 情况2: 只有一边找到,返回非空的那一边// 可能是LCA在某一侧,也可能是p/q之一是另一个的祖先return left ? left : right;}
};

图解示例

        3/ \5   1/ \ / \6  2 0  8/ \7   4查找 p=5, q=1 的LCA:后序遍历过程:
1. 访问左子树(5): 找到p,返回5
2. 访问右子树(1): 找到q,返回1
3. 根节点(3): left=5, right=1, 都非空 -> 返回3查找 p=5, q=4 的LCA:后序遍历过程:
1. 访问节点5的左子树(6): 返回null
2. 访问节点5的右子树(2):- 左子树(7): 返回null- 右子树(4): 找到q,返回4- 节点2: left=null, right=4 -> 返回4
3. 节点5: left=null, right=4 -> 返回4
4. 根节点3: left=4, right=null -> 返回4等等,上面有误,让我重新分析...正确过程:
1. lowestCommonAncestor(5, 5, 4):- root==p,直接返回5
2. lowestCommonAncestor(3, 5, 4):- left = lowestCommonAncestor(5, 5, 4) = 5- right = lowestCommonAncestor(1, 5, 4) = null- 返回 left = 5

算法正确性证明

情况1:p和q在root的两侧

  • 左子树返回p(或p的祖先)
  • 右子树返回q(或q的祖先)
  • root是LCA ✓

情况2:p和q都在root的左侧

  • 左子树返回LCA
  • 右子树返回null
  • 返回左子树的结果 ✓

情况3:p是q的祖先(或反之)

  • 先遇到的节点直接返回
  • 这个节点就是LCA ✓

时间复杂度:O(n),最坏情况遍历所有节点
空间复杂度:O(h),h为树高,递归栈空间

关键点总结

  1. 后序遍历,从下往上返回信息
  2. 遇到目标节点立即返回
  3. 根据左右子树返回值判断LCA位置

总结

本文涵盖了四大类进阶算法:

回溯算法

  • 核心:选择 → 递归 → 撤销
  • 去重:排序 + used数组
  • 应用:全排列、组合、子集

栈的应用

  • 括号匹配:栈的经典应用
  • 计算器:运算符优先级处理
  • 单调栈:寻找下一个更大/更小元素

哈希表技巧

  • 频率统计:快速查找和计数
  • 前缀和:累加定位
  • 空间换时间:O(1)查找

树的算法

  • 后序遍历:从下往上返回信息
  • 递归三要素:终止条件、递归逻辑、返回值
  • LCA:利用递归返回值判断

学习建议

  1. 理解本质:不要死记硬背,理解算法原理
  2. 画图分析:复杂问题用图示推演过程
  3. 变式练习:掌握一题多解,举一反三
  4. 复杂度分析:养成分析时空复杂度的习惯
  5. 代码规范:注意边界条件和代码可读性

相关题目推荐

回溯

  • 全排列 I/II
  • 组合总和 I/II/III
  • 子集 I/II
  • N皇后

  • 最小栈
  • 柱状图最大矩形
  • 接雨水
  • 逆波兰表达式

哈希表

  • 两数之和
  • 字母异位词分组
  • 最长连续序列

  • 二叉树的序列化与反序列化
  • 路径总和 I/II/III
  • 验证二叉搜索树

持续练习,定期复习,祝大家刷题顺利!🚀

http://www.dtcms.com/a/474445.html

相关文章:

  • 构造器是什么
  • docker desktop安装(windows os)
  • 免费的网站域名查询app国外的营销网站有哪些
  • 大模型基础入门与 RAG 实战:从理论到 llama-index 项目搭建(有具体代码示例)
  • 保定网站建设报价网页设计图片变圆角
  • 网站首页没收录大连网站流量优化定制
  • 基于CAN的UDS诊断服务
  • C++ : AVL 树之 右左双旋(第四章)
  • 南阳网站制作哪家好西安专业网站开发哪家好
  • 在 Windows PowerShell(pwsh)中配置 Oh My Posh + Conda 环境美化与性能优化
  • 小榄做网站新专业建设的重点任务
  • 把AI“浓缩”到1KB:超紧凑型决策树在MCU上的极限优化实战
  • Spring Boot 原理篇
  • 站酷网免费素材图库官网竣工验收全国公示平台
  • eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
  • 【Chrome插件】‘顾得助手’ 新功能介绍
  • 【控制系统建模与分析#1】电系统建模
  • 【Linux系统】9. 基础开发工具(三)
  • 付费网站做推广哪个好wordpress 顶部导航
  • 什么是AIGC?AIAIGCAGI什么区别?
  • NLP入门
  • 最低成本做企业网站 白之家杭州动漫设计公司最新招聘
  • 外汇跟单网站建设西安软件培训
  • 逻辑填空1【词的辨析】
  • 江油网站建设传媒公司业务范围介绍
  • 企业做网络推广有什么好处网站seo如何做
  • 成都网站开发建wordpress论坛用户
  • uzi粉丝做的网站wordpress 制作首页模板
  • 顺企网是什么网站flashfxp怎么上传网站
  • 【ChatGPT5】:“关于在当前 conda 环境里装 CUDA 12.8”