刷题篇--热题HOT 31-40

75.颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:不能使用代码库中的排序函数来解决这道题。
示例:输入: [2,0,2,1,1,0]输出: [0,0,1,1,2,2]
分析:数组只有三个(常数个)元素,那么只要计算012的个数然后填入原数组。

 1 class Solution {
 2     public void sortColors(int[] nums) {
 3         int numOfZero = 0, numOfOne = 0, numOfTwo = 0;
 4         for(int i=0;i<nums.length;i++){
 5             if(nums[i]==0){
 6                 numOfZero++;
 7             }else if(nums[i]==1){
 8                 numOfOne++;
 9             }else{
10                 numOfTwo++;
11             }
12         }
13         int index = 0;
14         while(index < numOfZero){
15             nums[index] = 0;
16             index++;
17         }
18         while(index < numOfOne+numOfZero){
19             nums[index] = 1;
20             index++;
21         }
22         while(index < numOfTwo+numOfOne+numOfZero){
23             nums[index] = 2;
24             index++;
25         }
26     }
27 }

 法二:定义三个指针p0,p1,cur分别代表着p0索引左边全是0,p2索引右边全是2,p0~p2索引和之间全是1.所以要确定p0和p2的位置就可以了。

cur初始定义为0,当nums[cur]=2时,就交换cur和p2的元素,p2-1。当nums[cur] = 0时,交换cur和p0的元素,cur+1,p0+1。如果nums[cur]=1,则将cur右移。直到cur>p2.

 1 class Solution {
 2     public void sortColors(int[] nums) {
 3         int p0 = 0, p2 = nums.length-1, cur = 0;
 4         while(cur <= p2){
 5             if(nums[cur]==0){
 6                 swap(nums, cur, p0);
 7                 cur++;
 8                 p0++;
 9             }else if(nums[cur]==1){
10                 cur++;
11             }else{
12                 swap(nums, cur, p2);
13                 p2--;
14             }
15         }
16     }
17     public void swap(int[] nums, int i, int j){
18         int temp = nums[i];
19         nums[i] = nums[j];
20         nums[j] = temp;
21     } 
22 }

 

 76.最小覆盖字串

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例:输入: S = "ADOBECODEBANC", T = "ABC",输出: "BANC"
说明:如果 S 中不存这样的子串,则返回空字符串 ""。如果 S 中存在这样的子串,我们保证它是唯一的答案。
分析:此问题有俩个点:

  1.如何确定包含字符串T:使用HashMap,key为字符串T的字符,value是字符出现次数。滑动窗口right++时,如果S[right]是T的字符,则将value-1,滑动窗口left++时,如果如果S[left]是T的字符,则将value+1。如果HashMap所有的value都是0,则说明left~right的字串包含T。

  2.如何保证最小字串:使用滑动窗口(一般用于最多、最少连续等关键词),定义left、right指针,right用于扩展,left用于收缩。确定最小窗口,则为结果。

 1 class Solution {
 2     public String minWindow(String s, String t) {
 3         int left = 0, right = 0;//滑动窗口左右两索引
 4         int minLeft = 0, minRight = -1;//保存最小子串的左右两边索引
 5         int minLen = s.length();//默认最小字串长度
 6         Map<Character, Integer> map = new HashMap();
 7         //初始map
 8         for(int i=0;i<t.length();i++){
 9             char ch = t.charAt(i);
10             map.put(ch, map.getOrDefault(ch,0)+1);
11         }
12         //滑动窗口
13         while(right < s.length()){
14             char chOfRight = s.charAt(right);
15             //移动右指针,并判断是否已经包含T
16             if(map.containsKey(chOfRight)){
17                 //当前相同字符次数减一
18                 map.put(chOfRight,map.get(chOfRight)-1);
19                 //判断是否全包含T
20                 while(match(map)){
21                     //移动左指针
22                     char chOfLeft = s.charAt(left);
23                     //当前相同字符次数加一
24                     if(map.containsKey(chOfLeft)){
25                         map.put(chOfLeft,map.get(chOfLeft)+1);
26                     }
27                     //修改最小字串索引、长度
28                     if((right-left+1) <= minLen){
29                         minLen = right-left+1;
30                         minLeft = left;
31                         minRight = right;
32                     }
33                     left++;
34                 }
35             }
36             right++;
37         }
38         return s.substring(minLeft, minRight+1);
39     }
40     //判断map的value是否为空
41     public boolean match(Map<Character, Integer> map){
42         for(Integer value : map.values()){
43             if(value > 0){
44                 return false;
45             }
46         }
47         return true;
48     }
49 }

