代码随想录算法训练营day25 | 491.递增子序列 、46.全排列 、47.全排列 II、51.N皇后、37.解数独

碎碎念:加油加油,继续坚持
参考:代码随想录

491.递增子序列

题目链接

491.递增子序列

思想

注意结果中不能有重复的子集。
不能排序后处理,因为进行排序以后改变元素顺序,求的递增子序列会改变。

树形图:
在这里插入图片描述
结果分布在节点上,只是对节点里面的数字有些要求。

定义一个一维数组path,一个二维数组result。
一进来递归的时候就收集结果:如果path.size>1,就把当前path放到result中。
定义一个set,用来判断当前取到的数之前是否取过。【之前的90.子集II 用的是used数组,因为之前的题目我们可以用排序使得相同的数字相邻,这里不能用排序了,因为排序会改变递增子序列】
回溯三部曲:

  1. 参数和返回值:参数有nums,startIndex
  2. 终止条件:如果startIndex>=nums.size
  3. 单层搜索逻辑:for循环,i从startIndex开始,如果当前取的数小于子集里最右面的元素(对path为空做一个判断)或者uset里存在了当前取的数,continue。
    用uset记录一下当前取的数,把要取的数放入path,递归(i+1),把之前加入的数取出来。【为什么没有回溯uset的值呢,因为本层定义的uset只记录本层的情况,进入下一层会重新定义一个uset】

题解

// cpp 
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        if (path.size() > 1){
            result.push_back(path);
        }
        unordered_set<int> uset;
        for (int i = startIndex; i < nums.size(); i++) {
            if (!path.empty() && nums[i] < path.back() || uset.find(nums[i])!= uset.end()) continue;
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums, i+1);
            path.pop_back();
        }

    }
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracking(nums, 0);
        return result;
    }
};
# python 用数组模拟set
class Solution:
    def __init__(self):
        self.path = []
        self.result = []

    def backtracking(self, nums, startIndex):
        if len(self.path) > 1:
            self.result.append(self.path[:])
        
        used = [0] * 201
        for i in range(startIndex, len(nums)):
            if (self.path and nums[i] < self.path[-1] or used[nums[i] + 100] == 1):
                continue
            used[nums[i] + 100] = 1
            self.path.append(nums[i])
            self.backtracking(nums, i+1)
            # used[nums[i] + 100] = 0 不用写了 因为每一层都会定义一个新uesd
            self.path.pop()
            

    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        self.backtracking(nums, 0)
        return self.result

反思

注意思考为什么不用排序+used数组,注意思考为什么单层搜索逻辑不用回溯uset。
这种用set去重的方法在之前提到的题目如90.子集II 也可以使用。
本题的set也可以用数组模拟。

46.全排列

题目链接

46.全排列

思想

本题没有重复的元素,那么就不用考虑去重。
排列和组合的区别:[2,1]和[1,2]是同一个组合,但是是不同的排列。排列是强调元素顺序的。
这里用used数组来标记哪个元素使用过了。【组合类问题用startIndex来防止重复取】

树形图:
在这里插入图片描述

定义一个一维数组path,一个二维数组result。
回溯三部曲:

  1. 参数和返回值:参数是nums和uesd数组
  2. 终止条件:如果path的长度和nums的长度相等的时候,收获结果,return
  3. 单层搜索逻辑:for循环,i从0开始 ,如果used[i]==true,coutinue。修改used数组,把取的数字加入path,进入下一层递归,把数字取出,修改used。

题解

// cpp
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, vector<bool>& used) {
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0 ; i < nums.size(); i++) {
            if (used[i] == true) continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};
# python
class Solution:
    def backtracking(self, nums, used, path, result):
        if len(path) == len(nums):
            result.append(path[:])
            return
        for i in range(0, len(nums)):
            if used[i] == True:
                continue
            used[i] = True
            path.append(nums[i])
            self.backtracking(nums, used, path, result)
            path.pop()
            used[i] = False

    def permute(self, nums: List[int]) -> List[List[int]]:
        path = []
        result = []
        used = [False] * len(nums)
        self.backtracking(nums, used, path, result)
        return result

