leetcode刷题笔记java版,持续更新中....
- leetcode热题 HOT 100
- 高频知识点
)
leetcode热题 HOT 100
题目分类
分类 | 题号 |
---|---|
深度搜索 | 207 |
广度搜索 | 207 |
拓扑排序 | 210 |
二叉树 | 226、236 |
动态规划 | 221、279、300 |
前缀和 | 238 |
滑动窗口 | 239 |
最大最小堆 | 239 |
搜索 | 240 |
二分查找 | 240 |
双指针 | 283 |
207. 课程表
【方法1】拓扑排序、深度搜索
class Solution {
private List<List<Integer>> edges;
// 0-未搜索,1-搜索中,2-已完成
private int[] visited;
// 图中是否存在环,即此题是否有解
private boolean valid = true;
private List<List<Integer>> buildGraph(int nodes, int[][] relationship) {
List<List<Integer>> edges = new ArrayList<>();
for (int i = 0; i < nodes; i++) {
edges.add(new ArrayList<>());
}
for (int[] a : relationship) {
edges.get(a[1]).add(a[0]);
}
return edges;
}
private void dfs(int u) {
// 搜索u时现将u标记为搜索中
visited[u] = 1;
// 遍历所有与u相连的节点
for (int v : edges.get(u)) {
if (visited[v] == 0) {
// v节点未搜索时,则深度搜索v
dfs(v);
// 如果无效则直接返回
if (!valid) {
return;
}
} else if (visited[v] == 1) {
// v节点搜索中时,图中存在环,无效
valid = false;
return;
}
}
// 对节点u完成搜索,标记为已完成
visited[u] = 2;
}
public boolean canFinish(int numCourses, int[][] prerequisites) {
edges = buildGraph(numCourses, prerequisites);
visited = new int[numCourses];
// 遍历节点,如果无效直接退出
for (int i = 0; i < numCourses & valid; i++) {
if (visited[i] == 0) {
// 节点未搜索,则深度搜索此节点
dfs(i);
}
}
return valid;
}
}
【方法2】拓扑排序、广度搜索
class Solution {
private List<List<Integer>> edges;
private int[] inDeg;
private List<List<Integer>> buildGraph(int nodes, int[][] relationship) {
List<List<Integer>> edges = new ArrayList<>();
for (int i = 0; i < nodes; i++) {
edges.add(new ArrayList<>());
}
for (int[] a : relationship) {
edges.get(a[1]).add(a[0]);
++inDeg[a[0]];
}
return edges;
}
public boolean canFinish(int numCourses, int[][] prerequisites) {
inDeg = new int[numCourses];
edges = buildGraph(numCourses, prerequisites);
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDeg[i] == 0) {
queue.offer(i);
}
}
List<Integer> stack = new ArrayList<>();
while (!queue.isEmpty()) {
int u = queue.poll();
stack.add(u);
for (int v : edges.get(u)) {
--inDeg[v];
if (inDeg[v] == 0) {
queue.offer(v);
}
}
}
return stack.size() == numCourses;
}
}
221. 最大正方形
【方法1】动态规划
状态转移方程:
定义dp[i][j]为以(i, j) 为有下角的正方形的最大边长,有:
d
p
[
i
]
[
j
]
=
{
m
a
t
r
i
x
[
i
]
[
j
]
i
=
0
或
j
=
0
0
m
a
t
r
i
x
[
i
]
[
j
]
=
0
min
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
−
1
]
)
+
1
其
他
dp[i][j] = \begin{cases} matrix[i][j] & i = 0 或 j = 0 \\ 0 & matrix[i][j] = 0 \\ \min(dp[i-1][j], dp[i][j - 1], dp[i-1][j-1]) +1 & 其他 \end{cases}
dp[i][j]=⎩⎪⎨⎪⎧matrix[i][j]0min(dp[i−1][j],dp[i][j−1],dp[i−1][j−1])+1i=0或j=0matrix[i][j]=0其他
class Solution {
public int maximalSquare(char[][] matrix) {
int[][] side = new int[matrix.length][matrix[0].length];
int maxSide = 0;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (i == 0 || j == 0) {
side[i][j] = matrix[i][j] - '0';
} else {
if (matrix[i][j] == '0') {
side[i][j] = 0;
} else {
side[i][j] = Math.min(Math.min(side[i - 1][j], side[i][j - 1]), side[i - 1][j - 1]) + 1;
}
}
maxSide = Math.max(maxSide, side[i][j]);
}
}
return maxSide * maxSide;
}
}
226. 翻转二叉树
【方法1】使用队列按层遍历二叉树
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = queue.peek();
TreeNode tmp = node.left;
node.left = node.right;
node.right = tmp;
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
queue.poll();
}
return root;
}
}
【方法2】递归遍历二叉树
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
236. 二叉树最近的公共祖先
【方法1】递归
递归条件:定义
f
x
f_x
fx表示
x
x
x节点的子树中是否包含 p 节点或 q节点,则公共祖先一定满足
(
f
l
s
o
n
&
&
f
r
s
o
n
)
∣
∣
(
(
x
=
=
p
∣
∣
x
=
=
q
)
&
&
(
f
l
s
o
n
∣
∣
f
r
s
o
n
)
)
(f_{lson} \&\& f_{rson})||((x==p||x==q)\&\&(f_{lson}||f_{rson}))
(flson&&frson)∣∣((x==p∣∣x==q)&&(flson∣∣frson))
- 公共祖先左子树和右子树同时包含p节点或者q节点,则一个子树包含一个节点,另外一个节点必在另一子树;
- 节点本身是p或q的一个节点,且其子树包含另一个节点
- 只要从叶子节点开始搜索,则可以保证深度最大的公共祖先
class Solution {
private TreeNode ans;
public Solution() {
this.ans = null;
}
private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return false;
boolean lson = dfs(root.left, p, q);
boolean rson = dfs(root.right, p, q);
if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {
ans = root;
}
return lson || rson || (root.val == p.val || root.val == q.val);
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.dfs(root, p, q);
return this.ans;
}
}
【方法二】存储前序遍历节点的路径
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
LinkedList<TreeNode> pPath = new LinkedList<>();
LinkedList<TreeNode> qPath = new LinkedList<>();
findPath(root, p, pPath);
findPath(root, p, qPath);
int len = Math.min(pPath.size(), qPath.size());
int i = 0;
for (; i < len; i++) {
if (pPath.get(i) != qPath.get(i)) {
break;
}
}
return pPath.get(i - 1);
}
private boolean findPath(TreeNode root, TreeNode p, LinkedList<TreeNode> path) {
if (root == null) {
return false;
}
path.add(root);
if (root == p) {
return true;
}
if (findPath(root.left, p , path) || findPath(root.right, p, path)) {
return true;
}
path.removeLast();
return false;
}
}
238. 除自身以外数组的乘积
【方法1】利用前缀乘积和后缀乘积数组
class Solution {
public int[] productExceptSelf(int[] nums) {
int[] prefix = new int[nums.length];
int[] suffix = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
if (i == 0) {
prefix[0] = 1;
suffix[nums.length - 1] = 1;
} else {
prefix[i] = prefix[i - 1] * nums[i - 1];
suffix[nums.length - 1 - i] = suffix[nums.length - i] * nums[nums.length - i];
}
}
int[] answer = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
answer[i] = prefix[i] * suffix[i];
}
return answer;
}
}
【方法2】优化,去掉数组prefix和suffix
class Solution {
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
int[] answer = new int[length];
// answer[i] 表示索引 i 左侧所有元素的乘积
// 因为索引为 '0' 的元素左侧没有元素, 所以 answer[0] = 1
answer[0] = 1;
for (int i = 1; i < length; i++) {
answer[i] = nums[i - 1] * answer[i - 1];
}
// R 为右侧所有元素的乘积
// 刚开始右边没有元素,所以 R = 1
int R = 1;
for (int i = length - 1; i >= 0; i--) {
// 对于索引 i,左边的乘积为 answer[i],右边的乘积为 R
answer[i] = answer[i] * R;
// R 需要包含右边所有的乘积,所以计算下一个结果时需要将当前值乘到 R 上
R *= nums[i];
}
return answer;
}
}
239. 滑动窗口最大值
【方法1】优先队列实现最大堆
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (k == 1) {
return nums;
}
List<Integer> result = new LinkedList<>();
Queue<int[]> queue = new PriorityQueue<>((o1, o2) -> o1[0] == o2[0] ? o2[1] - o1[1] : o2[0] - o1[0]);
for (int i = 0; i < k; i++) {
queue.add(new int[]{nums[i], i});
}
result.add(queue.peek()[0]);
for (int i = k; i < nums.length; i++) {
while (queue.peek()[1] <= i - k) {
queue.poll();
}
queue.offer(new int[] {nums[i], i});
result.add(queue.peek()[0]);
}
return result.stream().mapToInt(Integer::intValue).toArray();
}
}
240. 搜索二维矩阵II
【方法1】暴力解法,遍历搜索O(mn)不推荐
【方法2】按行二分查找O(mlogn)
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
for (int i = 0; i < matrix.length; i++) {
int start = 0;
int end = matrix[0].length;
while (start < end) {
int middle = (end - start) / 2 + start;
if (matrix[i][middle] > target) {
end = middle;
} else if (matrix[i][middle] == target) {
return true;
} else {
start = middle + 1;
}
}
}
return false;
}
}
【方法3】Z字型查找
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int x = 0, y = n - 1;
while (x < m && y >= 0) {
if (matrix[x][y] == target) {
return true;
}
if (matrix[x][y] > target) {
--y;
} else {
++x;
}
}
return false;
}
}
279. 完全平方数
【方法1】动态规划
定义dp(n)
为完全平方数的和为n
的个数,则
n
=
i
2
+
n
−
i
2
,
其
中
i
=
[
1
,
n
]
,
且
为
整
数
n = i^2 + n - i^2, 其中i = [1,\sqrt{n}], 且为整数
n=i2+n−i2,其中i=[1,n],且为整数
则
KaTeX parse error: Got function '\sqrt' with no arguments as superscript at position 14: dp(n) = min^\̲s̲q̲r̲t̲{n}_idp(n-i*i) …
class Solution {
public int numSquares(int n) {
int[] f = new int[n + 1];
for (int i = 1; i <= n; i++) {
int min = Integer.MAX_VALUE;
for (int j = 1; j * j <= i; j++) {
min = Math.min(min, f[i - j * j]);
}
f[i] = min + 1;
}
return f[n];
}
}
283.移动零
【方法1】双指针
class Solution {
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
int end = nums.length;
for (int i = nums.length - 1; i >= 0; i--) {
if (nums[i] == 0) {
end--;
int j = i;
while (j < end) {
nums[j] = nums[j + 1];
j++;
}
nums[end] = 0;
}
}
}
}
300. 最长递增子序列
【方法1】动态规划
定义dp(i)
为下标为i结尾的子数组的最长递增数组长度,则
d
p
(
i
)
=
m
a
x
(
d
p
(
j
)
)
+
1
,
其
中
0
=
<
j
<
i
且
n
u
m
s
[
j
]
<
n
u
m
s
[
i
]
dp(i) = max(dp(j)) + 1, 其中 0=<j<i 且 nums[j] < nums[i]
dp(i)=max(dp(j))+1,其中0=<j<i且nums[j]<nums[i]
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
dp[0] = 1;
int max = 1;
for (int i = 0; i < nums.length; i++) {
// 只包括当前数本身,即前面都是递减数列时
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
高频知识点
分类 | 题号 |
---|---|
二叉树 | 226、236 |
动态规划 | 221、1277 |