刷题篇--热题HOT 21-30

46.全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:输入: [1,2,3]输出:[ [1,2,3], [1,3,2]  [2,1,3],  [2,3,1],  [3,1,2],  [3,2,1]]

 

 



 

 

 

 

 

 

 

 

 1 class Solution {
 2 
 3     List<List<Integer>> res = new ArrayList();
 4 
 5     public List<List<Integer>> permute(int[] nums) {
 6         if(nums == null || nums.length == 0) return res;
 7         permutation(nums, 0);
 8         return res;
 9     }
10     public void permutation(int[] nums, int i){
11         if(i == nums.length-1){
12             ArrayList<Integer> list = new ArrayList();
13             for(int num : nums){
14                 list.add(num);
15             }
16             res.add(list);
17         }else{
18             for(int j = i; j < nums.length; j++){
19                 swap(nums, i, j);
20                 permutation(nums, i+1);
21                 swap(nums, i, j);
22             }
23         }
24     }
25     public void swap(int[] nums, int i, int j){
26         int temp = nums[i];
27         nums[i] = nums[j];
28         nums[j] = temp;
29     }
30 }

 

48.旋转图像

给定一个 n × n 的二维矩阵表示一个图像。将图像顺时针旋转 90 度。说明:你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

分析:转置+翻转

class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        //转置
        for(int i=0; i < n; i++){
            for(int j=i; j < n; j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
        //翻转
        for(int i=0; i<n; i++){
            for(int j=0; j < n/2; j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[i][n-1-j];
                matrix[i][n-1-j] = temp;
            }
        }
    }
}

 

 

49.字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:输入: ["eat", "tea", "ate", "nat", "bat"],输出:[ ["ate","eat","tea"], ["nat","tan"], ["bat"]]
说明:所有输入均为小写字母。 不考虑答案输出的顺序。
分析:字母异位词字母相同,若将字符转换成数组进行排序,则他们的排序结果相同。使用一个HashMap保存数据,key是排序字符串,value是一个列表,存储字母异位词。

 1 class Solution {
 2     public List<List<String>> groupAnagrams(String[] strs) {
 3         if(strs == null || strs.length == 0) return new ArrayList();
 4         Map<String,List> map = new HashMap();
 5         for(String str : strs){
 6             char[] ch = str.toCharArray();
 7             Arrays.sort(ch);
 8             String key = String.valueOf(ch);//排序后的字符串
 9             if(!map.containsKey(key)){
10                 map.put(key,new ArrayList());
11             }
12             map.get(key).add(str);
13         }
14         return new ArrayList(map.values());
15     }
16 }

 

 

53.最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和

示例:输入: [-2,1,-3,4,-1,2,1,-5,4],输出:解释:  连续子数组 [4,-1,2,1] 的和最大,为 6。

分析:贪心:每一步做到最好,然后找出整个数组中最优值。先维护一个当前位置和最大值curMax,和一个当前位置和当前位置之前的和最大值allMax。curMax的取值通过当前位置加上或者不加上前面取值(取较大值,且当前位置的值必包含在和内)来得到。时间复杂度O(N) 空间复杂度 O(1)

 1 class Solution {
 2     public int maxSubArray(int[] nums) {
 3         int curMax = nums[0], allMax = nums[0];
 4         for(int i=1; i < nums.length; i++){
 5             curMax = Math.max(nums[i], curMax + nums[i]);
 6             allMax = Math.max(curMax, allMax);
 7         }
 8         return allMax;
 9     }
10 }

 

 

 

55.跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。

示例 1:输入: [2,3,1,1,4]输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:输入: [3,2,1,0,4]输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

方法一:递归——所有可能  (超时了……),一个位置判断为不行之后,之前的位置在遍历时还会到这个位置,重复判断。时间复杂度O(2^n)

 1 class Solution {
 2     public boolean canJump(int[] nums) {
 3         return testJump(nums, 0);
 4     }
 5 
 6     public boolean testJump(int[] nums, int index){
 7         if(index > nums.length) return false;
 8         if(index == nums.length-1) return true;
 9         for(int i = 1; i <= nums[index]; i++){//可能的跳数
10             if(testJump(nums, index + i)){
11                 return true;
12             }
13         }
14         return false;
15     }
16 }

 

方法二:设置一个数组,有三个值-1,0,1。‘-1’表示该位置不能到达终点,‘0’表示初始未知,‘1’表示从该位置出发可以到达终点。为了重复判断,采取从右到左遍历方式。时间复杂度O(N^2) 空间复杂度 O(N)

 1 class Solution {
 2     public boolean canJump(int[] nums) {
 3         int len = nums.length;
 4         int[] standard = new int[len];//默认是0
 5         //最后一个位置肯定是1
 6         standard[len-1] = 1;
 7         //从右往左遍历
 8         for(int i = len-2; i>=0; i--){
 9             //当前位置有nums[i]种跳法
10             for(int jump = 1; jump <= nums[i] && i + jump < len; jump++){
11                 if(standard[i+jump] == 1){
12                     standard[i] = 1;
13                     break;
14                 }
15             }
16         }
17         return standard[0] == 1;
18     }
19 }

