碎碎念:加油加油,继续坚持
参考:代码随想录
491.递增子序列
题目链接
491.递增子序列
思想
注意结果中不能有重复的子集。
不能排序后处理,因为进行排序以后改变元素顺序,求的递增子序列会改变。
树形图:
结果分布在节点上,只是对节点里面的数字有些要求。
定义一个一维数组path,一个二维数组result。
一进来递归的时候就收集结果:如果path.size>1,就把当前path放到result中。
定义一个set,用来判断当前取到的数之前是否取过。【之前的90.子集II 用的是used数组,因为之前的题目我们可以用排序使得相同的数字相邻,这里不能用排序了,因为排序会改变递增子序列】
回溯三部曲:
- 参数和返回值:参数有nums,startIndex
- 终止条件:如果startIndex>=nums.size
- 单层搜索逻辑: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] * 201for i in range(startIndex, len(nums)):if (self.path and nums[i] < self.path[-1] or used[nums[i] + 100] == 1):continueused[nums[i] + 100] = 1self.path.append(nums[i])self.backtracking(nums, i+1)# used[nums[i] + 100] = 0 不用写了 因为每一层都会定义一个新uesdself.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。
回溯三部曲:
- 参数和返回值:参数是nums和uesd数组
- 终止条件:如果path的长度和nums的长度相等的时候,收获结果,return
- 单层搜索逻辑: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[:])returnfor i in range(0, len(nums)):if used[i] == True:continueused[i] = Truepath.append(nums[i])self.backtracking(nums, used, path, result)path.pop()used[i] = Falsedef 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。
回溯三部曲:
- 参数和返回值:参数是nums和uesd数组
- 终止条件:如果path的长度和nums的长度相等的时候,收获结果,return
- 单层搜索逻辑: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[:])returnfor i in range(0, len(nums)):if i > 0 and nums[i] == nums[i - 1] and uesd[i-1] == False:continueif uesd[i] == True:continueuesd[i] = Truepath.append(nums[i])self.backtracking(nums, uesd, path, result)path.pop()uesd[i] = Falsedef 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。
回溯三部曲:
- 参数和返回值:传入棋盘,棋盘大小,row(行数)
- 终止条件:如果row==n,收获结果到结果集,return
- 单层搜索逻辑: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 Falsei, j = row - 1, col - 1while i >= 0 and j >= 0:if chessboard[i][j] == 'Q':return Falsej -= 1i -= 1i, j = row - 1, col + 1while i >= 0 and j < n:if chessboard[i][j] == 'Q':return Falsei -= 1j += 1return Truedef backtracking(self, n, row, chessboard, result):if row == n:result.append(chessboard[:])returnfor 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个数字。
图解来自 代码随想录
回溯三部曲:
- 参数和返回值:返回值是bool类型的(找到一个结果就立刻返回),参数是棋盘
两层for循环,遍历棋盘中的每一个位置。 - 终止条件:不用特意写了。
- 单层搜索逻辑:遇到“.”开始处理,遍历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 Trueboard[i][j] = '.'return Falsereturn Truedef isValid(self, row, col, val, board):for j in range(9):if board[row][j] == str(val):return Falsefor i in range(9):if board[i][col] == str(val):return FalsestartRow = int(row / 3) * 3startCol = int(col / 3) * 3for i in range(startRow, startRow + 3):for j in range(startCol, startCol + 3):if board[i][j] == str(val):return Falsereturn Truedef 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什么很重要,容易弄错。
注意如何计算九宫格的起始行和起始列。