Sawana Huang Avatar

Sawana Huang

Leetcode

leetcode

这个界面会记录我在写 LeetCode 的时候总结到的关键知识点以及几个典型题目,或者有收获的题目的知识。

代码大多都是用 Python3 来写的。

我的 LeetCode.cn 主页

算法掌握度

代码随想录打卡

代码随想录 ↗

学而不思则罔,思而不学则殆。 -- 从学开始

数组 📊

  • 1. 数组理论基础 - (数组是物理上连续的数据结构,通过下标获取值)
  • 2. 二分查找 - (左毕右闭区间 [left, right],根据中点循环更新区间,直到中点命中目标)
  • 3. 移除元素 - (双指针法,逐个原地更新原数组,快指针读取原数组,慢指针更新得到新数组)
  • 4. 有序数组的平方 - (双指针比大小,变成微笑曲线,头尾比较得到较大的,从复制数组尾部开始放)
  • 5. 长度最小的子数组 - (双指针法实现滑动窗口,连续数组下可完全遍历,默认快指针移动,满足条件则慢指针移动收缩)
  • 6. 螺旋矩阵II - (模拟填写顺序,上右下左,不涉及算法,用四个指针约束当前处理的边界,并填入矩阵,左开右闭处理完该行/列再到下一步)
  • 7. 区间和 - (前缀和计算区间和,前缀和数组,则 区间和 等于 前缀和数组[a] 减去 前缀和数组[b],另 ACM 输入输出模式 python 用 sys.stdin.read)
  • 8. 开发商购买土地
  • 9. 总结篇 - (二分法,双指针法,滑动窗口,模拟,前缀和)