反思

注意和组合最大的区别在于for循环从0开始,而不是从startIndex开始。

47.全排列 II

题目链接

47.全排列 II

思想

本题和上一题的区别:本题给的集合有重复元素,而题目有要求给出的排列不重复,所以关键点在于去重。

树形图:
在这里插入图片描述

在主函数里要做一下排序,方便去重。
used数组用来标记排列里用了哪些元素。
定义一个一维数组path,一个二维数组result。
回溯三部曲:

  1. 参数和返回值:参数是nums和uesd数组
  2. 终止条件:如果path的长度和nums的长度相等的时候,收获结果,return
  3. 单层搜索逻辑:for循环,i从0开始,如果i>0且当前数字和前一个数字相等且used[i-1]为false(说明在树层上),continue。【去重】 如果used[i]==true,coutinue【防止元素重复使用】。修改used数组,把取的数字加入path,进入下一层递归,把数字取出,修改used【回溯】。

题解

// cpp
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, vector<bool>& used) {
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            if (i > 0 && nums[i] == nums[i - 1] && used[i-1] == false) continue;
            if (used[i] == true) continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};
# python
class Solution:
    def backtracking(self, nums, uesd, path, result):
        if len(nums) == len(path):
            result.append(path[:])
            return
        for i in range(0, len(nums)):
            if i > 0 and nums[i] == nums[i - 1] and uesd[i-1] == False:
                continue
            if uesd[i] == True:
                continue
            uesd[i] = True
            path.append(nums[i])
            self.backtracking(nums, uesd, path, result)
            path.pop()
            uesd[i] = False
        
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        path = []
        result = []
        uesd = [False] * len(nums)
        self.backtracking(nums, uesd, path, result)
        return result
        

反思

树层去重效率更高,剪枝能剪掉更多。

51.N皇后

题目链接

51.N皇后

思想

树形图:
在这里插入图片描述
定义一个三维数组result。
回溯三部曲:

  1. 参数和返回值:传入棋盘,棋盘大小,row(行数)
  2. 终止条件:如果row==n,收获结果到结果集,return
  3. 单层搜索逻辑:for循环,i从0开始,判断棋盘是否合法(用一个isValid函数判断),如果合法,在row,i位置上放皇后,进入下一层递归(row+1),把row,i位置上的皇后拿走(回溯)。

isValid函数:返回值是bool类型,参数有row,i,棋盘,棋盘大小。
验证标准:不能同行,不能同列,不能同斜线(45度和135度)。

题解

// cpp
class Solution {
private:
    vector<vector<string>> result;
    void backtracking(int n, int row, vector<string>& chessboard) {
        if (row == n) {
            result.push_back(chessboard);
            return;
        }
        for (int col = 0; col < n; col++) {
            if (isValid(row, col, chessboard, n)){
                chessboard[row][col] = 'Q';
                backtracking(n, row + 1, chessboard);
                chessboard[row][col] = '.';
            }
        }
    }
    bool isValid(int row, int col, vector<string>& chessboard, int n) {
        for (int i = 0; i < n; i++) {
            if (chessboard[i][col] == 'Q') return false;
        }
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (chessboard[i][j] == 'Q') return false;
        }
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (chessboard[i][j] == 'Q') return false;
        }
        return true;
    }