【优化】实践复杂度为O(mn),空间复杂度为O(m)。上面解法使用的是map存储T的字符次数。实际上可以使用一个int数组,索引是字符减去字符0之后的ascii值,数组值是出现的字符出现次数。那么如何判断T已经包含在left~right内呢?可以设置一个整数count = T.length(),每当right遍历到T中的字符,就减一,每当left遍历到就加一。代码此处省略

78.子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。
示例:输入: nums = [1,2,3];输出:[ [3],[1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
分析:类似全排列,只不过将全排列最后存储结果变为每小一步都存储结果。

结果输出为 [ [], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3] ]

 1 class Solution {
 2     List<List<Integer>> res = new ArrayList();
 3     public List<List<Integer>> subsets(int[] nums) {
 4         if(nums == null || nums.length == 0) return res;
 5         List<Integer> list = new ArrayList();
 6         helper(nums, 0, list);
 7         return res;
 8     }
 9     public void helper(int[] nums, int start, List<Integer> list){
10         res.add(new ArrayList(list));
11         for(int i = start ; i < nums.length ; i++){
12             list.add(nums[i]);
13             helper(nums, i+1, list);
14             list.remove(list.size()-1);
15         }
16     }
17 }

 

 79.单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]
给定 word = "ABCCED", 返回 true.
给定 word = "SEE", 返回 true.
给定 word = "ABCB", 返回 false.
分析:回溯+递归

 1 class Solution {
 2     public boolean exist(char[][] board, String word) {
 3         int index = 0;
 4         int m = board.length;
 5         int n = board[0].length;
 6         boolean[][] visited = new boolean[m][n];
 7         for(int i=0;i<m;i++){
 8             for( int j=0;j<n;j++){
 9                 if(board[i][j] == word.charAt(index)){
10                     boolean res = isExist(board, index, word, i, j, visited);
11                     if(res) return true;
12                 }
13             }
14         }
15         return false;
16     }
17     public boolean isExist(char[][] board, int index, String word, int i, int j, boolean[][] visited){
18         if(index == word.length()) return true;
19         if(i<0 || i >= board.length || j<0 || j>=board[0].length || visited[i][j] || board[i][j] != word.charAt(index)){
20             return false;
21         } 
22         visited[i][j] = true;
23         if(isExist(board, index+1, word, i-1, j, visited)||
24            isExist(board, index+1, word, i+1, j, visited)||
25            isExist(board, index+1, word, i, j-1, visited)||
26            isExist(board, index+1, word, i, j+1, visited))
27         {
28             return true;        
29         }
30         visited[i][j] = false;//回溯
31         return false;
32     }
33 }

 

 84.柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3] 

 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

 法一:分治,最大矩形面积取决于最低的柱子高度和两边之间的距离。先寻找出最低的柱子,以此柱子为界,再计算左右两边,取出最低柱子和左右边面积最大值。递归。

 1 class Solution {
 2     public int largestRectangleArea(int[] heights) {
 3         return calculateArea(heights, 0, heights.length-1);
 4     }
 5     public int calculateArea(int[] heights, int start, int end){
 6         if(start > end) return 0;
 7         //计算最小值
 8         int minHeightsIndex = start;
 9         for(int i = start; i <= end; i++){
10             if(heights[i] < heights[minHeightsIndex]){
11                 minHeightsIndex = i;
12             }
13         }
14         //返回计算三个面积的最大值
15         return Math.max(heights[minHeightsIndex]*(end-start+1),
16                Math.max(calculateArea(heights,start,minHeightsIndex-1),calculateArea(heights,minHeightsIndex+1,end)));
17     }
18 }

 法二:包含当前柱子的最大矩形示意图如下:

 

 图解:

①先将2存入栈内,左边沿的左边是-1;

② 1小于2,那么2的左边沿和右边沿都能确定了,计算2的最大矩形面积,压入1;

③ 5大于1,则5的左边沿能够确定,右边沿未知,则压入5;

④ 6和5同样操作;

⑤ 2小于6,那么6的左边沿和右边沿能够确定,则计算面积并弹出,继续比较,2小于5,则5的左右边沿也能够确定,同6操作,继续比较,2大于1,压入2;

⑥ 3大于 2,直接压入;