方法三:贪心,位置 i 能够跳到另一个位置 j 则需要条件:i + nums[i] >= j。根据这一条件我们可以从右往左不断寻找满足这样条件的位置,例如

[9,4,2,1,0,2,0],最后一个位置nums[6]=0,目的索引为i=6,从右往左寻找,发现 5 + nums[5] = 7 > 6,所以能够从i=5位置跳到i=6,此时将目的位置设置为i=5。再次寻找,发现 0 + nums[0] = 9 > 7,即能够从i=0位置跳到i=5,此时将目的位置设置为i=0,结束!虽然从i=0也能直接跳到i=6,但是只要有一种跳法就可以,如果一个点能够忽略中间“跳板”跳到终点,那么这个点肯定也能够通过“跳板”跳到终点,本方法采用的是贪心算法,尽可能多的跳。时间复杂度O(N) 空间复杂度 O(1)

 1 class Solution {
 2     public boolean canJump(int[] nums) {
 3         int len = nums.length;
 4         int destination = len - 1;
 5         for(int i = len - 2; i >= 0; i--){
 6             destination  = (i + nums[i]) >= destination ? i : destination;
 7         }
 8         return destination == 0;
 9     }
10 }

 

 56.合并区间

给出一个区间的集合,请合并所有重叠的区间。(二维数组是n行2列)
示例 1:输入: [[1,3],[2,6],[8,10],[15,18]],输出: [[1,6],[8,10],[15,18]],解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:输入: [[1,4],[4,5]],输出: [[1,5]],解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

分析:什么样的区间需要合并?例如[a,b]、[c,d],如果 c<=b<=d则两个区间有重合部分,需要合并。那合并后的区间应该是什么样的呢? 合并后的区间应该是 [ min(a,c) , max(b,d) ]。此题应该先将二维数组进行排序,避免合并区间进行多次循环,那么此时合并的条件就变成 !(c>b)。合并后的区间为[a , max(b,d)]。因为合并区间需要存入存出并且不知道合并后的区间个数,因此采用一个LinkedList临时存储合并后的区间,最后使用toArray返回结果。

 1 class Solution {
 2     public int[][] merge(int[][] intervals) {
 3         LinkedList<int[]> list = new LinkedList();
 4         if(intervals == null || intervals.length == 0) return list.toArray(new int[0][]);
 5         //根据数组首位元素进行排序
 6         Arrays.sort(intervals, new Comparator<int[]>(){
 7             public int compare(int[] o1, int[] o2){
 8                 return o1[0] - o2[0];//根据数组区间左位置排序
 9             }
10         });
11         list.add(intervals[0]);
12         for(int i = 1; i < intervals.length; i++){
13             if(intervals[i][0] > list.getLast()[1]){//c>b 直接在list填入
14                 list.add(intervals[i]);
15             }else{//需要合并区间,只要将list最后一个元素的右位置取两个中的最大值
16                 list.getLast()[1] = Math.max(intervals[i][1], list.getLast()[1]);
17             }
18         }
19         return list.toArray(new int[0][0]);
20 
21     }
22 }

 

 

62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?

 

 

 说明:m 和 n 的值均不超过 100。
示例 1:输入: m = 3, n = 2,输出: 3
解释:从左上角开始,总共有 3 条路径可以到达右下角。1. 向右 -> 向右 -> 向下2. 向右 -> 向下 -> 向右3. 向下 -> 向右 -> 向右
示例 2:输入: m = 7, n = 3,输出: 28

分析:如果机器人位于(i,j)位置,则下一步可能要走的位置( i+1 , j ) , ( i , j+1 )。感觉和全排列相似,使用递归回溯方法

法一:递归  超时

 1 class Solution {
 2     int res = 0;
 3     public int uniquePaths(int m, int n) {
 4         path(0,0,m,n);
 5         return res;
 6     }
 7     public void path(int i, int j, int m, int n){
 8         if(i == m-1 && j == n-1){
 9             res++;
10         }
11         if(i>=m||j>=n){
12             return;
13         }
14         path(i+1,j,m,n);
15         path(i,j+1,m,n); 
16         return;
17     }
18 }

 

法二:动态规划

