碎碎念:加油加油,继续坚持
参考:代码随想录
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] * 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.全排列
题目链接
思想
本题没有重复的元素,那么就不用考虑去重。
排列和组合的区别:[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[:])
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
题目链接
思想
本题和上一题的区别:本题给的集合有重复元素,而题目有要求给出的排列不重复,所以关键点在于去重。
树形图:
在主函数里要做一下排序,方便去重。
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[:])
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皇后
题目链接
思想
树形图:
定义一个三维数组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 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.解数独
题目链接
思想
本题需要两个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 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什么很重要,容易弄错。
注意如何计算九宫格的起始行和起始列。