207.你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
入度表(广度优先遍历):
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] indegrees = new int[numCourses];
List<List<Integer>> adjacency = new ArrayList<>();
Queue<Integer> queue = new LinkedList<>();
for(int i = 0; i < numCourses; i++)
adjacency.add(new ArrayList<>());
//统计课程安排图中每个节点的入度(一对数里面的第一个数字),生成入度表
for(int[] cp : prerequisites) {
indegrees[cp[0]]++;
//第cp[1](一对数里面的第二个数字)个列表添加cp[0]
adjacency.get(cp[1]).add(cp[0]);
}
//借助一个队列queue,将所有入度为0的节点入队
for(int i = 0; i < numCourses; i++){
if(indegrees[i] == 0)
queue.add(i);
}
//BFS.(若课程安排图中存在环,则一定有节点的入度始终不为0)
while(!queue.isEmpty()) {
int pre = queue.poll();
numCourses--;
for(int cur : adjacency.get(pre)){
//如果减后入度为0则入队
if(--indegrees[cur] == 0)
queue.add(cur);
}
}
return numCourses == 0;
}
}
深度优先遍历:
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
//遍历每一条路径,看是否形成环
List<List<Integer>> adjacency = new ArrayList<>();
for(int i = 0; i < numCourses; i++)
adjacency.add(new ArrayList<>());
int[] flags = new int[numCourses];
for(int[] cp : prerequisites)
//第cp[1](一对数里面的第二个数字)个列表添加cp[0]
adjacency.get(cp[1]).add(cp[0]);
for(int i = 0; i < numCourses; i++){
if(!dfs(adjacency, flags, i))
return false;
}
//最终都为-1返回true
return true;
}
private boolean dfs(List<List<Integer>> adjacency, int[] flags, int i) {
if(flags[i] == 1) return false;//从正在访问中,到正在访问中,表示遇到了环
if(flags[i] == -1) return true;//表示在访问的过程中没有遇到环,这个节点访问过了
//走到这里,是因为初始化此时flags[i]==0
//表示正在访问中
flags[i] = 1;
for(Integer j : adjacency.get(i)){
if(!dfs(adjacency, flags, j))
return false;
}
flags[i] = -1;//只有一次DFS完整结束了,才能执行到这一步,标记为-1,说明这条路没问题,再遇到不需要遍历了
return true;
}
}
208.Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
Trie Tree 的实现 @路漫漫我不畏⭐
class Trie {
//二十六叉树
private Trie[] children;
private boolean isEnd;
public Trie() {
children = new Trie[26];//字母映射表
isEnd = false;//该结点是否是一个串的结束
}
public void insert(String word) {
Trie node = this;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
node.children[index] = new Trie();
}
node = node.children[index];
}
node.isEnd = true;
}
public boolean search(String word) {
Trie node = this;
for (char c : word.toCharArray()) {
node = node.children[c - 'a'];
if (node == null) {
return false;
}
}
return node.isEnd;
}
public boolean startsWith(String prefix) {
Trie node = this;
for (char c : prefix.toCharArray()) {
node = node.children[c - 'a'];
if (node == null) {
return false;
}
}
return true;
}
}
/**
* 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※.给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
partition 减治 + 优先队列@liweiwei1419⭐
基于快速排序的选择方法:
import java.util.Random;
public class Solution {
private static Random random = new Random(System.currentTimeMillis());
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
//转换一下,第 k 大元素的下标是 len - k
int target = len - k;
int left = 0;
int right = len - 1;
while (true) {
int index = partition(nums, left, right);
if (index < target) {
left = index + 1;
} else if (index > target) {
right = index - 1;
} else {
return nums[index];
}
}
}
//在 while (true) 循环中,通过 left 与 right 向中间靠拢的方式逐步缩小搜索区间
//对数组 nums 的子区间 [left..right] 执行 partition 操作,返回 nums[left] 排序以后应该在的位置
private int partition(int[] nums, int left, int right) {
//在区间随机选择一个元素作为标定点
if (right > left) {
int randomIndex = left + 1 + random.nextInt(right - left);
swap(nums, left, randomIndex);
}
int pivot = nums[left];
int j = left;
for (int i = left + 1; i <= right; i++) {
if (nums[i] < pivot) {
//j 的初值为 left,先右移,再交换,小于 pivot 的元素都被交换到前面
j++;
swap(nums, j, i);
}
}
//在之前遍历的过程中,满足 nums[left + 1..j] < pivot,并且 nums(j..i) >= pivot
swap(nums, left, j);
//交换以后 nums[left..j - 1] < pivot, nums[j] = pivot, nums[j + 1..right] >= pivot
return j;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
基于堆排序的选择方法:
手写大根堆:
class Solution {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
buildMaxHeap(nums, heapSize);
//建堆完毕后,nums【0】为最大元素。逐个删除堆顶元素,直到删除了k-1个。
for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
//先将堆的最后一个元素与堆顶元素交换,由于此时堆的性质被破坏,需对此时的根节点进行向下调整操作。
swap(nums, 0, i);
//相当于删除堆顶元素,此时长度变为nums.length-2。即下次循环的i
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a, int heapSize) {
//从最后一个父节点位置开始调整每一个节点的子树。数组长度为heasize,因此最后一个节点的位置为heapsize-1,所以父节点的位置为heapsize-1-1/2。
for (int i = (heapSize-2)/ 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
public void maxHeapify(int[] a, int i, int heapSize) { //调整当前结点和子节点的顺序。
//left和right表示当前父节点i的两个左右子节点。
int left = i * 2 + 1, right = i * 2 + 2, largest = i;
//如果左子点在数组内,且比当前父节点大,则将最大值的指针指向左子点。
if (left < heapSize && a[left] > a[largest]) {
largest = left;
}
//如果右子点在数组内,且比当前父节点大,则将最大值的指针指向右子点。
if (right < heapSize && a[right] > a[largest]) {
largest = right;
}
//如果最大值的指针不是父节点,则交换父节点和当前最大值指针指向的子节点。
if (largest != i) {
swap(a, i, largest);
//由于交换了父节点和子节点,因此可能对子节点的子树造成影响,所以对子节点的子树进行调整。
maxHeapify(a, largest, heapSize);
}
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
调库:
优先队列1:
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
for (int num : nums) {
heap.add(num);
if (heap.size() > k) {
heap.poll();
}
}
return heap.peek();
}
}
优先队列2:
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
// 使用一个含有 k 个元素的最小堆,PriorityQueue 底层是动态数组,为了防止数组扩容产生消耗,可以先指定数组的长度
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, Comparator.comparingInt(a -> a));
// Java 里没有 heapify ,因此我们逐个将前 k 个元素添加到 minHeap 里
for (int i = 0; i < k; i++) {
minHeap.offer(nums[i]);
}
for (int i = k; i < len; i++) {
// 看一眼,不拿出,因为有可能没有必要替换
Integer topElement = minHeap.peek();
// 只要当前遍历的元素比堆顶元素大,堆顶弹出,遍历的元素进去
if (nums[i] > topElement) {
// Java 没有 replace(),所以得先 poll() 出来,然后再放回去
minHeap.poll();
minHeap.offer(nums[i]);
}
}
return minHeap.peek();
}
}
221.在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。
动态规划:
class Solution {
public int maximalSquare(char[][] matrix) {
int maxSide = 0;
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return maxSide;
}
int rows = matrix.length, columns = matrix[0].length;
int[][] dp = new int[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (matrix[i][j] == '1') {
if (i == 0 || j == 0) {
dp[i][j] = 1;
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
maxSide = Math.max(maxSide, dp[i][j]);
}
}
}
int maxSquare = maxSide * maxSide;
return maxSquare;
}
}
226.给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
递归:
class Solution {
public TreeNode invertTree(TreeNode root) {
//递归函数的终止条件,节点为空时返回
if(root==null) {
return null;
}
//下面三句是将当前节点的左右子树交换
TreeNode tmp = root.right;
root.right = root.left;
root.left = tmp;
//递归交换当前节点的左子树
invertTree(root.left);
//递归交换当前节点的右子树
invertTree(root.right);
//函数返回时就表示当前这个节点以及它的左右子树都已经交换完了
return root;
}
}
234.给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
将值复制到数组中后用双指针法;递归:
回文链表⭐@力扣官方题解
快慢指针:(链表指针这一块还不太懂)
/**
* 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) {//没有或只有一个为true
return true;
}
//之所以要设置flag,是因为通过本题解法后,链表的前半部分结构被翻转了,为了不破坏原有结构,还要对其进行复原操作
boolean flag = true;
//快慢指针的起点均在head处
ListNode fast = head;
ListNode slow = head;
//保存在fast走完后,slow的上一个结点
ListNode pre = head;
//设置ppre指针,便于翻转
ListNode ppre = null;
while (fast != null && fast.next != null) {
pre = slow;
//快指针步长为 2,慢指针步长为 1
slow = slow.next;
fast = fast.next.next;
pre.next = ppre;
ppre = pre;
}
//当链表长度为偶数时,fast最终必为null,偶数slow为中间的第二个
//当链表长度为奇数时,fast.next为null,fast不为null,奇数slow为中间的一个
if (fast != null) {
slow = slow.next;
}
ListNode tempL = pre; //记录中间经过翻转的前部分指针
ListNode tempR = slow; //记录翻转部分的下一个结点
while (pre != null && slow != null) {
if (pre.val != slow.val) {
flag = false;
break;
}
pre = pre.next;
slow = slow.next;
}
//将已经翻转的链表复原
while (tempL != null) {
ListNode tempLL = tempL.next;//设置一个next域,即保存前一个结点
tempL.next = tempR;
tempR = tempL;
tempL = tempLL;
}
return flag;
}
}