刷题篇--热题HOT 61-70

207.课程表

现在你总共有 n 门课需要选,记为 0 到 n-1。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:输入: 2, [[1,0]],输出: true,解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的

示例 2:输入: 2, [[1,0],[0,1]]输出: false,解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

分析:这个问题相当于查找一个循环是否存在于有向图中,可使用BFS/DFS,本体先使用BFS,易于理解。如何判断一门课程能够学习--这门课程不用先修课程,或者先修课程已经完成,我们将已经修完的课程直接抛弃参与以后的讨论。一门课程的先修课程数即使‘’入度‘,当入度为0时,就说明这门课程可以完成,所以我们首先创建一个degree数组存储每门课程的入度。当一个有向图中不存在环,那么刚开始时肯定有一个入度为0,即没有先修课程的课程。我们以此课程进行广度遍历。没遍历到一门课程,那么说明此条路径可达,那么这门课程的入度减一,如果入度变为0,说明此课程不在环内,待完成课程数减一。最后查看待完成课程是否为0.如何实现遍历呢,使用队列,先进先出

实例:degree[] = {0 ,0 ,1, 1, 2, 1}

以课程1为出发点遍历有序图,将课程1放入队列。此时栈内:1

 

出栈,遍历prerequisites[][],发现课程2的先修课程是1,那么就将degree[2] -1,此时degree[2]=0,那么说明2入度为0,可完成,入栈。此时栈内:2

出栈,遍历prerequisites[][],发现课程3的先修课程是2,那么就将degree[3 ]-1,此时degree[3]=0,那么说明3入度为0,可完成,入栈。此时栈内:3

(此处不用出栈,因为还在以课程2为入度的循环内)遍历prerequisites[][],发现课程4的先修课程是2,那么就将degree[4] -1,此时degree[4]=1,那么说明4入度为0,可完成,入栈。此时栈内:3,4

出栈,遍历prerequisites[][],发现课程5的先修课程是3,那么就将degree[5 ]-1,此时degree[5]=1,那么说明5入度为1,不可完成。此时栈内:4

出栈,遍历prerequisites[][],发现课程5的先修课程是4,那么就将degree[5 ]-1,此时degree[5]=0,那么说明5入度为0,可完成。入栈,此时栈内:5

出栈,遍历prerequisites[][],发现没有以5为先修课程的,那么结束循环。此时队列也为空,那么结束循环。判断此时待修课程是否为0;

 1 class Solution {
 2     public boolean canFinish(int numCourses, int[][] prerequisites) {
 3         //计算入度数组
 4         int[] degree = new int[numCourses];
 5         for(int[] cp : prerequisites){
 6             degree[cp[0]]++;
 7         }
 8         //存放入度为0课程
 9         LinkedList<Integer> queue = new LinkedList();
10         //找出入度为0的课程
11         for(int i=0;i<numCourses;i++){
12             if(degree[i]==0) queue.addLast(i);
13         }
14         //找到下一个入度为0的
15         while(!queue.isEmpty()){
16             //出栈,能够完成的课程
17             Integer pre = queue.removeFirst();
18             numCourses--;
19             for(int[] req : prerequisites){
20                 if(req[1] != pre) continue;
21                 degree[req[0]]--;
22                 if(degree[req[0]] == 0) queue.addLast(req[0]);
23             }
24         }
25         return numCourses==0;
26     }
27 }

208.实现Trie(前缀树)

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true

分析:Trie根节点不存入值

1.Trie节点结构:一个int[26]数组来存储字符值,一个boolean类型is_end判断是否以该字符结尾。下图表示leet在树中的表示。

 2.插入:插入一个值,从根部开始搜索字符值,存在两种情况:

    链接存在:继续向下搜索

    链接不存在:创建新节点,直到达到插入值最后一个字符,将末尾字符is_end设置为true

