1. 程式人生 > 其它 >🔥 LeetCode 熱題 HOT 100(21-30)

🔥 LeetCode 熱題 HOT 100(21-30)

46. 全排列

思路:典型回溯法

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        LinkedList<Integer> track = new LinkedList<>();
        boolean[] visited = new boolean[nums.length];

        dfs(nums, track, visited);
        return res;
    }

    private List<List<Integer>> res = new LinkedList<>();
    private void dfs(int[] nums, LinkedList<Integer> track, boolean[] visited) {
        //base case
        if (track.size() == nums.length) {
            res.add(new LinkedList<>(track));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            //剪枝
            if (visited[i]) continue;

            // 選擇
            track.offerLast(nums[i]);
            visited[i] = true;

            dfs(nums, track, visited);

            //撤銷選擇
            track.pollLast();
            visited[i] = false;
        }
    }
}

48. 旋轉影象

思路:(技巧題,記住就好)

  • 先沿對角線翻轉
  • 然後每一行按中間元素翻轉
class Solution {
    public void rotate(int[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;

        for (int i = 0; i < row; i++) {
            for (int j = i + 1; j < row; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }

        for (int i = 0; i < row; i++) {
            int left = 0;
            int right = col - 1;
            while (left < right) {
                int temp = matrix[i][left];
                matrix[i][left] = matrix[i][right];
                matrix[i][right] = temp;
                left++;
                right--;
            }
        }
    }
}

49. 字母異位詞分組

思路:根據字母異位詞定義可知:

  • 互為異位詞的字串根據字元排序後得到的字串相同;
  • 互為異位詞的字串中各個字元的出現次數相同。

因此有兩種方法:排序法、計數法。

排序法:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>();

        for (String str : strs) {
            char[] strArr = str.toCharArray();
            Arrays.sort(strArr);
            String orderStr = new String(strArr);
            List<String> list = map.getOrDefault(orderStr, new LinkedList<>());
            list.add(str);
            map.put(orderStr, list);
        }

        return new ArrayList<List<String>>(map.values());
    }
}

計數法:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>();

        for (String str : strs) {
            //統計str中字元出現次數
            int[] count = new int[26];
            for (int i = 0; i < str.length(); i++) {
                count[str.charAt(i) - 'a']++;
            }

            //拼接key
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < count.length; i++) {
                if (count[i] > 0) {
                    sb.append('a' + i);
                    sb.append(count[i]);
                }
            }
            String key = sb.toString();
            
            List<String> list = map.getOrDefault(key, new LinkedList<>());
            list.add(str);
            map.put(key, list);
        }

        return new ArrayList<List<String>>(map.values());
    }
}

53. 最大子序和

思路:動態規劃

class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;

        //以當前位置結尾的子陣列的最大和
        int[] dp = new int[len];

        //base case 
        dp[0] = nums[0];

        int max = dp[0];
        //轉移方程:若dp[i - 1] 大於 0, dp[i] = dp[i - 1] + nums[i];否則 dp[i] = nums[i];
        for (int i = 1; i < len; i++) {
            if (dp[i - 1] > 0) {
                dp[i] = dp[i - 1] + nums[i];
            } else {
                dp[i] = nums[i];
            }
            max = Math.max(max, dp[i]);
        }

        return max;
    }
}

以上程式碼可以看出當前狀態只與前一個狀態相關,故可以將 間複雜度由O(n) 降到O(1):

class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;

        int sum = nums[0];
        int max = sum;

        for (int i = 1; i < len; i++) {
            if (sum > 0) {
                sum = sum + nums[i];
            } else {
                sum = nums[i];
            }
            max = Math.max(max, sum);
        }

        return max;
    }
}

55. 跳躍遊戲

思路一:動態規劃,推薦

class Solution {
    public boolean canJump(int[] nums) {
        int len = nums.length;
        //起始位置能否跳至當前位置
        boolean[] dp = new boolean[len];

        //base case
        dp[0] = true;

        //轉移方程:i前面所有的點只要有一個能跳到當前結點就說明當前結點可達
        for (int i = 1; i < len; i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && (j + nums[j] >= i)) {
                    dp[i] = true;
                    break;
                }
            }
        }

        return dp[len - 1];
    }
}

思路二:貪心,時間複雜度更低,多數靠死記硬背。如果能用動態規劃就儘量用動規,超時了再說