public:
    vector<vector<string>> solveNQueens(int n) {
        std::vector<std::string> chessboard(n, std::string(n, '.'));
        backtracking(n, 0, chessboard);
        return result;
    }
};
# python
class Solution:
    def isValid(self, row, col, chessboard, n):
        for i in range(row):
            if chessboard[i][col] == 'Q':
                return False
        

        i, j = row - 1, col - 1
        while i >= 0 and j >= 0:
            if chessboard[i][j] == 'Q':
                return False
            j -= 1
            i -= 1
        
        i, j = row - 1, col + 1
        while i >= 0 and j < n:
            if chessboard[i][j] == 'Q':
                return False
            i -= 1
            j += 1
        
        return True
    
    def backtracking(self, n, row, chessboard, result):
        if row == n:
            result.append(chessboard[:])
            return
        
        for col in range(n):
            if self.isValid(row, col, chessboard, n):
                chessboard[row] = chessboard[row][:col] + 'Q' + chessboard[row][col+1:]
                self.backtracking(n, row + 1, chessboard, result)
                chessboard[row] = chessboard[row][:col] + '.' + chessboard[row][col+1:]

    def solveNQueens(self, n: int) -> List[List[str]]:
        result = []
        chessboard = ['.' * n for _ in range(n)]
        self.backtracking(n, 0, chessboard, result)
        return result
        

反思

在给出的代码中没有对同行进行检查,因为在单层的搜索过程中,每一层递归只会选择同一行里的一个元素,不用进行去重。

37.解数独

题目链接

37.解数独

思想

本题需要两个for循环+递归,一个for循环遍历行,一个for循环遍历列,递归来枚举9个数字。
在这里插入图片描述
图解来自 代码随想录

回溯三部曲:

  1. 参数和返回值:返回值是bool类型的(找到一个结果就立刻返回),参数是棋盘
    两层for循环,遍历棋盘中的每一个位置。
  2. 终止条件:不用特意写了。
  3. 单层搜索逻辑:遇到“.”开始处理,遍历1~9(字符类型的),判断在该位置放入数字后是否合法【用isValid判断】,如果合法,把数字放入数独。进入下一层递归,注意用result接一下返回值,如果result为true,返回true【找到结果了立刻return】。把数独改回去【回溯】。如果1-9都不合法,return false。直到把棋盘填满,return true。

isValid的实现:
判断是否合法的三个维度:同行是否重复;同列是否重复;九宫格内是否重复。

题解

// cpp
class Solution {
private:
    bool isValid(int row, int col, char val, vector<vector<char>>& board) {
        for (int j = 0; j < 9; j++) {
            if (board[row][j] == val) return false;
        }
        for (int i = 0; i < 9; i++) {
            if (board[i][col] == val) return false;
        }
        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for (int i = startRow; i < startRow + 3; i++) {
            for (int j = startCol; j < startCol + 3; j++) {
                if (board[i][j] == val) return false;
            }
        }
        return true;
    }
    bool backtracking(vector<vector<char>>& board) {
        for (int i = 0; i < board.size(); i++) {
            for (int j = 0; j < board[0].size(); j++) {
                if (board[i][j] == '.') {
                    for (char k = '1'; k <= '9'; k++) {
                        if (isValid(i, j, k, board)) {
                            board[i][j] = k;
                            if (backtracking(board)) return true;
                            board[i][j] = '.';
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};
# python
class Solution:
    def backtracking(self, board):
        for i in range(len(board)):
            for j in range(len(board[0])):
                if board[i][j] == '.':
                    for k in range(1, 10):
                        if self.isValid(i, j, k, board):
                            board[i][j] = str(k)
                            if self.backtracking(board):
                                return True
                            board[i][j] = '.'
                    return False
        return True
    
    def isValid(self, row, col, val, board):
        for j in range(9):
            if board[row][j] == str(val):
                return False
        
        for i in range(9):
            if board[i][col] == str(val):
                return False
        
        startRow = int(row / 3) * 3
        startCol = int(col / 3) * 3
        for i in range(startRow, startRow + 3):
            for j in range(startCol, startCol + 3):
                if board[i][j] == str(val):
                    return False
        return True
    
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        self.backtracking(board)
        

反思

注意本题的递归函数的返回值是bool,之前我们遇到的回溯问题大多是void,因为之前需要搜索所有的节点,拿到所有的结果,而本题我们找到一个符合要求的结果就可以返回了,返回的bool类型数据可以作为找到结果的标记。
本题在N皇后的区别在于,和N皇后差了一个维度。
注意backtracking在哪里return,return什么很重要,容易弄错。
注意如何计算九宫格的起始行和起始列。

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值