LeetCode Week 4:第 31 ~ 40 题

31. 下一个排列

题目描述

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

题解:
找规律O(n):
我们可以这样来分析:
我们希望 下一个数比当前数大,这样才满足“下一个排列”的定义。因此只需要将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。
比如 123456,将 56 交换就能得到一个更大的数 123465
我们还希望 下一个数增加的幅度尽可能的小,这样才满足“下一个排列与当前排列紧邻“的要求。为了满足这个要求,我们需要:
在尽可能靠右的低位进行交换,需要从后向前查找
将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 54 交换而不是把 64 交换
将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。以 123465 为例:首先按照上一步,交换 54
得到 123564;然后需要将 5 之后的数重置为升序,得到 123546。显然 123546123564 更小,123546 就是 123465 的下一个排列。

c++版

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int i = nums.size() - 1;
        while(i > 0 && nums[i - 1] >= nums[i])i--;
        if(i == 0)
            sort(nums.begin(),nums.end());
        else{
            int t = i;
            while(t < nums.size() && nums[t] > nums[i - 1])t++;
            swap(nums[t - 1], nums[i - 1]);
            reverse(nums.begin() + i, nums.end());
        }
    }
};

python版

class Solution:
    def nextPermutation(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        i = len(nums) - 1
        while i > 0 and nums[i - 1] >= nums[i]:
            i -= 1
        if i == 0:
            nums.sort()
        else:
            t = i
            while t < len(nums) and nums[t] > nums[i - 1]:
                t += 1
            nums[i - 1], nums[t - 1] = nums[t - 1], nums[i - 1]
            nums[i:len(nums) + 1] = nums[i:len(nums) + 1][::-1]

2. 最长有效括号

题目描述
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:

输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”

示例 2:

输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”

题解:
二分O(logn):
对于旋转数组 nums = [4,5,6,7,0,1,2]
首先根据 nums[0] 与 target 的关系判断 target 是在左段还是右段。
例如 target = 5, 目标值在左半段,因此在 [4, 5, 6, 7, inf, inf, inf] 这个有序数组里找就行了;
例如 target = 1, 目标值在右半段,因此在 [-inf, -inf, -inf, -inf, 0, 1, 2] 这个有序数组里找就行了。

c++版

class Solution {
public:
    int longestValidParentheses(string s) {
        int arr[100010] = {0};
        int t = 0;
        int ans = 0;
        for(int i = 0; i < s.size(); i++){
            if(s[i] == '(')
                arr[++t] = i;
            else{
                if(t == 0)
                    arr[++t] = i;
                else if(s[arr[t]] == '(')
                    t--;
                else arr[++t] = i;
            }
            if(t == 0) ans = i + 1;
            else ans = max(ans, i - arr[t]);
        }
        return ans;
    }
};

python版

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        l = [0 for _ in range(100010)]
        t = 0
        ans = 0
        for i in range(0, len(s)):
            if s[i] == '(':
                t += 1
                l[t] = i
            else:
                if t > 0 and s[l[t]] == '(':
                    t -= 1
                else:
                    t += 1
                    l[t] = i
            if t == 0:
                ans = i + 1
            else:
                ans = max(ans, i - l[t])
        return ans

34. 在排序数组中查找元素的第一个和最后一个位置

题目描述
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

题解;
二分模板题O(logn)

c++版

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> a, b;
        a.push_back(-1);
        a.push_back(-1);
        if(nums.size() == 0)
            return a;
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) 
                left = mid + 1;
            else if (nums[mid] >= target) 
                right = mid - 1;
        }
        // 最后要检查 left 越界的情况
        if (left >= nums.size() || nums[left] != target)
            return a;
        b.push_back(left);

        left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) 
                left = mid + 1;
            else if (nums[mid] > target) 
                right = mid - 1;
        }
        // 最后要检查 left 越界的情况
        if (right < 0 || nums[right] != target)
            return a;
        b.push_back(right);
        return b;
    }
};

python版

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if not nums:
            return [-1,-1]
    
        n = len(nums)
        l, r = 0, n - 1
        while l <= r:
            mid = (l + r) // 2
            if nums[mid] >= target:
                r = mid - 1
            else: l = mid + 1
        if l >= n or nums[l] != target:
            return [-1, -1]
        ans1 = l

        l, r = 0, n - 1
        while l <= r:
            mid = (l + r) // 2
            if nums[mid] <= target:
                l = mid + 1
            else: r = mid - 1
        if r < 0 or nums[r] != target:
            return [-1, -1]
        ans2 = r
        return [ans1, ans2]

35. 搜索插入位置

题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5
输出: 2

示例 2:

输入: [1,3,5,6], 2
输出: 1

示例 3:

输入: [1,3,5,6], 7
输出: 4

示例 4:

输入: [1,3,5,6], 0
输出: 0

题解:
二分模板题O(logn)

c++版

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;
        while(l <= r){
            int mid = (l + r) / 2;
            if(nums[mid] == target) return mid;
            if(nums[mid] > target) r = mid - 1;
            else l = mid + 1; 
        }
        return r + 1;
    }
};