class Solution {
    public boolean canJump(int[] nums) {
        int len = nums.length;
        //目前從起點能調到的最遠位置
        int maxFar = 0;

        for (int i = 0; i <= maxFar; i++) {
            if (nums[i] + i > maxFar) {
                maxFar = nums[i] + i;
            }
            if (maxFar >= (len - 1)) {
                return true;
            }
        }

        return false;
    }
}

56. 合併區間

class Solution {
    public int[][] merge(int[][] intervals) {
        int len = intervals.length;
        //按照 左 邊界排序
        Arrays.sort(intervals, (arr1, arr2) -> arr1[0] - arr2[0]);
        int[][] res = new int[len][2];

        //i指向新區間的第一個小區間,k為合併後的新區間個數
        int i = 0, k = 0;
        while (i < len) {
                int left = intervals[i][0];
                //合併區間的過程就是修改right的過程
                int right = intervals[i][1];

                //尋找可以合併的區間並完成合並
                int j = i + 1;
                while (j < len && right >= intervals[j][0]) {
                    right = Math.max(right, intervals[j][1]);
                    j++;
                }

                //儲存新區間的範圍
                res[k][0] = left;
                res[k][1] = right;
                k++;

                i = j; 
        }

        return Arrays.copyOf(res, k);
    }
}

62. 不同路徑

思路:動態規劃

  • 狀態:從起點到當前結點的不同路徑數。

  • 轉移方程:起點到當前點的不同路徑數dp[i][j]等於:起點到當前結點相鄰左結點dp[i][j - 1]和相鄰上結點dp[i - 1][j]不同路徑數之和。

  • base case:第0行dp[0][x]和0列dp[x][0]都為1,前者只能通過其相鄰左節點到達,後者只能通過相鄰上結點到達。

class Solution {
    public int uniquePaths(int m, int n) {
        //狀態
        int dp[][] = new int[m][n];

        //base case
        for (int i = 0; i < n; i++) {
            dp[0][i] = 1;
        }
        for (int i = 1; i < m; i++) {
            dp[i][0] = 1;
        }

        //轉移方程
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
            }
        }

        return dp[m - 1][n - 1];
    }
}

64. 最小路徑和

思路:動態規劃,和上一題相似。

  • 狀態:起點到當前結點的最小路徑和

  • 轉移方程:起點到當前結點最小路徑和dp[i][j]等於:min(起點到其相鄰左結點最小路徑和dp[i][j - 1],起點到其相鄰上結點最小路徑和dp[i - 1][j]) + 當前結點值grid[i][j]

  • base case: dp[0][0] = grid[0][0]; 第一行dp[0][x]都為其相鄰左結點dp[0][x -1] + 自身結點值grid[0][x], x >= 1;第一列dp[x][0]都為其相鄰上結點dp[x - 1][0] + 自身結點值grid[x][0], x >= 1

class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;

        //從左上角到i, j的最短路徑和
        int[][] dp = new int[m][n];

        //base case
        dp[0][0] = grid[0][0];
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int i = 1; i < n; i++) {
            dp[0][i] = dp[0][i - 1] + grid[0][i];
        }

        //轉移方程
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], dp [i][j - 1]) + grid[i][j];
            }
        }

        return dp[m - 1][n - 1];
    }
}

70. 爬樓梯

思路:動態規劃

  • 狀態:從第0個臺階跳到當前臺階的不同方式

  • 轉移方程:第0個臺階到當前臺階的不同方式dp[i]等於:第0個臺階到當前臺階下面兩個臺階的不同方式之和(dp[i - 1] + dp[i - 2])

  • base case: dp[0] = dp[1] = 1

class Solution {
    public int climbStairs(int n) {
        //狀態:從第0個臺階跳到當前臺階的不同方式
        int[] dp = new int[n + 1];

        //base case
        dp[0] = 1;
        dp[1] = 1;
        
        //轉移方程
        for (int i = 2; i < n + 1; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }
}

72. 編輯距離

思路:動態規劃

class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();

        //word1 前m個字元和 word2 前n個字元之間的編輯距離,z
        int[][] dp = new int[m + 1][n + 1];

        //base case
        dp[0][0] = 0;
        for (int i = 1; i < m + 1; i++) {
            dp[i][0] = i;
        }
        for (int i = 1; i < n + 1; i++) {
            dp[0][i] = i;
        }

        //狀態轉移
        for (int i = 1; i < m + 1; i++) {
            for (int j = 1; j < n + 1; j++) {
                //最後一個字元相等
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                //不等則分別進行 替換、刪除word1中最後一個字元,或在word1最後插入一個字元,取代價最小的一個
                } else {
                    dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]) + 1;
                }
            }
        }

        return dp[m][n];
    }
}