3.查找:

 4.前缀

 1 class TrieNode{
 2     TrieNode[] chiled;//记录孩子节点
 3     boolean is_end;
 4     TrieNode(){
 5         chiled = new TrieNode[26];//一个孩子节点有2266个分支,即一层树有26各分支
 6         is_end = false;
 7     }
 8 }
 9 class Trie {
10     TrieNode root;//根节点,不存值
11     /** Initialize your data structure here. */
12     public Trie() {
13         root = new TrieNode();
14     }
15     
16     /** Inserts a word into the trie. */
17     public void insert(String word) {
18         TrieNode node = root;
19         for(int i=0;i<word.length();i++){
20             char c = word.charAt(i);
21             if(node.chiled[c-'a']==null){//如果该层该字符位置为空,则新建一个
22                 node.chiled[c-'a'] = new TrieNode();
23             }
24             node = node.chiled[c-'a'];//指向下一层
25         }
26         node.is_end = true;//字符串最后一个字符那层的位置is_end=true
27     }
28     
29     /** Returns if the word is in the trie. */
30     public boolean search(String word) {
31         TrieNode node = root;
32         for(int i=0;i<word.length();i++){
33             char c= word.charAt(i);
34             if(node.chiled[c-'a'] == null){//判断是否存在
35                 return false;
36             }
37             node = node.chiled[c-'a'];
38         }
39         return node.is_end;//判断结尾
40     }
41     
42     /** Returns if there is any word in the trie that starts with the given prefix. */
43     /**
44     *serach和startWith区别在于是否判断结尾is_end,startWith是只要字符存在就行
45     */
46     public boolean startsWith(String prefix) {
47         TrieNode node = root;
48         for(int i=0;i<prefix.length();i++){
49             char c= prefix.charAt(i);
50             if(node.chiled[c-'a'] == null){//判断是否存在
51                 return false;
52             }
53             node = node.chiled[c-'a'];
54         }
55         return true;
56     }
57 }
58 
59 /**
60  * Your Trie object will be instantiated and called as such:
61  * Trie obj = new Trie();
62  * obj.insert(word);
63  * boolean param_2 = obj.search(word);
64  * boolean param_3 = obj.startsWith(prefix);
65  */

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

示例 1:输入: [3,2,1,5,6,4] 和 k = 2,输出: 5
示例 2:输入: [3,2,3,1,2,4,5,5,6] 和 k = 4,输出: 4
分析:排序,输出nums[length-K],快排O(NlogN);复杂度不咋地,看了题解使用堆排序,维持一个大小为K的小顶堆,那么堆顶就是答案,如果大于堆顶且堆满就加入,反之不操作,复杂度为O(NlogK)。

 1 class Solution {
 2     public int findKthLargest(int[] nums, int k) {
 3         //利用最小堆,创建k+1的size,遍历数组进行添加,并poll一个
 4         PriorityQueue<Integer> heap = new PriorityQueue(k+1);
 5         for(int num : nums){
 6             heap.add(num);
 7             if(heap.size() > k){
 8                 heap.poll();
 9             }
10         }
11         return heap.poll();
12     }
13 }

221.最大正方形

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

示例:

输入: 

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

输出: 4

分析:动态规划,建立一个二维数组dp[i][j],在左上角矩阵中由matrix[i-1][j-1]参与构成的最大正方形边长。

如果matrix[i-1][j-1]='1'  =>  dp[i][j] = min{dp[i-1][j], dp[i-1][j-1], dp[i][j-1] } +1。否则 dp[i][j]=0。

 1 class Solution {
 2     public int maximalSquare(char[][] matrix) {
 3         if(matrix.length == 0) return 0;
 4         int rl = matrix.length;
 5         int cl = matrix[0].length;
 6         int[][] dp = new int[rl+1][cl+1];
 7         int maxLen = 0;
 8         for(int i=1;i<=rl;i++){
 9             for(int j=1;j<=cl;j++){
10                 if(matrix[i-1][j-1] == '1'){
11                     dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j],dp[i][j-1]))+1;
12                     maxLen = Math.max(maxLen, dp[i][j]);
13                 }
14             }
15         }
16         return maxLen*maxLen;
17     }
18 }

226.翻转二叉树

示例:

输入:

     4
   /   \
  2     7
 / \      / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \      / \