定义一个二维数组dp[][],表示到从出发点到当前位置有多少种走法,因为机器人只能向右或者向下移动,所以d[0][],d[][0]都为1(一直向下,或者一直向右)。当i!=0,j!=0时,到达(i,j)位置只能通过(i-1,j)和(i,j-1)两个位置到达,所以dp[i][j] = dp[i-1][j] + dp[i][j-1].

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

 

 

 

64.最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]        输出: 7解释: 因为路径 1→3→1→1→1 的总和最小。

分析:根据上题可以轻松想到动态规划(可以新建一个dp二维数组,也可以直接在原来数组上修改,看实际应用是是否允许修改原数组)

 1 class Solution {
 2     public int minPathSum(int[][] grid) {
 3         int m = grid.length;
 4         int n = grid[0].length;
 5         int[][] dp = new int[m][n];
 6         dp[0][0] = grid[0][0];
 7         for(int i=1;i<m;i++) dp[i][0] = dp[i-1][0] + grid[i][0];
 8         for(int j=1;j<n;j++) dp[0][j] = dp[0][j-1] + grid[0][j];
 9         for(int i=1;i<m;i++){
10             for(int j=1;j<n;j++){
11                 dp[i][j] = grid[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
12             }
13         }
14         return dp[m-1][n-1];
15     }
16 }

 

 

70.爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。法一:递归 超时(这玩意能少就少用)

1 class Solution {
2     public int climbStairs(int n) {
3         if(n<=3){
4             return n;
5         }else{
6             return climbStairs(n-1) + climbStairs(n-2);
7         }
8     }
9 }

 

法二:动态规划,使用数组

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

 

法三:斐波那契数列,不需要数组

 1 class Solution {
 2     public int climbStairs(int n) {
 3         if(n<=3){
 4             return n;
 5         }
 6         int first = 1;
 7         int second = 2;
 8         for(int i=3;i<=n;i++){
 9             int third = first + second;
10             first = second;
11             second = third;
12         }
13         return second;
14     }
15 }

 

 

72.编辑距离

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作: 插入一个字符、删除一个字符、替换一个字符
示例 1:输入: word1 = "horse", word2 = "ros",输出: 3
解释: horse -> rorse (将 'h' 替换为 'r',rorse -> rose (删除 'r'),rose -> ros (删除 'e')
示例 2:输入: word1 = "intention", word2 = "execution",输出: 5
解释: intention -> inention (删除 't'),inention -> enention (将 'i' 替换为 'e'),enention -> exention (将 'n' 替换为 'x'),exention -> exection (将 'n' 替换为 'c'),exection -> execution (插入 'u')

分析:?这题不会分析,直接题解了。。。官方使用动态规划算法,递归也能解决,不过重复率过高。

  首先建立一个二维数组dp[i][j]表示word1的前i个字母和word2的前j个字母之间的最短编辑距离。按照以往经验,从前往后推dp数组元素,根据前面的数组元素确定当前最短编辑距离。那么怎么确定当前元素呢?已知对一个单词可以有三种操作:删除、插入、替换。

  如果当前i位置和j位置的字符相同,即word1[i] = word2[j],那么这两个字符不需要任何操作,dp[i][j] = dp[i-1][j-1]。

  如果当前i位置和j位置的字符相同,即word1[i] != word2[j],那么当前字符应该如何操作呢?我们来看dp[i][j]之前的操作。

    ① dp[i-1][j-1]:如果使得dp[i][j] = dp[i-1][j-1]+1,那么就是当前字符进行替换

 

 

 

    ② dp[i][j-1]:如果使得dp[i][j] = dp[i][j-1]+1,那么就是在word1[i]字符进行插入操作,这样word1[i]就和word2[j]匹配了,前移j一个,继续和i比较。

===》》》》》

    ③ dp[i-1][j-1]:如果使得dp[i][j] = dp[i-1][j]+1,那么就是在word1[i]字符进行删除操作,保持j位置不动,i往前移动一个。

  已经确定了之前的三个操作,那么如何选择呢,当然是选择最小的了!

 1 class Solution {
 2     public int minDistance(String word1, String word2) {
 3         int len1 = word1.length();
 4         int len2 = word2.length();
 5         int[][] dp = new int[len1+1][len2+1];
 6         //开始填入0行0列初始数据
 7         for(int i=0;i<=len1;i++) dp[i][0] = i;//只进行删除操作
 8         for(int i=0;i<=len2;i++) dp[0][i] = i;
 9         //计算dp[][]
10         for(int i=1;i<=len1;i++){
11             for(int j=1;j<=len2;j++){
12                 if(word1.charAt(i-1)==word2.charAt(j-1)){
13                     dp[i][j] = dp[i-1][j-1];//当前相等,就直接往前移动一个
14                 }else{
15                     dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i][j-1],dp[i-1][j]))+1;//替换、插入、删除
16                 }
17             }
18         }
19         return dp[len1][len2];
20 
21     }
22 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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