【LeetCode】 41 - 50題解
41. 缺失的第一個正數
給你一個未排序的整數陣列,請你找出其中沒有出現的最小的正整數。
示例 1:
輸入: [1,2,0]
輸出: 3
示例 2:
輸入: [3,4,-1,1]
輸出: 2
示例 3:
輸入: [7,8,9,11,12]
輸出: 1
提示:
你的演算法的時間複雜度應為O(n),並且只能使用常數級別的額外空間。
雜湊表法
// 時間複雜度 O(N) 空間複雜度 O(N) public int firstMissingPositive(int[] nums) { int n = nums.length; Set<Integer> set = new HashSet<>(); for(int num : nums) set.add(num); for(int i = 1; i <= n; i ++){ if(!set.contains(i)) return i; } return n + 1; }
排序法
// 時間複雜度 O(Nlog(N)) 空 public int firstMissingPositive(int[] nums) { // 先排序 Nlog(N) Arrays.sort(nums); int pre = 0; for(int i = 0; i < nums.length; i ++){ // 跳過非正整數和重複值 if(nums[i] <= 0 || nums[i] == pre) continue; // 找到第一個突變的元素 else if(nums[i] > pre + 1) break; pre ++; } return pre + 1; }
原地雜湊法
// 時間複雜度 O(N) 空間複雜度 O(1) // 原地雜湊: 自定義雜湊函式 將數值 i 對映到 i - 1的位置上 public int firstMissingPositive(int[] nums) { int len = nums.length; for(int i = 0; i < len; i ++){ // 在指定範圍內, 且沒有放在正確位置上, 交換 while(nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]){ swap(nums, nums[i] - 1, i); } } // 找到不符合的位置 for(int i = 0; i < len; i ++){ if(nums[i] != i + 1) return i + 1; } // 都正確則返回陣列長度 + 1 return len + 1; } void swap(int[] nums, int i, int j){ int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; }
42. 接雨水
給定 n 個非負整數表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。
上面是由陣列 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。 感謝 Marcos 貢獻此圖。
示例:
輸入: [0,1,0,2,1,0,1,3,2,1,2,1]
輸出: 6
暴力求解
// 時間複雜度 O(N^2)
public int trap(int[] height) {
int res = 0;
// 遍歷每一列柱子
for(int i = 1; i < height.length - 1; i ++){
// 分別記錄當前柱子向左 向右的最大高度
int leftMax = 0, rightMax = 0;
for(int j = 0; j <= i; j ++)
leftMax = Math.max(leftMax, height[j]);
for(int j = i; j < height.length; j ++)
rightMax = Math.max(rightMax, height[j]);
// 當前位置 water[i] = min(max(h[0->i], h(i->len-1))) - h[i]
res += Math.min(leftMax, rightMax) - height[i];
}
return res;
}
動態規劃
// 時間複雜度 優化至 O(N) , 但空間複雜度為 O(N)
public int trap(int[] height) {
if(height.length == 0) return 0;
int n = height.length;
// 優化方案: 建立兩個備忘錄,分別記錄leftMax 和 rightMax
int[] leftMax = new int[n];
int[] rightMax = new int[n];
int res = 0;
// 初始化
leftMax[0] = height[0];
rightMax[n - 1] = height[n - 1];
for(int i = 1; i < n; i ++)
leftMax[i] = Math.max(height[i], leftMax[i - 1]);
for(int i = n - 2; i >= 0; i --)
rightMax[i] = Math.max(height[i] , rightMax[i + 1]);
for(int i = 1; i < n - 1; i ++)
res += Math.min(leftMax[i], rightMax[i]) - height[i];
return res;
}
雙指標
// 時間複雜度 O(N) 空間 O(1)
// 省去建立備忘錄的空間, 邊走變算
public int trap(int[] height) {
if(height.length == 0) return 0;
int n = height.length;
int l = 0, r = height.length - 1, res = 0;
int leftMax = height[0], rightMax = height[n - 1];
while(l <= r){
leftMax = Math.max(leftMax, height[l]);
rightMax = Math.max(rightMax, height[r]);
if(leftMax < rightMax)
res += leftMax - height[l ++];
else
res += rightMax - height[r --];
}
return res;
}
43. 字串相乘
給定兩個以字串形式表示的非負整數 num1
和 num2
,返回 num1
和 num2
的乘積,它們的乘積也表示為字串形式。
示例 1:
輸入: num1 = "2", num2 = "3"
輸出: "6"
示例 2:
輸入: num1 = "123", num2 = "456"
輸出: "56088"
說明:
num1
和num2
的長度小於110。num1
和num2
只包含數字0-9
。num1
和num2
均不以零開頭,除非是數字 0 本身。- 不能使用任何標準庫的大數型別(比如 BigInteger)或直接將輸入轉換為整數來處理。
/**
num1的第i位(高位從0開始)和num2的第j位相乘的結果在乘積中的位置是[i+j, i+j+1]
例: 123 * 45, 123的第1位 2 和45的第0位 4 乘積 08 存放在結果的第[1, 2]位中
index: 0 1 2 3 4
1 2 3
* 4 5
---------
1 5
1 0
0 5
---------
0 6 1 5
1 2
0 8
0 4
---------
0 5 5 3 5
這樣我們就可以單獨都對每一位進行相乘計算把結果存入相應的index中
**/
public String multiply(String num1, String num2) {
int m = num1.length(), n = num2.length();
// 記錄每位數字的陣列
int[] arr = new int[m + n];
// 從個位開始計算
for(int i = m - 1; i >= 0; i --){
for(int j = n - 1; j >= 0; j --){
// i+j, i+j+1
int mul = (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
int p1 = i + j, p2 = i + j + 1;
// 本身乘積 + 原來的數
int sum = mul + arr[p2];
// 取個位
arr[p2] = sum % 10;
// 進位
arr[p1] += sum / 10;
}
}
// 移除前導零,只需記錄第一個非0的位置即可
int i = 0;
while(i < arr.length && arr[i] == 0) i ++;
if(i == arr.length) return "0";
StringBuilder res = new StringBuilder();
while(i < arr.length){
res.append(arr[i++]);
}
return res.toString();
}
44. 萬用字元匹配
給定一個字串 (s
) 和一個字元模式 (p
) ,實現一個支援 '?'
和 '*'
的萬用字元匹配。
'?' 可以匹配任何單個字元。
'*' 可以匹配任意字串(包括空字串)。
兩個字串完全匹配才算匹配成功。
說明:
s
可能為空,且只包含從a-z
的小寫字母。p
可能為空,且只包含從a-z
的小寫字母,以及字元?
和*
。
示例 1:
輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字串。
示例 2:
輸入:
s = "aa"
p = "*"
輸出: true
解釋: '*' 可以匹配任意字串。
示例 3:
輸入:
s = "cb"
p = "?a"
輸出: false
解釋: '?' 可以匹配 'c', 但第二個 'a' 無法匹配 'b'。
示例 4:
輸入:
s = "adceb"
p = "*a*b"
輸出: true
解釋: 第一個 '*' 可以匹配空字串, 第二個 '*' 可以匹配字串 "dce".
示例 5:
輸入:
s = "acdcb"
p = "a*c?b"
輸出: false
// f[i][j] = 標識 s的 0 - i 與 p的 0 - j 是否匹配
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] f = new boolean[m + 1][n + 1];
// 空串匹配
f[0][0] = true;
// s為空,只要p的開頭是* 就代表匹配
for(int j = 1; j <= n; j ++){
if(p.charAt(j - 1) == '*') f[0][j] = true;
else break;
}
for(int i = 1; i <= m; i ++){
for(int j = 1; j <= n; j ++){
// f[i][j - 1]的情況 標識 ab和ab*
// f[i - 1][j]的情況 標識 abcd 和 ab*
if(p.charAt(j - 1) == '*'){
f[i][j] = f[i][j - 1] || f[i - 1][j];
}else if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)){
f[i][j] = f[i - 1][j - 1];
}
}
}
return f[m][n];
}
45. 跳躍遊戲 II
給定一個非負整數陣列,你最初位於陣列的第一個位置。
陣列中的每個元素代表你在該位置可以跳躍的最大長度。
你的目標是使用最少的跳躍次數到達陣列的最後一個位置。
示例:
輸入: [2,3,1,1,4]
輸出: 2
解釋: 跳到最後一個位置的最小跳躍數是 2。
從下標為 0 跳到下標為 1 的位置,跳 1 步,然後跳 3 步到達陣列的最後一個位置。
說明:
假設你總是可以到達陣列的最後一個位置。
https://leetcode-cn.com/problems/jump-game-ii/solution/45-by-ikaruga/
貪心演算法
public int jump(int[] nums) {
int ans = 0, start = 0, end = 1;
while(end < nums.length){
int maxPos = 0;
for(int i = l; i < end; i ++){
//能跳到的最遠的距離
maxPos = Math.max(maxPos, i + nums[i]);
}
//下一次起跳點範圍開始的格子
start = end;
//下一次起跳點範圍結束的格子
end = maxPos + 1;
ans ++;
}
return ans;
}
46. 全排列
給定一個 沒有重複 數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
dfs(nums);
return res;
}
void dfs(int[] nums){
if(path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i ++){
if(!used[i]){
used[i] = true;
path.add(nums[i]);
dfs(nums);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
}
47. 全排列 II
給定一個可包含重複數字的序列,返回所有不重複的全排列。
示例:
輸入: [1,1,2]
輸出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
我們需要明確,在這個DFS的過程中,哪裡出現了重複?在這個圖中,已經十分明顯地看到:
- 這次搜尋的起點和上次的起點相同 , visited[i - 1] == visited[i]。
- 且上一次的數已經被撤銷,visited[i-1] ==false。
為保證i-1不越界,加上i>0的條件:
if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
continue;
}
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] visited;
public List<List<Integer>> permuteUnique(int[] nums) {
visited = new boolean[nums.length];
Arrays.sort(nums); //保證陣列的有序性
dfs(nums);
return res;
}
void dfs(int[]nums){
if(path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length ; i++){
if( visited[i]) continue;
if( i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue; //此處剪枝
visited[i] = true;
path.add(nums[i]);
dfs(nums);
visited[i] = false;
path.remove(path.size()-1);
}
}
48. 旋轉影象
給定一個 n × n 的二維矩陣表示一個影象。
將影象順時針旋轉 90 度。
說明:
你必須在原地旋轉影象,這意味著你需要直接修改輸入的二維矩陣。請不要使用另一個矩陣來旋轉影象。
示例 1:
給定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋轉輸入矩陣,使其變為:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
給定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋轉輸入矩陣,使其變為:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
public void rotate(int[][] matrix) {
int n = matrix.length;
//轉置
/*
1 2 3 --> 1 4 7
4 5 6 --> 2 5 8
7 8 9 --> 3 6 9
*/
for(int i = 0; i < n; i ++){
for(int j = i; j < n; j ++){
swap(matrix, i, j, j, i);
}
}
/*
1 4 7 --> 7 4 1
2 5 8 --> 8 5 2
3 6 9 --> 9 6 3
*/
for(int i = 0; i < n; i ++){
for(int j = 0; j < n / 2; j ++){
swap(matrix, i, j, i, n - j - 1);
}
}
}
void swap(int[][] m, int i1, int j1, int i2, int j2){
int temp = m[i1][j1];
m[i1][j1] = m[i2][j2];
m[i2][j2] = temp;
}
49. 字母異位詞分組
給定一個字串陣列,將字母異位詞組合在一起。字母異位詞指字母相同,但排列不同的字串。
示例:
輸入: ["eat", "tea", "tan", "ate", "nat", "bat"]
輸出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
說明:
- 所有輸入均為小寫字母。
- 不考慮答案輸出的順序。
public List<List<String>> groupAnagrams(String[] strs) {
//O(N KlogK) N = strs.length K = str.length()
if(strs.length == 0) return new ArrayList<>();
Map<String, List> hash = new HashMap<>();
for(String str : strs){
// 字元排序
char[] chs = str.toCharArray();
Arrays.sort(chs);
String key = new String(chs);
if(!hash.containsKey(key)) hash.put(s, new ArrayList<List>());
hash.get(key).add(str);
}
return new ArrayList(hash.values());
}
50. Pow(x, n)
實現 pow(x, n) ,即計算 x 的 n 次冪函式。
示例 1:
輸入: 2.00000, 10
輸出: 1024.00000
示例 2:
輸入: 2.10000, 3
輸出: 9.26100
示例 3:
輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25
說明:
- -100.0 < x < 100.0
- n 是 32 位有符號整數,其數值範圍是 [−231, 231 − 1] 。
https://leetcode-cn.com/problems/powx-n/solution/50-powx-n-kuai-su-mi-qing-xi-tu-jie-by-jyd/
public double myPow(double x, int n) {
if(x == 0.0f) return 0.0d;
long b = n;
double res = 1.0;
// 當n < 0時,轉化為 n >= 0的情況
if(b < 0){
x = 1 / x;
b = -b;
}
while(b > 0){
if((b & 1) == 1) // b % 2 == 1
res *= x;
x *= x; // x = x ^ 2
b >>= 1; // b = b // 2
}
return res;
}