9   6 3   1

分析:递归

 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode(int x) { val = x; }
 8  * }
 9  */
10 class Solution {
11     public TreeNode invertTree(TreeNode root) {
12         if(root == null){
13             return root;
14         }
15         TreeNode leftNode = invertTree(root.left);
16         TreeNode rightNode = invertTree(root.right);
17         root.left = rightNode;
18         root.right = leftNode;
19         return root;
20     }
21 }

迭代

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.poll();
            TreeNode temp = node.left;
            node.left = node.right;
            node.right = temp;
            if(node.left!=null) queue.add(node.left);
            if(node.right!=null) queue.add(node.right);
        }
        return root;
    }
}

234.回文链表

请判断一个链表是否为回文链表。
示例 1:输入: 1->2输出: false
示例 2:输入: 1->2->2->1输出: true
分析:1.快慢指针,指向中间。2.将前半段倒序。3.遍历比较

 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public boolean isPalindrome(ListNode head) {
11         if(head == null || head.next == null) return true;
12         ListNode cur = head;
13         ListNode slow = head, fast = head.next, pre = null, prepre = null;
14         while(fast!=null&&fast.next!=null){
15             pre = slow;//先保存slow
16             slow = slow.next;
17             fast = fast.next.next;
18             //先移动再反转
19             pre.next = prepre;
20             prepre = pre;
21         }
22         ListNode p2 = slow.next;//右边
23         slow.next = pre;
24         ListNode p1 = fast == null ? slow.next : slow;//如果是奇数个,则跳过中间节点
25         while(p1 != null){
26             if(p1.val != p2.val) 
27                 return false;
28             p1 = p1.next;
29             p2 = p2.next;
30         }
31         return true;
32     }
33 }

236.二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1;输出: 3,解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4;输出: 5,解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
分析:递归,从根节点开始。如果存在公共祖先,所以要么是根节点,要么在左子树或右子树。如果根节点已经包含待求值中的某一值,则根节点一定是最早公共祖先(一个节点先遍历到,另一个节点是其祖先)。遍历过程中,存在三种情况:

- 如果左子树中没有待求值,但右子树中返回了某一节点值,则可判断右子树中存在公共子节点,而递归操作就可以达到查找到最早公共子节点的作用

- 如果左子树中返回每一节点,而右子树中为空,与上述情况相同

- 如果左右子树均有返回节点,则可判断左右子树中应分别有一个待求节点,故此时的根节点一定是最早公共祖先。

 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode(int x) { val = x; }
 8  * }
 9  */
10 class Solution {
11     public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
12         if(root == null || root == p || root == q){
13             return root;
14         }
15         //根据当前节点寻找左右子树
16         TreeNode left = lowestCommonAncestor(root.left,p,q);
17         TreeNode right = lowestCommonAncestor(root.right,p,q);
18         //如果左子树有一个,右子树也找到一个,则返回当前节点
19         if(left!=null&&right!=null){
20             return root;
21         }
22         //左子树没有,那么去右子树找
23         if(left == null) return right;
24         //与上述相反
25         if(right == null) return left;
26         return null;
27     }
28 }

238.除自身以外数组的乘积

给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:输入: [1,2,3,4],输出: [24,12,8,6]
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。

分析:暴力法不可取,O(N^2)。乘积 = 当前数左边乘积 * 当前数右边乘积。设置一数组,一次从头到尾遍历原数组存入左边乘值,一次从未到头遍历原数组存入右边乘值。例如:[1,2,3,4]

从前往后:[1,1*1, 2*1, 2*3]

从后往前:[1*2*3*4,1*1 * 3*4,2*1 * 4,2*3]

 1 class Solution {
 2     public int[] productExceptSelf(int[] nums) {
 3         int[] res = new int[nums.length];
 4         int k = 1;
 5         for(int i=0;i<nums.length;i++){
 6             res[i] = k;//[1,1,2,6]
 7             k *= nums[i];//[1,2,6,24]给下次乘值使用,这样就是i左边的乘值
 8         }
 9         k = 1;
10         for(int i=nums.length-1;i>=0;i--){
11             res[i] *= k;//[24,12,8,6]
12             k *= nums[i];//[24,24,12,4] i右边的乘值 
13         }
14         return res;
15     }
16 }

