🔥 LeetCode 热题 HOT 100(61-70)
207. 课程表
思路:根据题意可知:当课程之间不存在 环状 循环依赖时,便能完成所有课程的学习,反之则不能。因此可以将问题转换成: 判断有向图中是否存在环。使用 拓扑排序法 :
- 构建 入度表:记录每个结点的入度数;
- 构建 邻接表:记录每个结点的后继结点;
- 将入度为0的结点加入队列,开始遍历;
- 当一个结点出队时,将其后继结点的入度数减1,若减1后入度为0则将后继结点也加入队列;
- 当所有入度为0的结点都已经出队时,检查是否还有入度不为0的结点,存在则说明不可能完成所有课程的学习。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
Map<Integer, Integer> indegreeMap = new HashMap<>();
Map<Integer, List<Integer>> postsMap = new HashMap<>();
// 初始化 入度表, 邻接表
for (int i = 0; i < numCourses; i++) {
indegreeMap.put(i, 0);
postsMap.put(i, new LinkedList<>());
}
for (int[] temp : prerequisites) {
int cur = temp[1];
int post = temp[0];
// 统计后继结点入度
indegreeMap.put(post, indegreeMap.getOrDefault(post, 0) + 1);
// 建立 邻接表
postsMap.get(cur).add(post);
}
Deque<Integer> queue = new LinkedList<>();
// 向队列中加入入度为0的结点
for (Integer key : indegreeMap.keySet()) {
if (indegreeMap.get(key) == 0) {
queue.offer(key);
}
}
// 出队
while (!queue.isEmpty()) {
Integer cur = queue.poll();
List<Integer> posts = postsMap.get(cur);
for (Integer post : posts) {
// 将后继结点入度减1
indegreeMap.put(post, indegreeMap.get(post) - 1);
// 将减1后入度为0,且存在后继的后继结点加入队列
if (indegreeMap.get(post) == 0) {
queue.offer(post);
}
}
}
for (Integer key : indegreeMap.keySet()) {
if (indegreeMap.get(key) > 0) {
return false;
}
}
return true;
}
}
208. 实现 Trie (前缀树)
class Trie {
private Node root;
/** Initialize your data structure here. */
public Trie() {
root = new Node();
}
/** Inserts a word into the trie. */
public void insert(String word) {
Node temp = root;
for (int i = 0; i < word.length(); i++) {
if (temp.children[word.charAt(i) - 'a'] == null) {
temp.children[word.charAt(i) - 'a'] = new Node();
}
temp = temp.children[word.charAt(i) - 'a'];
}
temp.isEnd = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
Node temp = root;
for (int i = 0; i < word.length(); i++) {
if (temp.children[word.charAt(i) - 'a'] == null) {
return false;
}
temp = temp.children[word.charAt(i) - 'a'];
}
return temp.isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
Node temp = root;
for (int i = 0; i < prefix.length(); i++) {
if (temp.children[prefix.charAt(i) - 'a'] == null) {
return false;
}
temp = temp.children[prefix.charAt(i) - 'a'];
}
return true;
}
}
class Node {
boolean isEnd = false;
Node[] children = new Node[26];
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
215. 数组中的第K个最大元素
思路:基于快速排序的选择方法。每次partition
即可确定一个元素的位置 mid
,如果当前mid
的位置小于len - k
,则下一次的partition
区间就为[mid + 1, right]
,反之则为[left, mid + 1]
,直到mid = len - k
。
class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int left = 0;
int right = len - 1;
while (true) {
int mid = partition(nums, left, right);
if (mid == (len - k)) {
return nums[mid];
} else if (mid < (len - k)) {
left = mid + 1;
} else if (mid > (len - k)) {
right = mid - 1;
}
}
}
private Random random = new Random(47);
// 将 大于等于 pivot 的值放在 pivot 右边,小于 pivot 的放在 pivot 左边
private int partition(int[] nums, int left, int right) {
int randIndex = left + random.nextInt(right - left + 1);
swap(nums, left, randIndex);
int pivot = nums[left];
while (left < right) {
// 在右边找一个小于 pivot 的值填至 left
while (left < right && nums[right] >= pivot) {
right--;
}
if (left < right) {
swap(nums, left, right);
left++;
}
// 在左边找一个大于等于 pivot 的值填至 right
while (left < right && nums[left] < pivot) {
left++;
}
if (left < right) {
swap(nums, left, right);
right--;
}
}
nums[left] = pivot;
return left;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
221. 最大正方形
class Solution {
public int maximalSquare(char[][] matrix) {
int row = matrix.length;
int col = matrix[0].length;
// 以 matrix[i][j] 为右下角的正方形的最大边长
int[][] dp = new int[row][col];
int maxLen = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
//base case
if (i == 0 || j == 0) {
dp[i][j] = matrix[i][j] - '0';
// 转移方程:取 左、上、左上 三个dp值的最小值加1
} else if (matrix[i][j] == '1') {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
maxLen = Math.max(maxLen, dp[i][j]);
}
}
return maxLen * maxLen;
}
}
推荐题解:理解 三者取最小+1
226. 翻转二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return root;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
234. 回文链表
思路:快慢指针找到中点,反转后半部分,然后依次比较,最后要记得还原链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
//没有或只有一个结点
if (head == null || head.next == null) {
return true;
}
ListNode mid = getMidNode(head);
ListNode rHead = reverse(mid.next);
//切断
mid.next = null;
ListNode lTemp = head;
ListNode rTemp = rHead;
//右半部分链表长度小于等于左半部分链表长度
while (rTemp != null) {
if (lTemp.val != rTemp.val) {
return false;
}
lTemp = lTemp.next;
rTemp = rTemp.next;
}
//还原
mid.next = reverse(rHead);
return true;
}
//对于偶数个结点和奇数个结点,slow都位于右部分的前一个结点
private ListNode getMidNode(ListNode head) {
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
//反转链表
private ListNode reverse(ListNode head) {
ListNode pre = null, cur = head, next = head;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
- 推荐使用
slow = head;fast = head.next;
的方式使用快慢指针 - 该方式下,结点个数为奇数,slow指向中点;结点个数为偶数,slow指向左边中点
236. 二叉树的最近公共祖先
思路:后序遍历演变而来,若找到其中一个结点就自底向上返回。若p、q互不为对方子树中的结点,p、q最终会在某个结点相遇,该节点就为最近公共祖先;否则p或q即为最近公共结点。
class Solution {
//重要已知:p != q
// p 和 q 均存在于给定的二叉树中。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) {
return root;
}
return left != null ? left : right;
}
}
238. 除自身以外数组的乘积
如果元素中不含 0 就很简单:先遍历一遍数组求出所有元素的 总乘积,第二次遍历数组只需要用 总乘积 除以当前元素即可得到其他各元素的乘积。
思路:由于这里元素是可能为0的,那么可以通过两个备忘录,分别记录当前元素 左侧所有元素乘积 和 右侧所有元素乘积,然后两者对应坐标元素相乘即可。
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length;
int[] l_product = new int[len];
int[] r_product = new int[len];
// 左侧所有元素乘积
l_product[0] = 1;
for (int i = 1; i < len; i++) {
l_product[i] = nums[i - 1] * l_product[i - 1];
}
// 左侧所有元素乘积
r_product[len - 1] = 1;
for (int i = len - 2; i >= 0; i--) {
r_product[i] = nums[i + 1] * r_product[i + 1];
}
int[] res = new int[len];
for (int i = 0; i < len; i++) {
res[i] = l_product[i] * r_product[i];
}
return res;
}
}
由于题目进阶要求 常数空间复杂度(输出数组不被视为额外空间,也就是上面代码中的res
)。
思路:我们其实可以先只存储 左侧所有元素乘积:
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length;
int[] res = new int[len];
res[0] = 1;
for (int i = 1; i < len; i++) {
res[i] = nums[i - 1] * res[i - 1];
}
int r_product = 1;
for (int i = len - 1; i >= 0; i--) {
res[i] = res[i] * r_product;
r_product = nums[i] * r_product;
}
return res;
}
}
239. 滑动窗口最大值
求窗口中的最大值肯定很简单,但是如何在窗口向右滑动时(最左边元素出窗,最右边有新元素进窗)快速( O(n) )求出新的最大值?
思路:使用单调队列(由大到小)记录当前窗口中的最大值以及候选最大值
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int len = nums.length;
int[] maxs = new int[len - k + 1];
int j = 0;
for (int i = 0 ; i < len; i++) {
if (i < k - 1) {
offer(nums[i]);
} else {
offer(nums[i]);
maxs[j++] = max();
poll(nums[i - k + 1]);
}
}
return maxs;
}
private Deque<Integer> queue = new LinkedList<>();
//保证队列头始终是当前窗口中的最大值,后面是次大值(如果存在)
private void offer(int e) {
//将队列尾部小于新元素的候选值全部出队
while (!queue.isEmpty() && e > queue.peekLast()) {
queue.pollLast();
}
queue.offer(e);
}
//当 出窗口的元素是当前最大值时更新队列
private void poll(int e) {
if (e == queue.peek()) {
queue.poll();
}
}
private int max() {
return queue.peek();
}
}
推荐题解:单调队列解题详解
240. 搜索二维矩阵 II
思路:从右上角开始,target小于当前值则该列可以被排除,target大于当前值则该行可以被排除
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row = matrix.length;
int col = matrix[0].length;
int i = 0;
int j = col - 1;
while (i < row && j >= 0) {
if (target < matrix[i][j]) {
j--;
} else if (target > matrix[i][j]) {
i++;
} else if (target == matrix[i][j]) {
return true;
}
}
return false;
}
}