🔥 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];
}
}