239.滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,输出: [3,3,5,5,6,7]
解释:
  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

提示:你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
分析:双向链表:剑指Offer

Step1.使用一个双端队列存储滑窗内元素的下标。

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

queue

     

 

Step2.若queue为空,说明刚开始滑动,将num第一个元素的索引压入队列中。

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

 queue

 0    

 

Step3.遍历num下一个元素num[1]=3,

  ①先判断当前索引位置减去队列最左边元素是否>=size,如果是,那么说明随着滑窗滑动,当前滑窗内的最大值滑出去了,已经不再这个滑窗内,这是需要将队列最左边元素弹出。(队列最左边为滑窗内最大值索引,判断是否有效)

  ②将当前元素与队列最右边的索引处的数组值对比,如果当前元素大于队列最右边对应的值,则将队列最右边的值弹出,直到找到对应值比当前元素值大的。(队列左边是最大的值,随着上述操作,会逐渐更新队列,滑出去或者新添加的值更大的话将会更新队列,保证队头对应值最大)

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

 

 1    

 

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

 

2    

---输出4

 

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

 

 2 3  

---输出4

 

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

 

4    

---输出6

 

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

 

4 5  

---输出6

 

value 2 3 4 2 6 2 5
index 0 1 2 3 4 5 6

 

4  5  

---输出6

 

value 2 3 4 2 6 2 5 1
index 0 1 2 3 4 5 6 7

 --当前索引处位置(7)-队列最左边(4)=size(3)--》滑出

 4 5 7

 --

5 7  

 --输出5

 补充:队头是存储最大值的索引,队头被弹出的情况:新来的比队内所有数都大、队头不在当前子数组中。新来成员时,判断是否有老成员太老了和新成员是否比其他老成员大(循环比,大一个弹一个,直到队列为空或者比老成员小)。队内情况:索引由小到大,因为位于当前元素前面并且小的被淘汰了。

 1 class Solution {
 2     public int[] maxSlidingWindow(int[] nums, int k) {
 3         if(nums==null||nums.length==0) return nums;
 4         int len = nums.length;
 5         int[] res = new int[len-k+1];
 6         int index = 0;
 7         ArrayDeque<Integer> queue = new ArrayDeque<Integer>();//队列存储的是索引值
 8         for(int i=0;i<nums.length;i++){
 9             if(queue.isEmpty()){
10                 queue.add(i);//刚开始时
11             }
12             if(i-queue.peekFirst()>=k){
13                 queue.pollFirst();//将太老的老成员提出
14             }
15             while(!queue.isEmpty() && nums[i]>=nums[queue.peekLast()]){
16                 queue.pollLast();
17             }
18             queue.addLast(i);
19             if(i>=k-1){//k-1个元素开始,就要输出了
20                 res[index++] = nums[queue.peekFirst()];
21             }
22         }
23         return res;
24     }
25 }

240.搜索二维矩阵Ⅱ

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。
示例:
现有矩阵 matrix 如下:
[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。给定 target = 20,返回 false。
分析:矩阵特点:左上角最小,右下角最大。本题从左下角遍历,如果大于当前元素,则向右寻找,反之,向上寻找。复杂度O(n+m)。

 1 class Solution {
 2     public boolean searchMatrix(int[][] matrix, int target) {
 3         if(matrix == null || matrix.length ==0 || matrix[0].length == 0){
 4             return false;
 5         }
 6         int m = matrix.length;
 7         int n = matrix[0].length;
 8         int row = m-1, col = 0;
 9         while(row >= 0 && col <= n-1){
10             if(matrix[row][col] == target){
11                 return true;
12             }else if(matrix[row][col] < target){
13                 col++;
14             }else{
15                 row--;
16             }
17         }
18         return false;
19     }
20 }

猜你喜欢

转载自www.cnblogs.com/qmillet/p/12161319.html