1. 程式人生 > 實用技巧 >【LeetCode】 41 - 50題解

【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. 字串相乘

給定兩個以字串形式表示的非負整數 num1num2,返回 num1num2 的乘積,它們的乘積也表示為字串形式。

示例 1:

輸入: num1 = "2", num2 = "3"
輸出: "6"

示例 2:

輸入: num1 = "123", num2 = "456"
輸出: "56088"

說明:

  1. num1num2 的長度小於110。
  2. num1num2 只包含數字 0-9
  3. num1num2 均不以零開頭,除非是數字 0 本身。
  4. 不能使用任何標準庫的大數型別(比如 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]
]

https://leetcode-cn.com/problems/permutations-ii/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liwe-2/

我們需要明確,在這個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;
    }