python版

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        n = len(nums)
        l, r = 0, n - 1
        while l <= r:
            mid = (l + r) // 2
            if nums[mid] == target:
                return mid
            if nums[mid] > target:
                r = mid - 1
            else: l = mid + 1
        return r + 1

36. 有效的数独

题目描述

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

在这里插入图片描述
上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

示例 1:

输入:
[
[“5”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: true

示例 2:

输入:
[
[“8”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

说明:

  • 一个有效的数独(部分已被填充)不一定是可解的。
  • 只需要根据以上规则,验证已经填入的数字是否有效即可。
  • 给定数独序列只包含数字 1-9 和字符 ‘.’ 。
  • 给定数独永远是 9x9 形式的。

题解:
1.位运算判重
分别使用一个整型数组记录每行、每列和每个九宫格内数字的存在情况。
位运算可以极大的简化判断,提高效率,具体看代码。
2.二维布尔数组判断
原理跟位运算一样

c++版

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        int N = 9;
        int r[N], c[N], cell[3][3];
        for(int i = 0; i < N; i++) r[i] = c[i] = (1 << N) - 1;
        for(int i = 0; i < 3; i++) 
            for(int j = 0; j < 3; j++)
                cell[i][j] = (1 << N) - 1;

        for(int i = 0; i < N; i++){
            for(int j = 0; j < N; j++){
                if(board[i][j] != '.'){
                    int a = i / 3;
                    int b = j / 3;
                    int t = board[i][j] - '1';
                    if(!(r[i] & 1 << t) || !(c[j] & 1 << t) || !(cell[a][b] & 1 << t))
                        return false;
                    r[i] -= 1 << t;
                    c[j] -= 1 << t;
                    cell[a][b] -= 1 << t;
                }
            }
        }
        return true;
    }
};

python版

class Solution:
    def isValidSudoku(self, board):
        """
        :type board: List[List[str]]
        :rtype: bool
        """
        # init data
        N = 9
        row = [{} for _ in range(N)]
        col = [{} for _ in range(N)]
        cell = [{} for _ in range(N)]

        for i in range(N):
            for j in range(N):
                ce_index = i // 3 * 3 + j // 3
                if board[i][j] != '.':
                    t = int(board[i][j])
                    if t in row[i] or t in col[j] or t in cell[ce_index]:
                        return False
                    row[i][t] = 1
                    col[j][t] = 1
                    cell[ce_index][t] = 1
        return True

37. 解数独

题目描述

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
    空白格用 ‘.’ 表示。

在这里插入图片描述

一个数独。

在这里插入图片描述

提示

  • 给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。

题解
(递归回溯):
1.首先预处理出 col、row 和 squ 数组。
2.从 (0,0) 位置开始尝试并递归。遇到 . 时,枚举可以填充的数字,然后判重并加入 col、row 和 squ 数组中。
3.如果成功到达结尾,则返回 true,告知递归可以终止。

c++版

class Solution {
public:
    bool row[9][9], col[9][9], cell[3][3][9];
    void solveSudoku(vector<vector<char>>& board) {
        memset(row, false, sizeof row);
        memset(col, false, sizeof col);
        memset(cell, false, sizeof cell);
        for(int i = 0; i < 9; i++)
            for(int j = 0; j < 9; j++)
                if(board[i][j] != '.'){
                    int t = board[i][j] - '1';
                    row[i][t] = col[j][t] = cell[i / 3][j / 3][t] = true;
                }
        dfs(board, 0, 0);
    }
    bool dfs(vector<vector<char>>& board, int x, int y){
        if(y == 9) x += 1, y = 0;
        if(x == 9) return true;

        if(board[x][y] != '.') return dfs(board, x, y + 1);
        for(int i = 0; i < 9; i++){
            if(!row[x][i] && !col[y][i] && !cell[x / 3][y / 3][i]){
                board[x][y] = i + '1';
                row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;
                if(dfs(board, x, y + 1)) return true;
                board[x][y] = '.';
                row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = false;
            }
        }
        return false;
    }
};

python版

class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """

        row = [[False for _ in range(9)] for _ in range(9)]
        col = [[False for _ in range(9)] for _ in range(9)]
        cell = [[False for _ in range(9)] for _ in range(9)]
        for i in range(9):
            for j in range(9):
                if board[i][j] != '.':
                    t = int(board[i][j]) - 1
                    row[i][t] = col[j][t] = cell[i // 3 * 3 + j // 3][t] = True;

        def dfs(board, x, y):
            if y == 9:
                x += 1; y = 0
            if x == 9: return True

            if board[x][y] != '.': return dfs(board, x, y + 1)
            for i in range(9):
                if not row[x][i] and not col[y][i] and not cell[x // 3 * 3 + y // 3][i]:
                    board[x][y] = str(i + 1)
                    row[x][i] = col[y][i] = cell[x // 3 * 3 + y // 3][i] = True
                    if(dfs(board, x, y + 1)): return True
                    board[x][y] = '.'
                    row[x][i] = col[y][i] = cell[x // 3 * 3 + y // 3][i] = False
            return False
        dfs(board, 0, 0)

38. 外观数列

题目描述

给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。

注意:整数序列中的每一项将表示为一个字符串。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:

1.     1
2.     11
3.     21
4.     1211
5.     111221

第一项是数字 1

描述前一项,这个数是 1 即 “一个 1 ”,记作 11

描述前一项,这个数是 11 即 “两个 1 ” ,记作 21

描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211

描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221

示例 1:

输入: 1
输出: “1”
解释:这是一个基本样例。

示例 2:

输入: 4
输出: “1211”
解释:当 n = 3 时,序列是 “21”,其中我们有 “2” 和 “1” 两组,“2” 可以读作 “12”,也就是出现频次 = 1 而 值 = 2;类似 “1” 可以读作 “11”。所以答案是 “12” 和 “11” 组合在一起,也就是 “1211”。

题解:
(模拟) O(n2)
直接按照从 2 到 n 的顺序生成字符串,即每次找连续相同的数字段,合并。

c++版

class Solution {
public:
    string countAndSay(int n) {
        string s = "1";
        for (int i = 0; i < n - 1; i ++ ) {
            string t;
            for (int j = 0; j < s.size();) {
                int k = j + 1;
                while (k < s.size() && s[k] == s[j]) k ++ ;
                t += to_string(k - j) + s[j];
                j = k;
            }
            s = t;
        }

        return s;
    }
};

python版

class Solution:
    def countAndSay(self, n: int) -> str:
        s = '1'
        for i in range(n - 1):
            j = 0
            t = ""
            while j < len(s):
                k = j + 1
                while k < len(s) and s[k] == s[j]: k += 1
                t += str(k - j) + s[j]
                j = k
            s = t
        return s

39. 组合总和

题目描述

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]

示例 2:

输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

提示:

  • 1 <= candidates.length <= 30
  • 1 <= candidates[i] <= 200
  • candidate 中的每个元素都是独一无二的。
  • 1 <= target <= 500

题解:
(递归枚举)
在每一层搜索中,枚举这个数字添加几次。
搜索的终止条件是层数超过的数组的长度或者当前数字组合等于目标值。
剪枝:可以先将数组从小到大排序,搜索中如果 sum != target 并且 sum+candidates[i] > target,
则可以直接终止之后的递归,因为之后的数字都会比 candidates[i] 大,不会再产生答案。

c++版

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        dfs(candidates, 0, target);
        return ans;
    }

    void dfs(vector<int>& candidates, int cur, int target){
        if(target == 0){
            ans.push_back(path);
            return ;
        }
        if(target < 0) return ;
        if(cur == candidates.size()) return ;

        for(int i = 0; i * candidates[cur] <= target; i++){
            dfs(candidates, cur + 1, target - i * candidates[cur]);
            path.push_back(candidates[cur]);
        }
        for(int i = 0; i * candidates[cur] <= target; i++)
            path.pop_back();
    }
};

python版

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        ans = []
        path = []
        def dfs(c, cur, target):
            if target == 0:
                ans.append(path[:])
                return
            if target < 0: return 
            if cur == len(c): return 

            i = 0
            while(i * c[cur] <= target):
                dfs(c, cur + 1, target - i * c[cur])
                path.append(c[cur])
                i += 1
            i = 0
            while(i * c[cur] <= target):
                path.pop()
                i += 1
        dfs(candidates, 0, target)
        return ans

40. 组合总和 II

题目描述

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。
**
**说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

题解:
递归搜索:
排序,对于每层搜索,判断当前数字重复的次数,保证搜索下一层的数字和当前数字不一样

c++版

class Solution {
public:

    vector<vector<int>> ans;
    vector<int> path;

    vector<vector<int>> combinationSum2(vector<int>& c, int target) {
        sort(c.begin(), c.end());
        dfs(c, 0, target);

        return ans;
    }

    void dfs(vector<int>& c, int u, int target) {
        if (target == 0) {
            ans.push_back(path);
            return;
        }
        if (u == c.size()) return;

        int k = u + 1;
        while (k < c.size() && c[k] == c[u]) k ++ ;
        int cnt = k - u;

        for (int i = 0; c[u] * i <= target && i <= cnt; i ++ ) {
            dfs(c, k, target - c[u] * i);
            path.push_back(c[u]);
        }

        for (int i = 0; c[u] * i <= target && i <= cnt; i ++ ) {
            path.pop_back();
        }
    }
};

python

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        ans = []
        path = []
        candidates.sort()
        def dfs(c, cur, target):
            if target == 0:
                ans.append(path[:])
                return 
            if cur == len(c): return
            if target < 0: return 
            k = cur + 1
            while k < len(c) and c[k] == c[cur]: k += 1
            cnt = k - cur
            i = 0
            while i <= cnt and i * c[cur] <= target: 
                dfs(c, k, target - i * c[cur])
                path.append(c[cur])
                i += 1
            i = 0
            while i <= cnt and i * c[cur] <= target:
                path.pop()
                i += 1
        dfs(candidates, 0, target)
        return ans

猜你喜欢

转载自blog.csdn.net/qq_43328040/article/details/107427116