⑦处理栈内剩下元素,这些元素当初是因为右边沿无法确定而一直存在栈内的,但是数组遍历完之后,这些栈内元素的右边沿都是数组的长度,左边沿是左边相邻的元素。【这里的压入弹出的数据时数组索引,而不是值】

  只需要计算上图中的六个面积的最大值即为答案。那面积如何求得呢?通过观察发现,矩形的左边沿是左边第一个小于本柱子的右边,右边沿是右边第一个小于本柱子的左边

  那么如何寻找呢?使用单调栈,栈内元素从小到大排列,这也就保证可以存储到左边沿。

  那么右边沿如何判断呢?如果遍历到的柱子高度低于栈顶元素,也就是说明以栈顶元素为核心的矩形找到了,遍历到的柱子就是右边沿右边一个位置。当然,这个遍历到的柱子可能是很多柱子的右边沿的右边,需要while循环。这样就可以得知左边沿()和右边沿了,只要在弹出过程中保存最大面积即可。

  遍历结束时如果最后栈内剩下元素,那么一定是递增的,右边沿是数组长度,例如上例就会剩下 1  4 5分别代表高度1 2 3,此时再使用一个while循环,不断计算面积保存最大值。

 1 class Solution {
 2     public int largestRectangleArea(int[] heights) {
 3         Stack<Integer> stack = new Stack();
 4         stack.push(-1);
 5         int maxArea = 0;
 6 
 7         //遍历
 8         for(int i=0; i < heights.length; i++){
 9             //左边沿位置 stack.pop().peek(),右边沿位置i-1
10             while(stack.peek() != -1 && heights[i] <= heights[stack.peek()]){
11                 maxArea = Math.max(maxArea, heights[stack.pop()]*(i-1-stack.peek()));
12             }
13             //不管有没有上述操作都要压入
14             stack.push(i);
15         }
16 
17         //处理剩下的数据,右边沿是heights.length-1,即最后一个元素
18         while(stack.peek() != -1){
19             maxArea = Math.max(maxArea, heights[stack.pop()]*(heights.length-stack.peek()-1));
20         }
21 
22         return maxArea;
23     }
24 }

 85.最大矩形

给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例:输入:
[
  ["1","0","1","0","0"],
  ["1","0","1","1","1"],
  ["1","1","1","1","1"],
  ["1","0","0","1","0"]
]       输出: 6

分析:面积如何计算?矩形左上角位置和右下角位置索引计算。

将本题转换为84题。

① 利用一维数组存储从上往下,以每行为底的每列高度;

②遍历matrix每一行,利用84题中栈方法更新最大面积;

 1 class Solution {
 2     public int maximalRectangle(char[][] matrix) {
 3         if(matrix.length == 0) return 0;
 4         int maxArea = 0;
 5         int[] heights = new int[matrix[0].length];
 6         //利用matrix数组存储从上往下,以每行为底的每列高度;
 7         for(int i=0;i<matrix.length;i++){
 8             for(int j=0;j<matrix[0].length;j++){
 9                 heights[j] = matrix[i][j] == '1' ? heights[j] + 1 : 0;
10             }
11             maxArea = Math.max(maxArea, largestRectangleArea84(heights));
12         }
13         return maxArea;
14     }
15     public int largestRectangleArea84(int[] heights) {
16           Stack<Integer> stack = new Stack();
17           stack.push(-1);
18           int maxArea = 0;
19           //遍历
20           for(int i=0; i < heights.length; i++){
21              //左边沿位置 stack.pop().peek(),右边沿位置i-1
22             while(stack.peek() != -1 && heights[i] <= heights[stack.peek()]){
23                  maxArea = Math.max(maxArea, heights[stack.pop()]*(i-1-stack.peek()));
24              }
25              //不管有没有上述操作都要压入
26              stack.push(i);
27          }
28          //处理剩下的数据,右边沿是heights.length-1,即最后一个元素
29          while(stack.peek() != -1){
30              maxArea = Math.max(maxArea, heights[stack.pop()]*(heights.length-stack.peek()-1));
31          }
32          return maxArea;
33      }
34 }

94. 二叉树的中序遍历

递归

 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     
12     public List<Integer> inorderTraversal(TreeNode root) {
13         ArrayList<Integer> res = new ArrayList();
14         helper(root,res);
15         return res;
16     }
17     public void helper(TreeNode root, List<Integer> res){
18         if(root == null) return;
19         helper(root.left, res);
20         res.add(root.val);
21         helper(root.right, res);
22     }
23 }

 非递归

 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 List<Integer> inorderTraversal(TreeNode root) {
12         ArrayList<Integer> res = new ArrayList();
13         Stack<TreeNode> stack = new Stack();
14         TreeNode cur = root;
15         while(cur != null || !stack.empty()){
16             while(cur!=null){
17                 stack.push(cur);
18                 cur = cur.left;
19             }
20             cur = stack.pop();
21             res.add(cur.val);
22             cur = cur.right;
23         }
24         return res;
25     }
26 }

 97.不同的二叉搜索树

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

 示例:输入: 3输出: 5
