刷题系列总结

文章目录

  • 觉得很重要,很值得做的题目
  • python中常见操作的时间复杂度
  • 数据结构
    • 数组/hash表
    • 链表
    • 队列
    • 堆/优先级队列
      • 普通遍历
      • 深度优先遍历DFS
      • 广度优先遍历BFS
      • 拓扑排序/topological sort
      • 路径
    • 字符串
      • 前缀树/字典树/trie tree
      • 回文串/Palindrome
    • 并查集
  • 基础算法
    • 枚举
    • 贪心
    • 分治
    • 回溯+递归
    • 排序
    • 查找/二分法
    • 动态规划
      • 递推
      • LIS(最长递增序列)
      • LCS(最长公共子序列)
      • 树形dp
      • 区间dp
      • 需要结合其他数据结构优化类
      • 字符串类
      • 背包
      • 股票买卖系列
    • 前缀和
    • 位运算/Bit Manipulation
  • 其他
    • 数学
    • x数之和
    • 自己实现加减乘除
      • 加法
      • 乘法
      • 计算器
    • 滑动窗口
    • 排列生成
    • 设计类题目

一个刷题的记录总结。

觉得很重要,很值得做的题目

155, 88(space complexity needs to be o ( 1 ) o(1) o(1)), 380, 139, 275
168
136(think about how to generalize to further conditions, for example, how to find the only element that appears at n times, while other elements appear m times)

162, 1027

918

146,链表必做

贪心:435
二分:33

python中常见操作的时间复杂度

参考链接:https://wiki.python.org/moin/TimeComplexity

list相关 时间复杂度
转换成set o ( n ) o(n) o(n)
in o ( n ) o(n) o(n)
len o ( 1 ) o(1) o(1)
dict相关 时间复杂度
get o ( 1 ) o(1) o(1)
pop(删掉某个key) o ( 1 ) o(1) o(1)
len o ( 1 ) o(1) o(1)
in almost certainly o ( 1 ) o(1) o(1), unless some weird inputs to become the worst, o ( n ) o(n) o(n)
set相关 时间复杂度
in 平均 o ( 1 ) o(1) o(1),最坏 o ( n ) o(n) o(n)

max(a), min(a),时间复杂度都是o(n)

sort的时间复杂度是 o ( n l o g n ) o(nlogn) o(nlogn)

数据结构

数组/hash表

too easy, don’t even waste your time
缺失数字系列,思路是空间换时间:268. 缺失数字, 41. 缺失的第一个正数
88. 合并两个有序数组(空间 o ( 1 ) o(1) o(1)值得思考)

easy, useful when you haven’t coding for a long time
977. 有序数组的平方
724. 寻找数组的中心索引
49. 字母异位词分组
1013. 将数组分成和相等的三个部分

medium
290. Word Pattern
448. 找到所有数组中消失的数字(可以修改原数组的元素), 287. 寻找重复数(不可以修改原数组的元素)
11. 盛最多水的容器
17. 电话号码的字母组合
334. 递增的三元子序列
36. 有效的数独(熟悉python特殊用法)、37. Sudoku Solver(follow-up)

274. H-Index( o ( n ) o(n) o(n)做法可以思考下)

380. Insert Delete GetRandom O(1)(值得思考)

54. Spiral Matrix (The devil is in the details!!!)

42. Trapping Rain Water

Boyer-Moore Majority Vote
169, 229. Majority Element II

查找常用字符

区间类
56. 合并区间、57. 插入区间、区间列表的交集
划分字母区间、提莫攻击

最小区间

435. 无重叠区间、用最少数量的箭引爆气球

链表

206. 反转链表、92. 反转链表 II、25. K 个一组翻转链表、两两交换链表中的节点

旋转链表

复制带随机指针的链表
86. Partition List
重排链表

链表中点、删除链表的倒数第N个节点
141. 环形链表(判断链表中是否有环)、142. 环形链表 II(找链表中环的入口)

移除链表元素、移除链表中的重复节点

21. 合并两个有序链表、合并K个排序链表
Merge In Between Linked Lists

725. Split Linked List in Parts

2. 两数相加、两数相加II

146. LRU Cache

队列

3. 无重复字符的最长子串、K 个不同整数的子数组
员工的重要性

单调队列
队列里的元素要么单调递增,要么单调递减

239. 滑动窗口最大值
绝对差不超过限制的最长连续子数组
双端搜索
核心思想:寻找图中某个点到某个点是否存在联通的路径,那么可以从起点和中点一起出发,如果2头有重叠的点,则找到联通的路径了(不知道为啥代码写的不对。。。暂时不管这里了)

