一. 初识算法
1.1 什么是算法?
定义
在数学和计算机科学领域,算法是一系列有限的严谨指令,通常用于解决一类特定问题或执行计算
In mathematics and computer science, an algorithm (/ˈælɡərɪðəm/) is a finite sequence of rigorous instructions, typically used to solve a class of specific problems or to perform a computation.[^1]
Introduction to Algorithm[^2]
不正式的说,算法就是任何定义优良的计算过程:接收一些值作为输入,在有限的时间内,产生一些值作为输出。
Informally, an algorithm is any well-defined computational procedure that takes some value, or set of values, as input and produces some value, or set of values, as output in a finite amount of time.
1.2 什么是数据结构?
定义
在计算机科学领域,数据结构是一种数据组织、管理和存储格式,通常被选择用来高效访问数据
In computer science, a data structure is a data organization, management, and storage format that is usually chosen for efficient access to data
Introduction to Algorithm[^2]
数据结构是一种存储和组织数据的方式,旨在便于访问和修改
A data structure is a way to store and organize data in order to facilitate access and modifications
接下来我们通过对一个非常著名的二分查找算法的讲解来认识一下算法
1.3 二分查找 [^3]
二分查找算法也称折半查找,是一种非常高效的工作于有序数组的查找算法。后续的课程中还会学习更多的查找算法,但在此之前,不妨用它作为入门。
二分查找基础版
需求:在有序数组 A A A 内,查找值 t a r g e t target target
- 如果找到返回索引
- 如果找不到返回 − 1 -1 −1
算法描述 | |
---|---|
前提 | 给定一个内含 n n n 个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ ⋯ ≤ A n − 1 A_{0}\leq A_{1}\leq A_{2}\leq \cdots \leq A_{n-1} A0≤A1≤A2≤⋯≤An−1,一个待查值 t a r g e t target target |
1 | 设置 i = 0 i=0 i=0, j = n − 1 j=n-1 j=n−1 |
2 | 如果 i > j i \gt j i>j,结束查找,没找到 |
3 | 设置 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) , m m m 为中间索引, f l o o r floor floor 是向下取整( ≤ i + j 2 \leq \frac {i+j}{2} ≤2i+j 的最小整数) |
4 | 如果 t a r g e t < A m target < A_{m} target<Am 设置 j = m − 1 j = m - 1 j=m−1,跳到第2步 |
5 | 如果 A m < t a r g e t A_{m} < target Am<target 设置 i = m + 1 i = m + 1 i=m+1,跳到第2步 |
6 | 如果 A m = t a r g e t A_{m} = target Am=target,结束查找,找到了 |
java 实现
public static int binarySearch(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) { // 在左边
j = m - 1;
} else if (a[m] < target) { // 在右边
i = m + 1;
} else {
return m;
}
}
return -1;
}
-
i
,
j
i,j
i,j 对应着搜索区间
[
0
,
a
.
l
e
n
g
t
h
−
1
]
[0,a.length-1]
[0,a.length−1](注意是闭合的区间),
i
<
=
j
i<=j
i<=j 意味着搜索区间内还有未比较的元素,
i
,
j
i,j
i,j 指向的元素也可能是比较的目标
- 思考:如果不加 i = = j i==j i==j 行不行?
- 回答:不行,因为这意味着 i , j i,j i,j 指向的元素会漏过比较
- m m m 对应着中间位置,中间位置左边和右边的元素可能不相等(差一个),不会影响结果
- 如果某次未找到,那么缩小后的区间内不包含 m m m
二分查找改变版
另一种写法
public static int binarySearch(int[] a, int target) {
int i = 0, j = a.length;
while (i < j) {
int m = (i + j) >>> 1;
if (target < a[m]) { // 在左边
j = m;
} else if (a[m] < target) { // 在右边
i = m + 1;
} else {
return m;
}
}
return -1;
}
-
i
,
j
i,j
i,j 对应着搜索区间
[
0
,
a
.
l
e
n
g
t
h
)
[0,a.length)
[0,a.length)(注意是左闭右开的区间),
i
<
j
i<j
i<j 意味着搜索区间内还有未比较的元素,
j
j
j 指向的一定不是查找目标
- 思考:为啥这次不加 i = = j i==j i==j 的条件了?
- 回答:这回 j j j 指向的不是查找目标,如果还加 i = = j i==j i==j 条件,就意味着 j j j 指向的还会再次比较,找不到时,会死循环
- 如果某次要缩小右边界,那么 j = m j=m j=m,因为此时的 m m m 已经不是查找目标了
衡量算法好坏
时间复杂度
下面的查找算法也能得出与之前二分查找一样的结果,那你能说出它差在哪里吗?
public static int search(int[] a, int k) {
for (
int i = 0;
i < a.length;
i++
) {
if (a[i] == k) {
return i;
}
}
return -1;
}
考虑最坏情况下(没找到)例如 [1,2,3,4]
查找 5
int i = 0
只执行一次i < a.length
受数组元素个数 n n n 的影响,比较 n + 1 n+1 n+1 次i++
受数组元素个数 n n n 的影响,自增 n n n 次a[i] == k
受元素个数 n n n 的影响,比较 n n n 次return -1
,执行一次
粗略认为每行代码执行时间是 t t t,假设 n = 4 n=4 n=4 那么
- 总执行时间是 ( 1 + 4 + 1 + 4 + 4 + 1 ) ∗ t = 15 t (1+4+1+4+4+1)*t = 15t (1+4+1+4+4+1)∗t=15t
- 可以推导出更一般地公式为, T = ( 3 ∗ n + 3 ) t T = (3*n+3)t T=(3∗n+3)t
如果套用二分查找算法,还是 [1,2,3,4]
查找 5
public static int binarySearch(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) { // 在左边
j = m - 1;
} else if (a[m] < target) { // 在右边
i = m + 1;
} else {
return m;
}
}
return -1;
}
-
int i = 0, j = a.length - 1
各执行 1 次 -
i <= j
比较 f l o o r ( log 2 ( n ) + 1 ) floor(\log_{2}(n)+1) floor(log2(n)+1) 再加 1 次 -
(i + j) >>> 1
计算 f l o o r ( log 2 ( n ) + 1 ) floor(\log_{2}(n)+1) floor(log2(n)+1) 次 -
接下来
if() else if() else
会执行 3 ∗ f l o o r ( log 2 ( n ) + 1 ) 3* floor(\log_{2}(n)+1) 3∗floor(log2(n)+1) 次,分别为- if 比较
- else if 比较
- else if 比较成立后的赋值语句
-
return -1
,执行一次
结果:
- 总执行时间为 ( 2 + ( 1 + 3 ) + 3 + 3 ∗ 3 + 1 ) ∗ t = 19 t (2 + (1+3) + 3 + 3 * 3 +1)*t = 19t (2+(1+3)+3+3∗3+1)∗t=19t
- 更一般地公式为 ( 4 + 5 ∗ f l o o r ( log 2 ( n ) + 1 ) ) ∗ t (4 + 5 * floor(\log_{2}(n)+1))*t (4+5∗floor(log2(n)+1))∗t
注意:
左侧未找到和右侧未找到结果不一样,这里不做分析
两个算法比较,可以看到 n n n 在较小的时候,二者花费的次数差不多
但随着 n n n 越来越大,比如说 n = 1000 n=1000 n=1000 时,用二分查找算法(红色)也就是 54 t 54t 54t,而蓝色算法则需要 3003 t 3003t 3003t
画图采用的是 Desmos | 图形计算器
计算机科学中,时间复杂度是用来衡量:一个算法的执行,随数据规模增大,而增长的时间成本
- 不依赖于环境因素
如何表示时间复杂度呢?
-
假设算法要处理的数据规模是 n n n,代码总的执行行数用函数 f ( n ) f(n) f(n) 来表示,例如:
- 线性查找算法的函数 f ( n ) = 3 ∗ n + 3 f(n) = 3*n + 3 f(n)=3∗n+3
- 二分查找算法的函数 f ( n ) = ( f l o o r ( l o g 2 ( n ) ) + 1 ) ∗ 5 + 4 f(n) = (floor(log_2(n)) + 1) * 5 + 4 f(n)=(floor(log2(n))+1)∗5+4
-
为了对 f ( n ) f(n) f(n) 进行化简,应当抓住主要矛盾,找到一个变化趋势与之相近的表示法
大 O O O 表示法[^4]
其中
- c , c 1 , c 2 c, c_1, c_2 c,c1,c2 都为一个常数
- f ( n ) f(n) f(n) 是实际执行代码行数与 n 的函数
- g ( n ) g(n) g(n) 是经过化简,变化趋势与 f ( n ) f(n) f(n) 一致的 n 的函数
渐进上界
渐进上界(asymptotic upper bound):从某个常数 n 0 n_0 n0开始, c ∗ g ( n ) c*g(n) c∗g(n) 总是位于 f ( n ) f(n) f(n) 上方,那么记作 O ( g ( n ) ) O(g(n)) O(g(n))
- 代表算法执行的最差情况
例1
- f ( n ) = 3 ∗ n + 3 f(n) = 3*n+3 f(n)=3∗n+3
- g ( n ) = n g(n) = n g(n)=n
- 取 c = 4 c=4 c=4,在 n 0 = 3 n_0=3 n0=3 之后, g ( n ) g(n) g(n) 可以作为 f ( n ) f(n) f(n) 的渐进上界,因此表示法写作 O ( n ) O(n) O(n)
例2
- f ( n ) = 5 ∗ f l o o r ( l o g 2 ( n ) ) + 9 f(n) = 5*floor(log_2(n)) + 9 f(n)=5∗floor(log2(n))+9
- g ( n ) = l o g 2 ( n ) g(n) = log_2(n) g(n)=log2(n)
- O ( l o g 2 ( n ) ) O(log_2(n)) O(log2(n))
已知 f ( n ) f(n) f(n) 来说,求 g ( n ) g(n) g(n)
- 表达式中相乘的常量,可以省略,如
- f ( n ) = 100 ∗ n 2 f(n) = 100*n^2 f(n)=100∗n2 中的 100 100 100
- 多项式中数量规模更小(低次项)的表达式,如
- f ( n ) = n 2 + n f(n)=n^2+n f(n)=n2+n 中的 n n n
- f ( n ) = n 3 + n 2 f(n) = n^3 + n^2 f(n)=n3+n2 中的 n 2 n^2 n2
- 不同底数的对数,渐进上界可以用一个对数函数
log
n
\log n
logn 表示
- 例如: l o g 2 ( n ) log_2(n) log2(n) 可以替换为 l o g 10 ( n ) log_{10}(n) log10(n),因为 l o g 2 ( n ) = l o g 10 ( n ) l o g 10 ( 2 ) log_2(n) = \frac{log_{10}(n)}{log_{10}(2)} log2(n)=log10(2)log10(n),相乘的常量 1 l o g 10 ( 2 ) \frac{1}{log_{10}(2)} log10(2)1 可以省略
- 类似的,对数的常数次幂可省略
- 如: l o g ( n c ) = c ∗ l o g ( n ) log(n^c) = c * log(n) log(nc)=c∗log(n)
常见大 O O O 表示法
按时间复杂度从低到高
- 黑色横线 O ( 1 ) O(1) O(1),常量时间,意味着算法时间并不随数据规模而变化
- 绿色 O ( l o g ( n ) ) O(log(n)) O(log(n)),对数时间
- 蓝色 O ( n ) O(n) O(n),线性时间,算法时间与数据规模成正比
- 橙色 O ( n ∗ l o g ( n ) ) O(n*log(n)) O(n∗log(n)),拟线性时间
- 红色 O ( n 2 ) O(n^2) O(n2) 平方时间
- 黑色朝上 O ( 2 n ) O(2^n) O(2n) 指数时间
- 没画出来的 O ( n ! ) O(n!) O(n!)
渐进下界
渐进下界(asymptotic lower bound):从某个常数 n 0 n_0 n0开始, c ∗ g ( n ) c*g(n) c∗g(n) 总是位于 f ( n ) f(n) f(n) 下方,那么记作 Ω ( g ( n ) ) \Omega(g(n)) Ω(g(n))
渐进紧界
渐进紧界(asymptotic tight bounds):从某个常数 n 0 n_0 n0开始, f ( n ) f(n) f(n) 总是在 c 1 ∗ g ( n ) c_1*g(n) c1∗g(n) 和 c 2 ∗ g ( n ) c_2*g(n) c2∗g(n) 之间,那么记作 Θ ( g ( n ) ) \Theta(g(n)) Θ(g(n))
空间复杂度
与时间复杂度类似,一般也使用大 O O O 表示法来衡量:一个算法执行随数据规模增大,而增长的额外空间成本
public static int binarySearchBasic(int[] a, int target) {
int i = 0, j = a.length - 1; // 设置指针和初值
while (i <= j) { // i~j 范围内有东西
int m = (i + j) >>> 1;
if(target < a[m]) { // 目标在左边
j = m - 1;
} else if (a[m] < target) { // 目标在右边
i = m + 1;
} else { // 找到了
return m;
}
}
return -1;
}
二分查找性能
下面分析二分查找算法的性能
时间复杂度
- 最坏情况: O ( log n ) O(\log n) O(logn)
- 最好情况:如果待查找元素恰好在数组中央,只需要循环一次 O ( 1 ) O(1) O(1)
空间复杂度
- 需要常数个指针 i , j , m i,j,m i,j,m,因此额外占用的空间是 O ( 1 ) O(1) O(1)
二分查找平衡版
public static int binarySearchBalance(int[] a, int target) {
int i = 0, j = a.length;
while (1 < j - i) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m;
} else {
i = m;
}
}
return (a[i] == target) ? i : -1;
}
思想:
- 左闭右开的区间, i i i 指向的可能是目标,而 j j j 指向的不是目标
- 不奢望循环内通过
m
m
m 找出目标, 缩小区间直至剩 1 个, 剩下的这个可能就是要找的(通过
i
i
i)
- j − i > 1 j - i > 1 j−i>1 的含义是,在范围内待比较的元素个数 > 1
- 改变 i i i 边界时,它指向的可能是目标,因此不能 m + 1 m+1 m+1
- 循环内的平均比较次数减少了
- 时间复杂度 Θ ( l o g ( n ) ) \Theta(log(n)) Θ(log(n))
二分查找 Java 版
private static int binarySearch0(long[] a, int fromIndex, int toIndex,
long key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
long midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
- 例如
[
1
,
3
,
5
,
6
]
[1,3,5,6]
[1,3,5,6] 要插入
2
2
2 那么就是找到一个位置,这个位置左侧元素都比它小
- 等循环结束,若没找到,low 左侧元素肯定都比 target 小,因此 low 即插入点
- 插入点取负是为了与找到情况区分
- -1 是为了把索引 0 位置的插入点与找到的情况进行区分
Leftmost 与 Rightmost
有时我们希望返回的是最左侧的重复元素,如果用 Basic 二分查找
-
对于数组 [ 1 , 2 , 3 , 4 , 4 , 5 , 6 , 7 ] [1, 2, 3, 4, 4, 5, 6, 7] [1,2,3,4,4,5,6,7],查找元素4,结果是索引3
-
对于数组 [ 1 , 2 , 4 , 4 , 4 , 5 , 6 , 7 ] [1, 2, 4, 4, 4, 5, 6, 7] [1,2,4,4,4,5,6,7],查找元素4,结果也是索引3,并不是最左侧的元素
public static int binarySearchLeftmost1(int[] a, int target) {
int i = 0, j = a.length - 1;
int candidate = -1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else if (a[m] < target) {
i = m + 1;
} else {
candidate = m; // 记录候选位置
j = m - 1; // 继续向左
}
}
return candidate;
}
如果希望返回的是最右侧元素
public static int binarySearchRightmost1(int[] a, int target) {
int i = 0, j = a.length - 1;
int candidate = -1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else if (a[m] < target) {
i = m + 1;
} else {
candidate = m; // 记录候选位置
i = m + 1; // 继续向右
}
}
return candidate;
}
应用
对于 Leftmost 与 Rightmost,可以返回一个比 -1 更有用的值
Leftmost 改为
public static int binarySearchLeftmost(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target <= a[m]) {
j = m - 1;
} else {
i = m + 1;
}
}
return i;
}
- leftmost 返回值的另一层含义: < t a r g e t \lt target <target 的元素个数
- 小于等于中间值,都要向左找
Rightmost 改为
public static int binarySearchRightmost(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else {
i = m + 1;
}
}
return i - 1;
}
- 大于等于中间值,都要向右找
几个名词
范围查询:
- 查询 x < 4 x \lt 4 x<4, 0.. l e f t m o s t ( 4 ) − 1 0 .. leftmost(4) - 1 0..leftmost(4)−1
- 查询 x ≤ 4 x \leq 4 x≤4, 0.. r i g h t m o s t ( 4 ) 0 .. rightmost(4) 0..rightmost(4)
- 查询 4 < x 4 \lt x 4<x,$rightmost(4) + 1 … \infty $
- 查询 4 ≤ x 4 \leq x 4≤x, l e f t m o s t ( 4 ) . . ∞ leftmost(4) .. \infty leftmost(4)..∞
- 查询 4 ≤ x ≤ 7 4 \leq x \leq 7 4≤x≤7, l e f t m o s t ( 4 ) . . r i g h t m o s t ( 7 ) leftmost(4) .. rightmost(7) leftmost(4)..rightmost(7)
- 查询 4 < x < 7 4 \lt x \lt 7 4<x<7, r i g h t m o s t ( 4 ) + 1.. l e f t m o s t ( 7 ) − 1 rightmost(4)+1 .. leftmost(7)-1 rightmost(4)+1..leftmost(7)−1
求排名: l e f t m o s t ( t a r g e t ) + 1 leftmost(target) + 1 leftmost(target)+1
- t a r g e t target target 可以不存在,如: l e f t m o s t ( 5 ) + 1 = 6 leftmost(5)+1 = 6 leftmost(5)+1=6
- t a r g e t target target 也可以存在,如: l e f t m o s t ( 4 ) + 1 = 3 leftmost(4)+1 = 3 leftmost(4)+1=3
求前任(predecessor): l e f t m o s t ( t a r g e t ) − 1 leftmost(target) - 1 leftmost(target)−1
- l e f t m o s t ( 3 ) − 1 = 1 leftmost(3) - 1 = 1 leftmost(3)−1=1,前任 a 1 = 2 a_1 = 2 a1=2
- l e f t m o s t ( 4 ) − 1 = 1 leftmost(4) - 1 = 1 leftmost(4)−1=1,前任 a 1 = 2 a_1 = 2 a1=2
求后任(successor): r i g h t m o s t ( t a r g e t ) + 1 rightmost(target)+1 rightmost(target)+1
- r i g h t m o s t ( 5 ) + 1 = 5 rightmost(5) + 1 = 5 rightmost(5)+1=5,后任 a 5 = 7 a_5 = 7 a5=7
- r i g h t m o s t ( 4 ) + 1 = 5 rightmost(4) + 1 = 5 rightmost(4)+1=5,后任 a 5 = 7 a_5 = 7 a5=7
求最近邻居:
- 前任和后任距离更近者
教程:2023新版Java数据结构与算法视频教程,java高级程序员必学的数据结构与算法
力扣高评价题目列表
引用自 面试最常考的 100 道算法题分类整理! - 知乎 (zhihu.com)
带 ✔️ 是本课程截至目前为止讲解过的
- 1. Two Sum (两数之和), Easy, 11757 likes
- 2. Add Two Numbers (两数相加), Medium, 6524 likes
- 3. Longest Substring Without Repeating Characters (无重复字符的最长子串), Medium, 5845 likes
- 4. Median of Two Sorted Arrays (寻找两个正序数组的中位数), Hard, 4303 likes
- 5. Longest Palindromic Substring (最长回文子串), Medium, 3896 likes
- 15. 3Sum (三数之和), Medium, 3582 likes
- 53. Maximum Subarray (最大子序和), Easy, 3533 likes
- 7. Reverse Integer (整数反转), Easy, 2970 likes
- 11. Container With Most Water (盛最多水的容器), Medium, 2659 likes
- 42. Trapping Rain Water (接雨水), Hard, 2552 likes
- 20. Valid Parentheses (有效的括号), Easy, 2544 likes ✔️
- 10. Regular Expression Matching (正则表达式匹配), Hard, 2273 likes
- 26. Remove Duplicates from Sorted Array (删除有序数组中的重复项), Easy, 2146 likes ✔️
- 136. Single Number (只出现一次的数字), Easy, 1958 likes
- 22. Generate Parentheses (括号生成), Medium, 1946 likes
- 206. Reverse Linked List (反转链表), Easy, 1886 likes ✔️
- 21. Merge Two Sorted Lists (合并两个有序链表), Easy, 1832 likes ✔️
- 70. Climbing Stairs (爬楼梯), Easy, 1791 likes ✔️
- 300. Longest Increasing Subsequence (最长递增子序列), Medium, 1773 likes
- 121. Best Time to Buy and Sell Stock (买卖股票的最佳时机), Easy, 1766 likes
- 72. Edit Distance (编辑距离), Hard, 1743 likes
- 14. Longest Common Prefix (最长公共前缀), Easy, 1707 likes
- 198. House Robber (打家劫舍), Medium, 1585 likes
- 9. Palindrome Number (回文数), Easy, 1568 likes
- 146. LRU Cache (LRU 缓存机制), Medium, 1544 likes
- 19. Remove Nth Node From End of List (删除链表的倒数第 N 个结点), Medium, 1494 likes ✔️
- 33. Search in Rotated Sorted Array (搜索旋转排序数组), Medium, 1493 likes
- 46. Permutations (全排列), Medium, 1484 likes
- 101. Symmetric Tree (对称二叉树), Easy, 1483 likes
- 84. Largest Rectangle in Histogram (柱状图中最大的矩形), Hard, 1472 likes
- 39. Combination Sum (组合总和), Medium, 1466 likes
- 13. Roman to Integer (罗马数字转整数), Easy, 1436 likes
- 23. Merge k Sorted Lists (合并K个升序链表), Hard, 1436 likes ✔️
- 17. Letter Combinations of a Phone Number (电话号码的字母组合), Medium, 1436 likes
- 322. Coin Change (零钱兑换), Medium, 1414 likes
- 32. Longest Valid Parentheses (最长有效括号), Hard, 1400 likes
- 287. Find the Duplicate Number (寻找重复数), Medium, 1325 likes
- 122. Best Time to Buy and Sell Stock II (买卖股票的最佳时机 II), Easy, 1306 likes
- 160. Intersection of Two Linked Lists (相交链表), Easy, 1302 likes ✔️
- 55. Jump Game (跳跃游戏), Medium, 1292 likes
- 76. Minimum Window Substring (最小覆盖子串), Hard, 1280 likes
- 200. Number of Islands (岛屿数量), Medium, 1270 likes
- 78. Subsets (子集), Medium, 1269 likes
- 31. Next Permutation (下一个排列), Medium, 1260 likes
- 96. Unique Binary Search Trees (不同的二叉搜索树), Medium, 1257 likes
- 148. Sort List (排序链表), Medium, 1248 likes
- 236. Lowest Common Ancestor of a Binary Tree (二叉树的最近公共祖先), Medium, 1238 likes
- 25. Reverse Nodes in k-Group (K 个一组翻转链表), Hard, 1230 likes
- 6. ZigZag Conversion (Z 字形变换), Medium, 1226 likes
- 152. Maximum Product Subarray (乘积最大子数组), Medium, 1223 likes
- 215. Kth Largest Element in an Array (数组中的第K个最大元素), Medium, 1211 likes
- 8. String to Integer (atoi) (字符串转换整数 (atoi)), Medium, 1168 likes
- 41. First Missing Positive (缺失的第一个正数), Hard, 1163 likes
- 283. Move Zeroes (移动零), Easy, 1162 likes
- 141. Linked List Cycle (环形链表), Easy, 1161 likes ✔️
- 98. Validate Binary Search Tree (验证二叉搜索树), Medium, 1156 likes
- 124. Binary Tree Maximum Path Sum (二叉树中的最大路径和), Hard, 1152 likes
- 105. Construct Binary Tree from Preorder and Inorder Traversal (从前序与中序遍历序列构造二叉树), Medium, 1149 likes
- 34. Find First and Last Position of Element in Sorted Array (在排序数组中查找元素的第一个和最后一个位置), Medium, 1137 likes ✔️
- 239. Sliding Window Maximum (滑动窗口最大值), Hard, 1114 likes
- 142. Linked List Cycle II (环形链表 II), Medium, 1097 likes ✔️
- 139. Word Break (单词拆分), Medium, 1097 likes
- 45. Jump Game II (跳跃游戏 II), Medium, 1094 likes
- 169. Majority Element (多数元素), Easy, 1089 likes
- 234. Palindrome Linked List (回文链表), Easy, 1072 likes ✔️
- 62. Unique Paths (不同路径), Medium, 1072 likes
- 189. Rotate Array (旋转数组), Medium, 1057 likes
- 94. Binary Tree Inorder Traversal (二叉树的中序遍历), Easy, 1052 likes ✔️
- 56. Merge Intervals (合并区间), Medium, 1051 likes
- 88. Merge Sorted Array (合并两个有序数组), Easy, 1041 likes ✔️
- 560. Subarray Sum Equals K (和为K的子数组), Medium, 1036 likes
- 279. Perfect Squares (完全平方数), Medium, 1035 likes
- 35. Search Insert Position (搜索插入位置), Easy, 1005 likes
- 24. Swap Nodes in Pairs (两两交换链表中的节点), Medium, 996 likes
- 85. Maximal Rectangle (最大矩形), Hard, 983 likes
- 28. Implement strStr() (实现 strStr()), Easy, 982 likes
- 92. Reverse Linked List II (反转链表 II), Medium, 980 likes
- 155. Min Stack (最小栈), Easy, 979 likes
- 79. Word Search (单词搜索), Medium, 979 likes
- 27. Remove Element (移除元素), Easy, 967 likes
- 51. N-Queens (N 皇后), Hard, 965 likes
- 75. Sort Colors (颜色分类), Medium, 961 likes
- 102. Binary Tree Level Order Traversal (二叉树的层序遍历), Medium, 960 likes ✔️
- 48. Rotate Image (旋转图像), Medium, 960 likes
- 95. Unique Binary Search Trees II (不同的二叉搜索树 II), Medium, 955 likes
- 64. Minimum Path Sum (最小路径和), Medium, 954 likes
- 406. Queue Reconstruction by Height (根据身高重建队列), Medium, 947 likes
- 226. Invert Binary Tree (翻转二叉树), Easy, 941 likes
- 437. Path Sum III (路径总和 III), Medium, 937 likes
- 104. Maximum Depth of Binary Tree (二叉树的最大深度), Easy, 937 likes
- 237. Delete Node in a Linked List (删除链表中的节点), Easy, 936 likes ✔️
- 337. House Robber III (打家劫舍 III), Medium, 929 likes
- 18. 4Sum (四数之和), Medium, 918 likes
- 91. Decode Ways (解码方法), Medium, 904 likes
- 207. Course Schedule (课程表), Medium, 897 likes
- 37. Sudoku Solver (解数独), Hard, 897 likes
- 175. Combine Two Tables (组合两个表), Easy, 891 likes
- 416. Partition Equal Subset Sum (分割等和子集), Medium, 886 likes
- 238. Product of Array Except Self (除自身以外数组的乘积), Medium, 885 likes
- 114. Flatten Binary Tree to Linked List (二叉树展开为链表), Medium, 877 likes