解释:给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1          3     3      2      1
             /       /      /        
     3     2     1      1    3      2
    /      /                             
   2     1          2                     3

分析:看了解析,发现数个数学归纳题。1…n数列,如果取中间i为根节点,则1…i-1,i+1…n分别为左子树和右子树。

 设置两个函数:

  G(n):长度为n的序列的不同二叉搜索树的个数。

     F(i,n):以i为根的不同二叉搜索树的个数。

易知:G(n) = F(1,n)+F(2,n)+F(3,n)+…+F(n-1,n)+F(n,n),G(0) = 1 , G(1) = 1;

      F(i,n) = G(i-1)*G(n-i)

综上:G(n) = ΣG(i-1)*G(n-i),i=1,2,...,n。例如G(4) = G(0)*G(4) + G(1)*G(3) + G(2)*G(2) + G(3)*G(1)

 1 class Solution {
 2     public int numTrees(int n) {
 3         int[] dp = new int[n+1];
 4         dp[0] = 1;
 5         dp[1] = 1;
 6         for(int i = 2 ; i <= n ; i++){
 7             for(int j=1;j<=i;j++){
 8                 dp[i] += dp[j-1]*dp[i-j]; 
 9             } 
10         }
11         return dp[n];
12     }
13 }

实际上,G(n)函数是卡塔兰数 G(n+1) = (2(2n+1)/(n+2))*G(n)

1 class Solution {
2     public int numTrees(int n) {
3         long res = 1;
4         for(int i=0;i<n;i++){
5             res = res*2*(2*i+1)/(i+2); 
6         }
7         return (int)res;
8     }
9 }

98.验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:节点的左子树只包含小于当前节点的数;节点的右子树只包含大于当前节点的数; 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
    2
   /
  1   3
输出: true
示例 2:
输入:
    5
   /
  1   4
     /
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。

分析:一个合格的搜索二叉树的中序遍历一定是一个单调递增的数列,例如示例一:123√,示例二:15346×。

   5
   /
  1   6
       /
     4   7

再例如上例:15467×。因此在中序遍历的时候,存储最后一个遍历的节点值temp,判断下一个遍历的节点值是否大于temp,如果大于,则继续遍历,直至遍历完。如果小于则返回false。

 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 boolean isValidBST(TreeNode root) {
12         Stack<TreeNode> stack = new Stack();
13         double temp = -Double.MAX_VALUE;
14         TreeNode cur = root;
15         while(cur != null || !stack.empty()){
16             while(cur!=null){
17                 stack.push(cur);
18                 cur = cur.left;
19             }
20             cur = stack.pop();
21             if(cur.val <= temp) return false;
22             temp = cur.val;
23             cur = cur.right;
24         }
25         return true;
26     }
27 }

101.对称二叉树

给定一个二叉树,检查它是否是镜像对称的。例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

     1
   /   
  2     2
 /     /
3  4  4  3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
    1
   /
  2   2
      
   3    3
递归

 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 boolean isSymmetric(TreeNode root) {
12         return helper(root, root);
13     }
14     public boolean helper(TreeNode left, TreeNode right){
15         if(left==null&&right==null){
16             return true;
17         }else if(left == null || right == null || left.val != right.val){
18             return false;
19         }else{
20             return helper(left.left,right.right) && helper(left.right,right.left);
21         }
22     }
23 }

//迭代-类似于层序遍历,使用队列

 1 class Solution {
 2     public boolean isSymmetric(TreeNode root) {
 3         Queue<TreeNode> queue = new LinkedList();
 4         queue.add(root);
 5         queue.add(root);
 6         while(!queue.isEmpty()){
 7             TreeNode node1 = queue.poll();
 8             TreeNode node2 = queue.poll();
 9             if(node1 == null && node2 == null) continue;
10             if(node1 == null || node2 == null || node1.val != node2.val) return false;
11             queue.add(node1.left);
12             queue.add(node2.right);
13             queue.add(node1.right);
14             queue.add(node2.left);
15         }
16         return true;
17     }
18 }

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/qmillet/p/12144717.html