堆/优先级队列

树状的结构

建堆时间复杂度分析
结论:建堆的时间复杂度:在n个节点上建堆,时间复杂度是 o ( n ) o(n) o(n)
分析:
看最坏的时间复杂度,假设n个节点,高度为h,则第i层有 2 i − 1 2^{i - 1} 2i1个节点,计算建堆的时间复杂度,既可以按照一个一个节点插入堆,然后调整节点来考虑,也可以按照所有节点都随机摆好,然后一个一个调整来计算。这里使用第二种情况考虑,最坏情况下,每个节点都需要考虑调整位置:
h - 1层的节点有 2 h − 1 2^{h - 1} 2h1个,需要调整1次,第h - 2层的节点有 2 h − 2 2^{h - 2} 2h2个,需要调整2次,以此类推,总共需要调整的次数s为:
s = 1 ∗ 2 h − 1 + 2 ∗ 2 h − 2 + ⋯ + ( h − 1 ) ∗ 2 1 + h ∗ 2 0 \begin{aligned} s &= 1*2^{h - 1} + 2 * 2^{h - 2} + \dots + (h - 1) * 2^1 + h * 2^0 \\ \end{aligned} s=12h1+22h2++(h1)21+h20
求解s,先求 1 2 s \frac{1}{2}s 21s,有:
1 2 s = 1 ∗ 2 h − 2 + 2 ∗ 2 h − 3 + ⋯ + ( h − 1 ) ∗ 2 0 + 1 2 h \begin{aligned} \frac{1}{2}s &= 1*2^{h - 2} + 2 * 2^{h - 3} + \dots + (h - 1) * 2^0 + \frac{1}{2}h \\ \end{aligned} 21s=12h2+22h3++(h1)20+21h
两者相减,有:
1 2 s = 2 h − 1 + 2 h − 2 + ⋯ + 2 0 − 1 2 h ⇔      s = 2 h + 2 h − 1 + ⋯ + 2 − h = 2 ( 1 − 2 h ) 1 − 2 − h = 2 h + 1 − 2 − h \begin{aligned} \frac{1}{2}s &= 2^{h - 1} + 2^{h - 2} + \dots + 2^0 - \frac{1}{2}h \\ \Leftrightarrow \;\; s &= 2^h + 2^{h - 1} + \dots + 2 - h \\ &= \frac{2(1 - 2^h)}{1 - 2} - h \\ &= 2^{h + 1} - 2 - h \end{aligned} 21ss=2h1+2h2++2021h=2h+2h1++2h=122(12h)h=2h+12h
因为 h = l o g 2 n h = log_2n h=log2n,所以上式继续化简,最终得到:
s = 2 h + 1 − 2 − h = 2 h − 4 − h = 2 l o g 2 n − 4 − h = n − 4 − l o g 2 n s = 2^{h + 1} - 2 - h = 2^h - 4 - h = 2^{log_2n} - 4 - h = n - 4 - log_2n s=2h+12h=2h4h=2log2n4h=n4log2n
所以建堆的复杂度是 o ( n ) o(n) o(n)

堆查找/堆插入/堆删除的时间复杂度分析
结论:假设建成的堆高度为logn,即堆内有n个节点,则插入/查找/删除节点的时间复杂度是 o ( log ⁡ n ) o(\log n) o(logn)
分析:插入的复杂度主要在节点调整上面了,调整1个节点的最坏时间复杂度就是 o ( log ⁡ n ) o(\log n) o(logn)

从n个数字中找出最小的k个数字
最优的时间复杂度是 o ( n log ⁡ k ) o(n \log k) o(nlogk)
先对前面k个数建个大根堆,时间复杂度是 o ( k ) o(k) o(k),然后再对剩下的n - k进行比较:是否比堆顶元素小?如果是,则将当前的数字插入到堆里(插入消耗 o ( l o g k ) o(logk) o(logk)的时间),否则继续向后遍历。所以时间复杂度是 o ( n    l o g k ) o(n \; logk) o(nlogk)

215. 数组中的第K个最大元素

2462. Total Cost to Hire K Workers

253. Meeting Rooms II

数据流的中位数
最接近原点的 K 个点
数据流中的第K大元素
前 K 个高频元素

373. Find K Pairs with Smallest Sums

767. 重构字符串

最低加油次数

medium, worth thinking
1871. Jump Game VII