链表 🔗

  • 1. 链表理论基础 - (定义链表用 O(1) 两端增减,O(n) 查询,物理上随机分布。)
  • 2. 移除链表元素 - (删除链表节点仅需 current = dummy.next,current.next = current.next.next)
  • 3. 设计链表 - (五个链表的常用操作,跳出操作的边界条件很关键,get,addAtHead,addAtTail,addAtIndex`)
  • 4. 反转链表 - (双指针法,循环操作模,前一个节点 previous,当前节点 current,后一个节点 after,输入 pre 则有后两个)
  • 5. 两两交换链表中的节点 - (画图理解,按模块循环处理,单模块涉及三个 pre -> cur -> aft,更改指向,下一循环更新模块,边界 while 可以最后设置)
  • 6. 删除链表的倒数第N个节点 - (双指针,快慢指针,虚拟头节点,快针先走 n + 1,然后快慢针一起走)
  • 7. 链表相交 - (交错相接法,while 条件为 node1 != node2,相交或都为 None 时退出)
  • 8. 环形链表II - (双指针快慢指针两步法,一 求 fast(2) slow(1) 相遇点,二 根据先验求 node1(head 开始) 和 node2(相遇点 开始) 的交点,即为结果)
  • 9. 总结篇 - (虚拟头节点,链表基本操作,思维导图解释环形链表找入口的算法)

哈希表 🗂️

  • 1. 哈希表理论基础 - (高级数组,哈希映射位置,O(1)时间复杂度,碰撞用拉链法/线性探测法)
  • 2. 有效的字母异位词 - (26位数组记录对应值,一加一减,全为零则满足,字母用 ascii 值 ord() 计算下标)
  • 3. 两个数组的交集 - (使用 set 或者用数组记录,下标即为数字)
  • 4. 快乐数 - (用集合,记录中间的数字 n,数字位平方和用字符串逐个取,循环判断)
  • 5. 两数之和 - (哈希法记录经过的数字,找找和它匹配的那个数字是否在曾经经过的数字里,字典存储)
  • 6. 四数相加II - (分为两组暴力分别求 AB CD 之和,哈希法记录 AB 和的字典 O(1),遍历 CD之和找字典中满足条件的)
  • 7. 赎金信 - (O(3n) 类似有效字母异位词,数组记录 字母 出现次数,用字母编号作为 key,区别是只需要小于等于)
  • 8. 三数之和 - (双指针法,固定前一个数,后两个数先排序,再用双指针从两端起查找)
  • 9. 四数之和 - (类似 三数之和 区别在于固定前两个数固定第一个数,第二个数求剩下两个的和是否满足,双指针前后找标值)
  • 10. 总结篇 - (哈希表用于快速判断一个值是否在集合里,小数据时数组作为哈希表,大数据时 dict 作为哈希表)

字符串 📝

  • 1. 反转字符串 - (双指针,首尾开始两两交换)
  • 2. 反转字符串II - (模拟,通过 range 寻找 2k 起点,新建反转函数)
  • 3. 替换数字
  • 4. 翻转字符串里的单词 - (移除多余空格提取单词,整个字符串反转,单词反转,使用 split() 可以一步到位)
  • 5. 右旋转字符串
  • 6. 实现strStr()
  • 7. 重复的子字符串
  • 8. 总结篇

双指针法 👥

  • 1. 移除元素
  • 2. 反转字符串
  • 3. 替换数字
  • 4. 翻转字符串里的单词
  • 5. 翻转链表
  • 6. 删除链表的倒数第N个节点
  • 7. 链表相交
  • 8. 环形链表II
  • 9. 三数之和
  • 10. 四数之和
  • 11. 双指针总结

栈与队列 📚

  • 1. 栈与队列理论基础 - (python queue 的实现是基于 deque,deque 的 c 源码是实现了 双向循环链表 + 定长数组)

  • 2. 用栈实现队列 - (入栈和出栈两个栈,腾空入栈到出栈实现 FIFO,出栈还有值时别腾空入栈)

  • 3. 用队列实现栈 - (用两个队列实现一个栈,其中一个入栈出栈,另一个用来腾空第一个队列,取出最后一个抛出)

  • 4. 有效的括号 - (用栈存储,遇到匹配的括号则消掉,有三种情况出错,从右括号开始、最后还有剩余、对应的括号不匹配)

  • 5. 删除字符串中的所有相邻重复项 - (用数组实现,双指针法单数组处理,相邻情景下先同步再判断)

  • 6. 逆波兰表达式求值 - (运算的后缀表达式有利于计算机运算,用栈保存,如果遇到符号,则把前两个数取出根据符号运算,结果压回)

  • 7. 滑动窗口最大值 - (创建单调队列保存数值型的最大值队列,双向队列,push,pop,front 方法,push 等于时也可允许重复值)

  • 8. 前K个高频元素 - (用 小顶堆 来维护频数数组,弹出时自动弹出最小值,留下最多的,实际使用 优先队列)

  • 9. 栈与队列总结 - (栈与队列的基本实现,底层实现,栈在计算机系统中的应用,单调队列和优先队列)

二叉树 🌳

  • 1. 二叉树理论基础
  • 2. 二叉树的递归遍历
  • 3. 二叉树的迭代遍历
  • 4. 二叉树的统一迭代法
  • 5. 二叉树的层序遍历
  • 6. 翻转二叉树
  • 7. 二叉树周末总结
  • 8. 对称二叉树
  • 9. 二叉树的最大深度
  • 10. 二叉树的最小深度
  • 11. 完全二叉树的节点个数
  • 12. 平衡二叉树
  • 13. 二叉树的所有路径
  • 14. 二叉树周末总结
  • 15. 左叶子之和
  • 16. 找树左下角的值
  • 17. 路径总和
  • 18. 从中序与后序遍历序列构造二叉树
  • 19. 最大二叉树
  • 20. 二叉树周末总结
  • 21. 合并二叉树
  • 22. 二叉搜索树中的搜索
  • 23. 验证二叉搜索树
  • 24. 二叉搜索树的最小绝对差
  • 25. 二叉搜索树中的众数
  • 26. 二叉树的最近公共祖先
  • 27. 二叉树周末总结
  • 28. 二叉搜索树的最近公共祖先
  • 29. 二叉搜索树中的插入操作
  • 30. 删除二叉搜索树中的节点
  • 31. 修剪二叉搜索树
  • 32. 将有序数组转换为二叉搜索树
  • 33. 把二叉搜索树转换为累加树
  • 34. 二叉树总结篇

回溯算法 🔄

  • 1. 回溯算法理论基础 - (递归,暴力 for 循环,三步法回溯模板:终止条件记录叶子节点,for 循环子集合,处理节点、深入、回溯)
  • 2. 组合问题
  • 3. 组合(优化)
  • 4. 组合总和III
  • 5. 电话号码的字母组合
  • 6. 回溯周末总结
  • 7. 组合总和
  • 8. 组合总和II
  • 9. 分割回文串
  • 10. 复原IP地址
  • 11. 子集问题
  • 12. 回溯周末总结
  • 13. 子集II
  • 14. 递增子序列
  • 15. 全排列
  • 16. 全排列II
  • 17. 回溯周末总结
  • 18. 回溯算法去重问题的另一种写法
  • 19. 重新安排行程
  • 20. N皇后
  • 21. 解数独
  • 22. 回溯法总结篇

贪心算法 🎯

  • 1. 贪心算法理论基础 - (贪心的本质是选择每一阶段的局部最优,从而达到全局最优。没有套路,核心是模拟。举反例策略,没有反例就使用)
  • 2. 分发饼干 - (贪心,模拟,每个孩子是个局部,循环所有孩子找到全部,排序然后匹配)
  • 3. 摆动序列 - (贪心模拟,三个数字为一个模块,递增、平台顶、平台尾三种情况,del 删数组元素)
  • 4. 最大子序和 - (贪心法,模拟后发现当连续和为负,则抛弃该数组,以下一个数为起点,局部是和为正的最大数组,全局是从数组中选最大的)
  • 5. 贪心周总结 - (局部最优到全局最优)
  • 6. 买卖股票的最佳时机II - (贪心思路,每两天利润为局部,仅在利润为正的天数参与)
  • 7. 跳跃游戏 - (贪心思路,逐个检查,可跳跃到的范围,更新最大范围,最后检查是否能够到达)
  • 8. 跳跃游戏II - (贪心思路,局部最优是下一步起跳点能覆盖最多范围,最终步数最少)
  • 9. K次取反后最大化的数组和
  • 10. 贪心周总结
  • 11. 加油站
  • 12. 分发糖果
  • 13. 柠檬水找零
  • 14. 根据身高重建队列
  • 15. 贪心周总结
  • 16. 根据身高重建队列(vector原理讲解)
  • 17. 用最少数量的箭引爆气球
  • 18. 无重叠区间
  • 19. 划分字母区间
  • 20. 合并区间
  • 21. 单调递增的数字
  • 22. 监控二叉树
  • 23. 分发糖果问题再谈贪心
  • 24. 贪心算法总结篇

动态规划 📈

  • 1. 动态规划理论基础
  • 2. 斐波那契数
  • 3. 爬楼梯
  • 4. 使用最小花费爬楼梯
  • 5. 动规周总结
  • 6. 不同路径
  • 7. 不同路径II
  • 8. 整数拆分
  • 9. 不同的二叉搜索树
  • 10. 动规周总结
  • 11. 0-1背包理论基础(一)
  • 12. 0-1背包理论基础(二)
  • 13. 分割等和子集
  • 14. 最后一块石头的重量II
  • 15. 动规周总结
  • 16. 目标和
  • 17. 一和零
  • 18. 完全背包理论基础
  • 19. 零钱兑换II
  • 20. 组合总和IV
  • 21. 爬楼梯(进阶)
  • 22. 零钱兑换
  • 23. 完全平方数
  • 24. 单词拆分
  • 25. 多重背包理论基础
  • 26. 背包问题总结篇
  • 27. 打家劫舍
  • 28. 打家劫舍II
  • 29. 打家劫舍III
  • 30. 买卖股票的最佳时机
  • 31. 买卖股票的最佳时机II(动态规划)
  • 32. 买卖股票的最佳时机III
  • 33. 买卖股票的最佳时机IV
  • 34. 最佳买卖股票时机含冷冻期
  • 35. 买卖股票的最佳时机含手续费
  • 36. 股票问题总结篇
  • 37. 最长上升子序列
  • 38. 最长连续递增序列
  • 39. 最长重复子数组
  • 40. 最长公共子序列
  • 41. 不相交的线
  • 42. 最大子序列和
  • 43. 判断子序列
  • 44. 不同的子序列
  • 45. 两个字符串的删除操作
  • 46. 编辑距离
  • 47. 编辑距离总结篇
  • 48. 回文子串
  • 49. 最长回文子序列
  • 50. 动态规划总结篇

单调栈 📊

  • 1. 每日温度
  • 2. 下一个更大元素I
  • 3. 下一个更大元素II
  • 4. 接雨水
  • 5. 柱状图中最大的矩形

图论 🌐

  • 图论正式发布
  • 1. 图论理论基础
  • 2. 深度优先搜索理论基础
  • 3. 可达路径
  • 4. 广度优先搜索理论基础.md
  • 5. 岛屿问题(一)孤岛计数.深搜版
  • 6. 岛屿问题(二)孤岛计数.广搜版
  • 7. 岛屿问题(三)最大岛屿的面积
  • 8. 岛屿问题(四)孤岛的总面积
  • 9. 岛屿问题(五)沉没孤岛
  • 10. 岛屿问题(六)高山流水
  • 11. 岛屿问题(七)建造最大工岛
  • 12. 岛屿问题(八)海岸线计算
  • 13. 字符串迁移
  • 14. 有向图的完全连通
  • 15. 并查集理论基础
  • 16. 寻找存在的路线
  • 17. 多余的边
  • 18. 多余的边II
  • 19. 最小生成树之prim
  • 20. 最小生成树之Kruskal
  • 21. 拓扑排序
  • 22. dijkstra朴素版
  • 23. dijkstra堆优化版
  • 24. Bellman_ford算法
  • 25. Bellman_ford之队列优化
  • 26. Bellman_ford之判断负权回路
  • 27. Bellman_ford之单源有限最短路
  • 28. Floyd算法
  • 29. A*算法
  • 30. 最短路问题总结篇
  • 31. 图论总结篇

链表

知识

链表的结构可以认为是一个“节点”,一般会初始化为一个链表实例

  • 指代一个链表节点实例
  • 需要调用 .val 方法才能获取值
  • 调用 .next 方法可以获取下一个节点
# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

一个经典的链表可以用列表的形式表示,但它不是链表:[1,2,3,4,5][1,2,3,4,5,null]

而是一个链,并且最终的节点通常指向空:[1] -> [2] -> [3] -> [4] -> [5] -> null

链表和变量表示

链表变量代表的是一整个节点,而不是单个值。eg: head.val, head.next

这是理解链表和链表相关代码的重要内容,因为对于一个链表,要意识到

  • 通过 node.val 获取值
  • 通过 node.next 获取下一个值
  • 通过迭代获取整个链表的内容

这意味着虽然我们会表示 node: [1] -> [2] -> [3] -> [4] -> [5] -> null,但事实上,它是 node: [1] -> ?

除非"事先知道",否则我们只能通过遍历这个节点的方式才能知道这个链表实际上是怎样的

# node: ?
def show_listnode(node:ListNode) -> List:
    show_list = []

    cur = node
    while cur.next:
        show_list.append(cur.val)
        cur = cur.next

    return show_list
# node list: [1,2,3,4,5]
  • 链表的变量的实际是指针,且是单个节点的指针,指向内存中的某个物理数据。因此无论多少个变量,只要指向同一个节点(如 head),他们一致。如果其中一个修改,另一个也修改。
    • a=head, b=head, a.val=1 => b.val -> 1

暂存链表节点

暂存是一个非常好的思路。curtem = cur.next ,我们就迭代得到了两个链表

  • cur: [1, 2, 3, 4, 5, null]
  • tem: [2, 3, 4, 5, null]

这两个链表依托于同一个物理数据实体,但他们往下的链是不同的。

通过这样的方式,我们可以在不改变 cur 原有链表的前提下,处理一个链表复制。

如,通过暂存节点更新指针。cur = list1, cur = cur.next。

  • list1: [1, 2, 3, 4, 5, null]
  • cur: [2, 3, 4, 5, null]

虚拟头节点

新建链表应该创建虚拟头,虚拟头应该为 dummy = ListNode(0),之后返回时去掉头,返回 dummy.next。

  • dummy: [0,55,63,4,2,3,null]
  • dummy.next: [55,63,4,2,3,null]

这个技巧用于防止头节点为空的问题。如果我们不规定新的链表有头节点,那么新链表很可能为 head: null。显然,此时 head 的类型不是 ListNode 也就没有 val 和 next 方法。

通过创建 new 节点跟踪链表尾部来更新链表尾部

  • 通过创建 new 来跟踪新链表表的尾部,并且更新迭代链表。常见操作:

    • 指定新下一个节点 new.next = target
    • 更新 new new = new.next
  • 新链表变量(如 cur = headA)是原链表的 “指针引用”,只要不直接修改 headA 本身(如 headA = new_node),仅操作新变量(如 cur = cur.next)不会改变原链表结构,headA 始终指向原链表头部,不会失效。

经典问题

栈与队列

我在我的这篇博客中讨论了 python 中 queue 库的队列 queue.Queue() 和栈 queue.LifoQueue() 是基于 deque 实现的。而 deque 的 C 语言底层是通过构建一个 双向循环链表 + 定长数组 实现的。

回溯算法

回溯算法的经典流程、模板(from 代码随想录)

dummy-code.py
def backtracking(参数):

    if (终止条件):
        存放结果;
        return;

    for i in [子节点集合]:
        # 剪枝 (可选)
        if (剪枝条件): continue;

        处理节点;
        backtracking(参数); # 递归
        回溯节点 撤销处理结果

哈希表 / 哈希法

哈希法本质是一种 “以值作为键”的方法 -- { key: 值, value: 要保存的东西 }

哈希表则是通过哈希函数,让所有的的值都转为数字 key,插入表中