hard, worth thinking
1696. Jump Game VI
630. Course Schedule III

too easy, don’t waste your time
20. 有效的括号

155. Min Stack(感觉挺重要的题目

最长有效括号
150. 逆波兰表达式求值
删除字符串中的所有相邻重复项 II

394. 字符串解码
基本计算器(只有+, -, ())、基本计算器 II(+, -, *, /,无括号)(内含有括号的代码)

735. Asteroid Collision

单调栈
42、496、901、402、581、238、407、
316. Remove Duplicate Letters
456. 132 Pattern
每日温度
柱状图中最大的矩形

遍历
前序、中序、后序遍历的递归版写法:

# preorder
def preorder(root: TreeNode):
    if not root:
        return
    print(root.val)
    preorder(root.left)
    preorder(root.right)
# inorder
def inorder(root: TreeNode):
    if not root:
        return
    preorder(root.left)
    print(root.val)
    preorder(root.right)
# postorder
def postorder(root: TreeNode):
    if not root:
        return
    preorder(root.left)
    preorder(root.right)
    print(root.val)

树的遍历,用递归的方法非常简单,但是递归的空间复杂度较高。改成非递归的形式,标记当前结点的访问情况,非常好记,也很方便延伸到其他题目。这个方法来自leetcode的一篇题解:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/

非递归版写法:

def marked_preorder(root: TreeNode) -> list:
    res = []
    stack = [(root, 0)]
    while stack:
        node, status = stack.pop()
        if not node:
            continue
        if status == 0:
            stack.append((node.right, 0))
            stack.append((node.left, 0))
            stack.append((node, 1))
        else:
            res.append(node.val)
    return res


def marked_inorder(root: TreeNode) -> list:
    res = []
    stack = [(root, 0)]
    while stack:
        node, status = stack.pop()
        if not node:
            continue
        if status == 0:
            stack.append((node.right, 0))
            stack.append((node, 1))
            stack.append((node.left, 0))
        else:
            res.append(node.val)
    return res


def marked_postorder(root: TreeNode) -> list:
    res = []
    stack = [(root, 0)]
    while stack:
        node, status = stack.pop()
        if not node:
            continue
        if status == 0:
            stack.append((node, 1))
            stack.append((node.right, 0))
            stack.append((node.left, 0))
        else:
            res.append(node.val)
    return res

遍历相关题目

  1. 前序遍历
    二叉树的前序遍历、二叉树展开为链表
  2. 中序遍历
    二叉搜索树结点最小距离/二叉搜索树的最小绝对差(两题相同)、二叉树的中序遍历、恢复二叉搜索树
  3. 后序遍历
    二叉树的坡度、Evaluate Boolean Binary Tree
  4. 层次遍历
    普通层序遍历:
    102. 二叉树的层序遍历、员工的重要性、111. 二叉树的最小深度
    考虑层数/最右节点的层次遍历:
    103. 二叉树的锯齿形层次遍历
    104. 二叉树的最大深度
    填充每个节点的下一个右侧节点指针(层次遍历+判断是否为本层最右节点)、117. 填充每个节点的下一个右侧节点指针 II(同前)
    199. 二叉树的右视图
    1161. Maximum Level Sum of a Binary Tree
    515, 637,107,1162, 102的所有相似题目
  5. 深度优先遍历
    路径总和、路径总和 II、路径总和 III(路径+前缀和)
    求根到叶子节点数字之和
    二叉树的所有路径
  6. 广度优先遍历( ≈ \approx 层次遍历)
    101. Symmetric Tree

特殊的二叉树

  • 二叉搜索树/BST(左子树的节点小于等于根节点,右子树的节点大于等于根节点):
    • 验证二叉搜索树(中序遍历递增,就是BST)
    • 将有序数组转换为二叉搜索树、有序链表转换二叉搜索树
    • 二叉搜索树中的众数、二叉搜索树的最近公共祖先、二叉搜索树结点最小距离
    • 96. 不同的二叉搜索树、95. 不同的二叉搜索树 II
    • 删除二叉搜索树中的节点
  • 平衡二叉树
    • 判断是否为平衡二叉树
  • 完全二叉树(缺失的结点是最底层从右向左的)
    • 判断是否为完全二叉树
  • 满二叉树/完美二叉树(每层的节点都是满的)
    • 判断是否为满二叉树

其他

  • 二叉树的最近公共祖先、二叉搜索树的最近公共祖先(利用BST的特性可以简化代码)
  • 另一个树的子树(递归)、最大二叉树(递归)

DFS和BFS的时间复杂度均为 o ( V + E ) o(V+E) o(V+E),空间复杂度均为 o ( V ) o(V) o(V)。Note that V V V and E E E are not always equal to n

普通遍历

easy, useful only for rehabilitation
1462. Course Schedule IV
1615. Maximal Network Rank

深度优先遍历DFS

核心思想:栈,入栈前检查是否符合入栈条件,符合则先标记、再入栈。保证栈内元素都符合入栈条件,且已被标记避免重复访问。

leetcode的题目基本思路都差不多,同行题目难度从左向右递增
1306. 跳跃游戏 III
130. 被围绕的区域
200. 岛屿数量、695. 岛屿的最大面积、463. 岛屿的周长、827. Making A Large Island
不同路径 III
判断二分图
矩阵中的最长递增路径
克隆图
图像渲染
79. 单词搜索(其实是用栈模拟了回溯过程)
冗余连接

1034

广度优先遍历BFS

核心思想:队列。由于后放入队列的元素,其step必然更大,所以先出来的元素一定是最短的。

1129. Shortest Path with Alternating Colors、1345. 跳跃游戏 IV、752. 打开转盘锁
Minimum Moves to Reach Target with Rotations
542. 01 Matrix
994,310
864. Shortest Path to Get All Keys

拓扑排序/topological sort

核心思想:每次从入度为0的点开始访问,每访问一个点,就把点的入度-1,如果点的入度被减为0,则把点加入到可访问队列中,直到图中再没有可以访问的点为止

207. 课程表、210. 课程表 II
1340. 跳跃游戏 V
冗余连接 II(超时未ac)

路径

  • 图的最长路径
  • 图的最短路径(For unweighted graph, use BFS. For positive weighted graph, use Dijkstra)
    743. Network Delay Time, 1514. Path with Maximum Probability
    407,499,505 743 778 787 1102 1631 2290
  • 欧拉通路/欧拉回路
    Hierholzer 算法:重新安排行程,753

字符串

168. Excel Sheet Column Title

14. Longest Common Prefix

判断子序列
字典序的第K小数字
Z 字形变换
外观数列
比较含退格的字符串
重构字符串
执行操作后字典序最小的字符串

字符串+数字
8. 字符串转换为整数(atoi)(可用有限自动机解)、7. 整数反转、有效数字

匹配
模式匹配、正则表达式匹配

dp相关
可见dp中的字符串类

子串相关的问题
139. 单词拆分(很值得做)、单词拆分II、连接词(WA)

其他类
第N个数字、学生出勤记录 I

前缀树/字典树/trie tree

前缀树在大量字符串查表过滤的时候很有用
实现 Trie (前缀树)
Remove Sub-Folders from the Filesystem

回文串/Palindrome

回文串就是中心对称的字符串

回文数、验证回文串、验证回文字符串 Ⅱ(可删除1个字符)、回文链表

5. 最长回文子串、最长回文串(利用给出的字符串构造最长回文串)、最长回文子序列、由子序列构造的最长回文串的长度、最短回文串

分割回文串、分割回文串II

回文子串(统计回文子串的个数)

回文对

并查集

冗余连接
带阈值的图连通性

基础算法

枚举

模式匹配

贪心

贪心的题目,一般需要先对数据进行排序,然后从最小的开始。
有时候可能需要证明为什么局部最优解是全局最优解。最方便的方法是反证法:假设不用当前这个顺序,那么新的顺序得到的值,比按照贪心的顺序得到的值更小,更无法满足要求。

分发饼干

135. 分发糖果(贪心+分治)

2366. Minimum Replacements to Sort the Array

55. 跳跃游戏、45. 跳跃游戏 II、1326. Minimum Number of Taps to Open to Water a Garden(Variant of jump game II: self-built jump list, and there might be no valid solution)

1647. Minimum Deletions to Make Character Frequencies Unique

1024

用最少数量的箭引爆气球

任务调度器

区间系列
Interval scheduling: earliest finish first
435. 无重叠区间、646. Maximum Length of Pair Chain

  • 进阶版weighted interval scheduling, 见区间dp

Interval partitioning: earliest start first
2406. Divide Intervals Into Minimum Number of Groups

子序列系列
334. 递增的三元子序列(结合LIS)
300. 最长上升子序列(贪心+二分,必做)、354. Russian Doll Envelopes
630. Course Schedule III

分治

分发糖果(贪心+分治)

回溯+递归

core function is f(already_done, remaining_variables). The problems could be sorted into these categories:

Category Time Complexity Space Complexity
subset o ( n ∗ 2 n ) o(n*2^n) o(n2n)
For each element, we decide whether to choose (1) or not (0), so there will be 2 n 2^n 2n states, every state takes o ( n ) o(n) o(n).
o ( n ) o(n) o(n)
combination o ( C n k ∗ k ) o(C_n^k*k) o(Cnkk)
There will be C n k C_n^k Cnk combinations, and every combination takes k k k time. This is a special case in subset, so the worst case won’t exceed n ∗ 2 n n*2^n n2n
o ( n ) o(n) o(n)
permutation o ( n ! ∗ n ) o(n! * n) o(n!n)
There will be n ! n! n! permutations, and each permutation takes o ( n ) o(n) o(n).
o ( n ) o(n) o(n)
N-queen ( n ! ) (n!) (n!) o ( n ) o(n) o(n)
sudoku o ( 9 n ) o(9^n) o(9n) o ( n 2 ) o(n^2) o(n2)

17. 电话号码的字母组合
105. Construct Binary Tree from Preorder and Inorder Traversal、106. Construct Binary Tree from Inorder and Postorder Traversal
22. 括号生成
93. 复原IP地址
39. 组合总和、40. 组合总和 II、216. 组合总和 III、377. 组合总和 Ⅳ(follow-up)
77. 组合
46. Permutations、47. Permutations II
78. Subsets
51. N皇后、52. N皇后 II
37. Sudoku Solver

79. 单词搜索

1601. Maximum Number of Achievable Transfer Requests

1751. Maximum Number of Events That Can Be Attended II

735. Asteroid Collision
2369. Check if There is a Valid Partition For The Array

403. Frog Jump

486. 预测赢家

划分为k个相等的子集、火柴拼正方形(思路和前面完全一样,但是更简单)

90, 47, 31, 60

不同的二叉搜索树 II
最大二叉树(递归)
戳气球(超时)、移除盒子(超时)
外观数列
24 点游戏
60. 第k个排列

排序

python自带的sort用了timesort,平均时间复杂度是 o ( n l o g n ) o(nlogn) o(nlogn)
练习各类排序的题:排序数组

摆动排序 II

常见的排序比较

排序方法 主要思想 时间复杂度 稳定性
直接插入排序/insertion sort 无序的数字向有序列表中插 o ( n 2 ) o(n^2) o(n2) 稳定
折半插入排序 无序的数字二分法向有序列表中插 o ( n 2 ) o(n^2) o(n2) 稳定
希尔排序/shell sort 把数组分成长度为g的子数组,每次对每个子数组相同位置的数字进行排序(如把每个子数组的第1个数字抽出来排序),循环直到g=1
g需要选择终值为1的递减序列
g n + 1 = 3 g n + 1 g_{n+1}=3g_n+1 gn+1=3gn+1时,复杂度稳定在 o ( n 1.25 ) o(n^{1.25}) o(n1.25) 不稳定
冒泡排序/bubble sort 每次遍历都找出来最大/最小的元素 o ( n 2 ) o(n^2) o(n2) 稳定
选择排序/selection sort 每次遍历找出最小/最大元素,和当前值交换位置 o ( n 2 ) o(n^2) o(n2) 不稳定
快速排序 每次排序都排好某个数,让比这个数小的数都在这个数左边,比这个数大的数都在这个数右边,实现可见这里 o ( n l o g n ) o(nlogn) o(nlogn) 不稳定
堆排序 o ( n l o g n ) o(nlogn) o(nlogn) 不稳定
归并排序 排左边,排右边,再合并有序数组 o ( n l o g n ) o(nlogn) o(nlogn) 稳定

应用场景及题目

排序方法 常用场景 相关题目
直接插入排序
折半插入排序
希尔排序
冒泡排序 找到最大值
快速排序 所有排序题目,建议首选 215. 数组中的第K个最大元素
堆排序
归并排序 链表排序 合并两个有序数组、合并两个有序链表、合并K个排序链表
有序矩阵中第K小的元素
排序链表、翻转对

快排实现
递归版,核心思想是分治法
时间复杂度:平均o(nlogn),不稳定
思想:每次排好1个数,在这个数前面的都是比它小的,在它后面的都是比它大的
优化点:如果每次都选择中间的数字作为pivot,对于已经排序好的数组,最坏情况复杂度会达到 o ( n 2 ) o(n^2) o(n2),所以选择pivot非常重要,一般最好随机选择。同时记录与pivot相同的数字,以降低复杂度

def quick_sort(nums: list) -> list:
    if len(nums) < 2:
        return nums
    pivot = random.choice(nums)
    left = [x for x in nums if x < pivot]
    right = [x for x in nums if x > pivot]
    mid = [x for x in nums if x == pivot]
    return quick_sort(left) + mid + quick_sort(right)

非递归版,核心思想是用栈来模拟递归

def partition(nums: list, left: int, right: int) -> tuple:
    """
    put pivot to the right place

    Returns:
        left_index, right_index for pivot
    """
    pivot = nums[random.choice(range(left, right + 1))]
    left_nums = [x for x in nums[left:right + 1] if x < pivot]
    right_nums = [x for x in nums[left: right + 1] if x > pivot]
    mid_nums = [x for x in nums[left: right + 1] if x == pivot]
    sorted_nums = left_nums + mid_nums + right_nums
    j = 0
    for i in range(left, right + 1):
        nums[i] = sorted_nums[j]
        j += 1
    return left + len(left_nums), left + len(left_nums) + len(mid_nums) - 1


def sortArray(nums: List[int]) -> List[int]:
    left, right = 0, len(nums) - 1
    stack = [(left, right)]
    while stack:
        left, right = stack.pop()
        new_left, new_right = partition(nums, left, right)
        if new_left > left:
            stack.append((left, new_left - 1))
        if new_right < right:
            stack.append((new_right + 1, right))
    return nums

三色旗问题
一个很有意思的排序问题,核心思想是保存最小的数字指针和最大的数字指针。题目是75. 颜色分类

查找/二分法

二分查找
核心思想:丢弃不符合要求的一半数据,在剩下的一半中继续寻找
关键点:

  1. left, right的选取:left, right代表要搜索的空间范围,正常来说,left = 0, right = len(nums)-1,但比如是插入的题,那么right = len(nums)
  2. 退出循环的条件:while left < right
  3. 最终返回值:更新后的mid。需要注意,实际上最终的结果应该是mid,但因为大部分情况下退出都是因为left=right,所以直接返回left或者right即可。但如果是插入题,初始化right = len(nums),同时mid取值选了右边界,可能会导致超出取数的索引,此时需要额外处理
  4. mid的取值方式,要和boundary shrink的方式匹配
# mid取左边界,那么下一步更新的时候,就要exclude旧left
mid = (left + right) // 2
if target > nums[mid]:
    left = mid + 1
else:
    right = mid

# mid取右边界,那么下一步更新exclude right
mid = (left + right + 1) // 2
if target < nums[mid]:
	right = mid - 1
else:
	left = mid

注意在exclude时,还可以进一步考虑,当已经找到了后,要不要把这个位置exclude掉。如果exclude掉,那么返回的索引,是插入的最右索引(如bisect.bisect_right),此时需要判断nums[loc-1]是否与target相同。写法上:

mid = (left + right) // 2
if target < nums[mid]:
	right = mid
else:
	left = mid + 1

感觉其实mid取左边界还是右边界无所谓的,重点是更新的时候,丢弃哪一部分。可以直接把题目分成2种,找到最左边界,找到最右边界。

  • 对于找左边界的题目,初始化mid = left,丢弃左边小于target的部分。
  • 对于找右边界的题目,初始化mid = right,丢弃右边大于target的部分。

总结题目类型。假设nums里可以有重复值,按照如果查找到,则返回索引,如果查不到,则返回插入的位置,那么可以分为,返回最左边的索引、返回最右边的索引
因为是插入,所以right=len(nums)。以返回最左边索引为例,令mid更新为左边界,按照exclude old left来更新,有:

def f_left(nums: list, target: int) -> int:
    left, right = 0, len(nums)
    while left < right:
        mid = (left + right) // 2
        if target > nums[mid]:
            left = mid + 1
        else:
            right = mid
    return left

类似地,以返回最右索引为例,需要额外注意,因为初始化时right=len(nums),导致插入的数字如果是最右边,那么在循环内会导致list取数出错,所以判断一下如果超过边界了就跳出循环。同时注意最终返回值应该是更新后的mid

def f_right(nums: list, target: int) -> int:
	left, right = 0, len(nums)
	while left < right:
		mid = (left + right + 1) // 2
		if mid >= len(nums):
			break
		if target < nums[mid]:
			right = mid - 1
		else:
			left = mid
	return (left + right + 1) // 2

python3内置了二分查找,见python3刷题技巧的bisect章节

  • 基本题目(easy):704. 二分查找、35. 搜索插入位置、374. 猜数字大小、69. Sqrt(x)
  • 旋转数组
    33. 搜索旋转排序数组、81. 搜索旋转排序数组ii(旋转数组+重复数字)
    153. 寻找旋转排序数组中的最小值、154. 寻找旋转排序数组中的最小值 II
    162. Find Peak Element
  • 其他变形:
    转变数组后最接近目标值的数组和(比较的数字不是直接给出的,而要经过转换)
    74. Search a 2D Matrix、240. Search a 2D Matrix II
    有序矩阵中第K小的元素(矩阵中的二分查找,有一定trick)
    第一个错误的版本(找左边界)、在排序数组中查找元素的第一个和最后一个位置(找左、右边界)、找到 K 个最接近的元素(找左边界、再线性扫描)

值得一做:275. H-Index II,2616. Minimize the Maximum Difference of Pairs

852. Peak Index in a Mountain Array
1870. Minimum Speed to Arrive on Time
2251. Number of Flowers in Full Bloom

410,774,875,1011,1231,1283,1482,1539,1802,2226,2560,2616

实现某种运算
两数相除
50. Pow(x, n)

二分插入
计算右侧小于当前元素的个数

动态规划

分类参考了这篇博客:https://blog.csdn.net/cc_again/article/details/25866971

递推

70. Climbing Stairs

53. 最大子序和、918. Maximum Sum Circular Subarray

542. 01 Matrix

2707. Extra Characters in a String

486. 预测赢家

Maximum Number of Consecutive Values You Can Make

杨辉三角、杨辉三角 II
不相交的线

62. 不同路径、63. 不同路径 II

地下城游戏
三角形最小路径和
不同的二叉搜索树

学生出勤记录 II

目标和

最大平均值和的分组、分割数组的最大值

整数拆分、剪绳子

764, 221, 85

1638
2328. Number of Increasing Paths in a Grid

最小路径和

198. 打家劫舍(简单递推)、213. 打家劫舍 II(2遍递推)、337. 打家劫舍 III(树形dp)

5545/1646-无矛盾的最佳球队(需要自己构造数组辅助dp)

1027. Longest Arithmetic Subsequence

LIS(最长递增序列)

300. 最长上升子序列、354. Russian Doll Envelopes
乘积最大子数组
646, 673, 712

递增子序列

LCS(最长公共子序列)

最长公共子序列、最长回文子序列
最长重复子数组

树形dp

124. 二叉树中的最大路径和、二叉树的坡度

树中距离之和

区间dp

weighted interval scheduling
1235. Maximum Profit in Job Scheduling、2830. Maximize the Profit as the Salesman

戳气球(递归超时)、移除盒子(递归超时)

需要结合其他数据结构优化类

这类问题,是普通的dp可以解决,但是时间复杂度比较高,需要结合其他数据结构做优化
1696. Jump Game VI
1871. Jump Game VII

1218. Longest Arithmetic Subsequence of Given Difference

字符串类


编辑距离、两个字符串的删除操作(编辑距离变种之只有删除操作)、712. 两个字符串的最小ASCII删除和(编辑距离变种之只有删除操作)
通配符匹配、正则表达式匹配

97. 交错字符串

背包

0-1背包
分割等和子集、474、494

完全背包
322、518
面试题 08.11. 硬币

1、组合问题:
377. 组合总和 Ⅳ
494. 目标和
518. Coin Change II
2、True、False问题:
139. 单词拆分
416. 分割等和子集
3、最大最小问题:
474. 一和零
322. 零钱兑换

股票买卖系列

参考题解:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/solution/yi-ge-tong-yong-fang-fa-tuan-mie-6-dao-gu-piao-wen/
dp本质是状态转移,所以可以先穷举所有状态,然后再用dp压缩中间过程

121. 买卖股票的最佳时机(只能进行一次交易)
122. 买卖股票的最佳时机 II(无限次交易)
123. 买卖股票的最佳时机 III(最多买卖2次)
188. 买卖股票的最佳时机 IV(最多买卖k次)
714. 买卖股票的最佳时机含手续费(无限次交易、买卖增加手续费)
309. 最佳买卖股票时机含冷冻期(无限次交易、第二次买入和上一次卖出需要间隔1天)

实际上,上述所有题目,都有一个前提,股票只能卖掉再买,也就是当前手中只能持有1个股票。更进一步地,还可以讨论当前手中最多能持有k个股票。再进一步地,可以有不同的股票,不同的价格,手中每种股票还能再持有k个……

前缀和

前缀和是数组前i项之和,包括i项

. 定义式 递推式
一维 b [ i ] = ∑ k = 0 i a [ k ] b[i] = \sum_{k = 0}^ia[k] b[i]=k=0ia[k] b [ i ] = a [ 0 ] + a [ 1 ] + ⋯ + a [ i ] b[i] = a[0] + a[1] + \dots + a[i] b[i]=a[0]+a[1]++a[i]
二维 b [ i ] [ j ] = ∑ x = 0 i ∑ y = 0 j a [ x ] [ y ] b[i][j] = \sum_{x = 0}^i\sum_{y = 0}^j a[x][y] b[i][j]=x=0iy=0ja[x][y] b [ i ] [ j ] = b [ i − 1 ] [ j ] + b [ i ] [ j − 1 ] − b [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i-1][j-1] + a[i][j] b[i][j]=b[i1][j]+b[i][j1]b[i1][j1]+a[i][j]

前缀和常见的题目,一般是求满足xx条件的子数组,注意子数组/subarray和子序列/subsequence之前的区别。

子数组/subarray: 连续的
子序列/subsequence: 可以不连续

前缀和的核心点在于,确定好相减这个操作的物理意义,尤其是被减掉的那个子数组。通常情况下,被减掉的子数组没有特殊要求,如560. 和为K的子数组等。但也有特定要求的子数组,如2488. Count Subarrays With Median K,就要求被减掉的数组是在本题中的k左边以保证中位数

  • 一维前缀和
    和为K的子数组(基础题目)、路径总和 III(基础题+树的遍历,树的路径是数组)
    和可被k整除的子数组(取模)、连续的子数组和(倍数,考虑0的取模)
    每个元音包含偶数次的最长子字符串(奇偶性,用异或的值来作为键)
    2488. Count Subarrays With Median K

  • 二维前缀和
    矩阵区域和(基础)

238. Product of Array Except Self

hard, worth thinking
1871. Jump Game VII

位运算/Bit Manipulation

求二进制中1的个数(x & x - 1, eliminate the rightest 1)

389. Find the Difference

136. 只出现一次的数字(异或解法、通用解法)、137. 只出现一次的数字 II(数学解法)、260. Single Number III

191. Number of 1 Bits、201. 数字范围按位与

汉明距离

1125. Smallest Sufficient Team

338. Counting Bits

其他

数学

第N个数字、快乐数

172. Factorial Trailing Zeroes

2811. Check if it is Possible to Split Array

459. Repeated Substring Pattern

x数之和

最基础、最简单的就是1. 两数之和,核心思想:用哈希表查询target - num

接下来以此为基础的题目,在数字上变形的,有:

  • 15. 三数之和,核心思想:降维到两数之和,注意去重的细节
  • 3Sum Closest: similar to 3Sum
  • 四数之和,核心思想:降维到三数之和

在输入形式上变形的,有:

  • 两数之和 II - 输入有序数组,核心思想:左右向中间扫描
  • 两数之和 IV - 输入 BST,核心思想:中序遍历后转换为两数之和/任意方式遍历时,结合哈希表

自己实现加减乘除

加法

  1. 核心思想:模拟人脑加法,从右向左、保持进位;利用数学性质,从左向右、每次结果*10再相加
    题目:两数相加、445. 两数相加II、加一、67. 二进制求和、字符串相加
  2. 核心思想:位运算
    题目:两整数之和

乘法

核心思想:从左向右加,每次结果乘10
题目:字符串相乘

计算器

基本计算器(只有+, -, ())、基本计算器 II(+, -, *, /,无括号)(内含有括号的代码)

滑动窗口

滑动窗口适用于这类题目:

  1. 求满足某条件的子数组
  2. 数组中只包含正数(可以通过缩减窗口来保证和变小,如果还有负数,则可能通过增大窗口,也能让和变小)

209. 长度最小的子数组、乘积小于K的子数组
K 个不同整数的子数组
1493. Longest Subarray of 1‘s After Deleting One Element、2024. Maximize the Confusion of an Exam

3. 无重复字符的最长子串

2090. K Radius Subarray Averages
239

438. Find All Anagrams in a String

排列生成

下一个排列

设计类题目

感觉这类题目出得好的很有难度,出得一般的写起来怪怪的。。。
设计推特
Design Authentication Manager

你可能感兴趣的:(OJ题目记录,算法)