今天来盘一盘 **回溯 ** 这类题目
使用python刷题分类整理的笔记,请参考: https://github.com/lxztju/leetcode-algorithm/tree/v1
回溯法其实就是暴力法。经常使用的暴力法就是多层循环, 但是面对树形结构的话很难采用循环的方式进行遍历。这时就要采用递归回溯的方法实现暴力法。
这类题目看似比较难,但是其实时很简单规范的套路性的代码, 下边几道题会有一个固定的模板套路, 请耐心体会。
class Solution {
public:
vector letterCombinations(string digits) {
// 构建字典映射
unordered_map character2Num({
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
});
// 定义返回值
vector res;
string path;
backtrace(digits, res, 0, path, character2Num);
return res;
}
void backtrace(string digits, vector& res, int index, string path, unordered_map& character2Num){
// 回溯递归函数
if (digits.empty()) return ;
// 如果遍历完成所有的数字,就保存得到的字母组合。
if (index == digits.size()) {
res.push_back(path);
return ;
}
// 得到某个数字映射得到的字符串
auto characters = character2Num[digits[index]];
// 遍历这个字符串,相当于树形结构中的一层
for (int i = 0; i < characters.size(); i++){
path += characters[i];
// 向树的下一层进行遍历
backtrace(digits, res, index + 1, path, character2Num);
// 回溯到上一层的时候,需要去除这一层的元素。
path.pop_back();
}
}
};
class Solution {
public:
vector restoreIpAddresses(string s) {
vector< string> res;
string path;
backtrace(s, res, path, 0, 0);
return res;
}
void backtrace(string& s, vector& res, string path, int cnt, int startindex){
// cnt表示分割的部分的数目,一共需要分割为4部分, startindex为接下来这一部分的开始位置
if (cnt == 4){
if (startindex == s.size())
res.push_back(path);
return;
}
for (int i = startindex; i < s.size(); i++){
// 每一部分的最长长度为3
if (i - startindex == 3) break;
auto prepath = path;
auto substring = s.substr(startindex, i - startindex + 1);
// 如果长度部位0, 且开头的数字为0, 不合法的ip
if ( i - startindex > 0 && s[startindex] == '0') break;
// 如果大于255直接返回。
if (stoi(substring) > 255 ) break;
path += substring;
if (i != s.size() - 1)
path += ".";
backtrace(s, res, path, cnt+1, i + 1);
path = prepath;
}
}
};
class Solution {
public:
vector> partition(string s) {
// 得到所有的子字符串,判断得到的是否都是回文串。
vector> res;
vector palstring;
string path;
backtrace(s, res, palstring, 0);
return res;
}
void backtrace(string& s, vector>& res, vector& palstring, int startindex){
if (startindex == s.size()){
res.push_back(palstring);
return ;
}
for (int i = startindex; i < s.size(); i++){
auto substring = s.substr(startindex, i - startindex + 1);
if ( ! ispalindrome(substring)) continue;
palstring.push_back(substring);
backtrace(s, res, palstring, i + 1);
palstring.pop_back();
}
}
bool ispalindrome(string s){
int l = 0, r = s.size() - 1;
while ( l < r){
if (s[l] != s[r])
return false;
l++; r--;
}
return true;
}
};
class Solution {
public:
vector> permute(vector& nums) {
vector> res;
vector path;
unordered_set visited;
backtrace(nums, res, path, visited, 0);
return res;
}
void backtrace(vector& nums, vector>& res, vector& path, unordered_set& visited, int index){
if (index == nums.size()){
res.push_back(path);
return ;
}
for (int i = 0; i< nums.size(); i++){
if(visited.find(nums[i]) != visited.end()) continue;
visited.insert(nums[i]);
path.push_back(nums[i]);
backtrace(nums, res, path, visited, index+1);
visited.erase(nums[i]);
path.pop_back();
}
}
};
class Solution {
public:
vector> permuteUnique(vector& nums) {
sort(nums.begin(), nums.end());
vector path;
vector< vector > res;
unordered_set visited;
backtrace(nums, res, path, visited, 0);
return res;
}
void backtrace(vector& nums, vector>& res, vector& path, unordered_set& visited, int index){
if (index == nums.size()){
res.push_back(path);
return ;
}
for (int i=0; i< nums.size(); i++){
if (visited.find(i) != visited.end()) continue;
// 这里采用nums中第i-1个元素如果没有访问过,说明在之后依然会出现,造成重复。
if (i > 0 && nums[i] == nums[i-1] && visited.find(i - 1) == visited.end()) continue;
visited.insert(i);
path.push_back(nums[i]);
backtrace(nums, res, path, visited, index+1);
path.pop_back();
visited.erase(i);
}
}
};
class Solution {
public:
vector> combine(int n, int k) {
vector> res;
vector path;
baketrace(n, k, 0, res, path);
return res;
}
void baketrace(int n, int k, int index, vector>& res, vector& path){
if (k == 0){
res.push_back(path);
return;
}
// 如果剩余的数字不够组成k个数字的组合,那么直接返回
if (n - index + 1 < k) return ;
for(int i = 1; i <= n; i++){
if (i <= index) continue;
path.push_back(i);
baketrace(n, k-1, i, res, path);
path.pop_back();
}
}
};
class Solution {
public:
vector> combinationSum(vector& candidates, int target) {
vector> res;
vector path;
backtrace(candidates, res, path, target, 0);
return res;
}
void backtrace(vector& candidates, vector>&res, vector& path, int target, int index){
if (target == 0){
res.push_back(path);
return ;
}
for (int i = 0; i< candidates.size(); i++){
if (candidates[i] > target) continue;
if (i < index) continue;
path.push_back(candidates[i]);
backtrace(candidates, res, path, target- candidates[i], i);
path.pop_back();
}
}
};
class Solution {
public:
vector> combinationSum2(vector& candidates, int target) {
// 每个数字只可以使用一次,并且可能含有重复的数字
vector> res;
vector path;
// 排序去重
sort(candidates.begin(), candidates.end());
unordered_set visited;
backtrace(candidates, res, path, visited, 0, target);
return res;
}
void backtrace(vector& candidates, vector>&res, vector& path, unordered_set& visited, int index, int target){
if(target == 0){
res.push_back(path);
return;
}
for (int i = index; i < candidates.size(); i++){
if (candidates[i] > target) break;
if ( i > 0 && candidates[i] == candidates[i-1] && visited.find(i-1) == visited.end()) continue;
path.push_back(candidates[i]);
visited.insert(i);
backtrace(candidates, res, path, visited, i+1, target - candidates[i]);
path.pop_back();
visited.erase(i);
}
}
};
class Solution {
public:
vector> combinationSum3(int k, int n) {
if ( k > 9 || n > 45) return {};
vector> res;
vector path;
vector candidates({1,2,3,4,5,6,7,8,9});
backtrace(candidates, res, path, 0, n, k);
return res;
}
void backtrace(vector& candidates, vector>&res, vector& path, int index, int target, int depth){
if(target == 0 && depth == 0){
res.push_back(path);
return;
}
for (int i = index; i < candidates.size(); i++){
if (candidates[i] > target) break;
path.push_back(candidates[i]);
backtrace(candidates, res, path, i+1, target - candidates[i], depth-1);
path.pop_back();
}
}
};
class Solution {
public:
vector> subsets(vector& nums) {
vector> res;
vector path;
backtrace(nums, res, path, 0);
return res;
}
void backtrace(vector& nums, vector>& res, vector& path, int index ){
res.push_back(path);
for (int i = index; i < nums.size(); i++){
path.push_back(nums[i]);
backtrace(nums, res, path, i+1);
path.pop_back();
}
}
};
class Solution {
public:
vector> subsetsWithDup(vector& nums) {
// 同一层中不能有相同的元素, 排序去重
sort(nums.begin(), nums.end());
vector> res;
vector path;
unordered_set visited;
backtrace(nums, res, path, visited, 0);
return res;
}
void backtrace(vector& nums, vector>& res, vector& path,unordered_set& visited, int index){
res.push_back(path);
for (int i = index; i< nums.size(); i++){
if (i > 0 && nums[i] == nums[i-1] && visited.find(i - 1) == visited.end()) continue;
visited.insert(i);
path.push_back(nums[i]);
backtrace(nums, res, path, visited, i + 1);
path.pop_back();
visited.erase(i);
}
}
};
class Solution {
public:
bool exist(vector>& board, string word) {
vector> offsets({ {-1, 0}, {0, 1}, {1, 0}, {0 ,-1} });
vector> visited(board.size(), vector(board[0].size(), 0));
for (int i = 0; i< board.size(); i++){
for (int j = 0; j< board[0].size(); j++){
if (board[i][j] == word[0]){
if (backtrace(board, word, visited, offsets, i, j, 1))
return true;
}
}
}
return false;
}
bool backtrace(vector>& board, string& word, vector>& visited, vector>& offsets, int x, int y, int index){
// if (board[x][y] != word[index]) return false;
visited[x][y] = 1;
if (index == word.size()) return true;
for (auto off :offsets){
int new_x = x + off.first;
int new_y = y + off.second;
if (!inboard(board, new_x, new_y)) continue;
if (visited[new_x][new_y] == 1) continue;
if (board[new_x][new_y] != word[index]) continue;
if (backtrace(board, word, visited, offsets, new_x, new_y, index + 1))
return true;
}
visited[x][y] = 0;
return false;
}
bool inboard(vector>& board, int x, int y){
return (x >= 0 && x < board.size()) && ( y >= 0 && y < board[0].size());
}
};
class Solution {
public:
int numIslands(vector>& grid) {
int m = grid.size();
int n = grid[0].size();
int res = 0;
vector> offsets({ {-1, 0},{1, 0}, {0, 1}, {0, -1} });
for (int i = 0; i < m; i ++){
for (int j = 0; j < n; j++){
if (grid[i][j] == '1'){
res++;
backtrace(grid, i, j, m, n, offsets);
}
}
}
return res;
}
void backtrace(vector>& grid, int x, int y, int m, int n, vector>& offsets){
grid[x][y] = '0';
for (auto off: offsets){
int new_x = x + off.first;
int new_y = y + off.second;
if (!inboard(m, n, new_x, new_y) || grid[new_x][new_y] == '0') continue;
backtrace(grid, new_x, new_y, m, n, offsets);
}
}
bool inboard(int m, int n, int x, int y){
return (x >= 0 && x < m) && (y >= 0 && y < n);
}
};
class Solution {
public:
void solve(vector>& board) {
//找到与边界联通的O,标记为o,然后将剩余的O变为x,o变为O即可
if (board.empty()) return ;
vector> offsets({
{1, 0},{-1, 0}, {0, 1}, {0, -1}});
int m = board.size(), n = board[0].size();
// 遍历边界
for ( int i = 0; i< m; i++){
if (board[i][0] == 'O')
backtrace(board, i, 0, offsets);
if (board[i][n-1] == 'O')
backtrace(board, i, n-1, offsets);
}
for (int j = 0; j < n; j++){
if (board[0][j] == 'O')
backtrace(board, 0, j, offsets);
if (board[m- 1][j] == 'O')
backtrace(board, m-1, j, offsets);
}
// 替换内部的O
for ( int i = 0;i< m; i++){
for (int j = 0; j < n; j++){
if (board[i][j] == 'O')
board[i][j] = 'X';
}
}
// 将边界的o换回O
for ( int i = 0;i< m; i++){
for (int j = 0; j < n; j++){
if (board[i][j] == 'o')
board[i][j] = 'O';
}
}
}
void backtrace(vector>& board, int x, int y, vector>& offsets){
board[x][y] = 'o';
for (auto off: offsets ){
int new_x = x + off.first;
int new_y = y + off.second;
if (! inboard(board, new_x, new_y) || board[new_x][new_y] != 'O') continue;
backtrace(board, new_x, new_y, offsets);
}
}
bool inboard(vector>& board, int x, int y){
return (x >= 0 && x < board.size()) && ( y >= 0 && y < board[0].size());
}
};
class Solution {
public:
vector> pacificAtlantic(vector>& matrix) {
//使用两个数组分别表示能流到大西洋atlantic与太平洋pacific的位置
if (matrix.empty()) return {};
int m = matrix.size(), n = matrix[0].size();
vector> atlantic(m, vector(n, false));
vector> pacific(m, vector(n, false));
vector> offsets({ {-1, 0},{0, -1},{1, 0},{0, 1}});
vector> res;
for (int i = 0; i< m; i++){
if (! pacific[i][0])
backtrace(matrix, i, 0, pacific, offsets);
if (! atlantic[i][n-1])
backtrace(matrix, i, n-1, atlantic, offsets);
}
for (int j = 0; j< n; j++){
if (! pacific[0][j])
backtrace(matrix, 0, j, pacific, offsets);
if (! atlantic[m-1][j])
backtrace(matrix, m-1, j, atlantic, offsets);
}
for (int i =0; i>& matrix, int x, int y, vector>& ocean, vector>& offsets){
ocean[x][y] = true;
for (auto off: offsets){
int new_x = x + off.first;
int new_y = y + off.second;
if (!inboard(matrix, new_x, new_y) || matrix[new_x][new_y] < matrix[x][y] || ocean[new_x][new_y]) continue;
backtrace(matrix, new_x, new_y, ocean, offsets);
}
}
bool inboard(vector>& matrix, int x, int y){
return (x >= 0 && x < matrix.size()) && ( y >= 0 && y < matrix[0].size());
}
};
class Solution {
public:
vector> queues;
vector> solveNQueens(int n) {
//主对角线i- j为常数,范围为1-n到n-1
// 副对角线i+j为常数,范围为0到2*n-2
vector diag(2*n-1, false); //主对角线,i - j + n - 1为其索引对应的对角线
vector otherdiag(2*n-1, false); // 副对角线i + j 为其索引对应的对角线。
vector queue(n, -1); //表示每行放置在哪一列。
unordered_set cols; // 表示已经放置的列
backtrace(n, diag, otherdiag, queue, cols, 0);
return queues;
}
void backtrace(int n, vector& diag, vector& otherdiag, vector& queue, unordered_set cols, int row){
if (row == n){
queues.push_back(generateString(queue, n));
return ;
}
for (int j = 0; j < n; j++){
if (diag[row-j+n-1] || otherdiag[row+j] || cols.find(j) != cols.end()) continue;
diag[row-j+n-1] = true;
otherdiag[row+j] = true;
cols.insert(j);
queue[row] = j;
backtrace(n, diag, otherdiag, queue, cols, row + 1);
diag[row-j+n-1] = false;
otherdiag[row+j] = false;
queue[row] = -1;
cols.erase(j);
}
}
vector generateString(vector& queue, int n){
vector res(n, string(n, '.'));
for (int i=0; i< n; i++)
res[i][queue[i]] = 'Q';
return res;
}
};
class Solution {
public:
int totalNQueens(int n) {
vector diag(2*n - 1, false); // 主对角线是否占用, i-j+n-1为其主对角线的索引
vector otherdiag(2*n-1, false); //副对角线,i+j为其索引
unordered_set cols; // 保存已经被占用的列
vector queue(n, -1); //表示每个元素分别放置在第j列的位置。
int res = 0;
backtrace(n, diag, otherdiag, cols, queue, res, 0);
return res;
}
void backtrace(int n, vector& diag, vector& otherdiag, unordered_set& cols, vector& queue, int& res, int row){
if (row == n){
res++;
return ;
}
// 第row行可以放的列的位置,遍历查找
for (int i=0; i< n; i++){
if(diag[row - i + n - 1] || otherdiag[row + i] || cols.find(i) != cols.end()) continue;
diag[row - i + n - 1 ] = true;
otherdiag[row + i] = true;
cols.insert(i);
queue[row] = i;
backtrace(n, diag, otherdiag, cols, queue, res, row+1);
diag[row - i + n - 1 ] = false;
otherdiag[row + i] = false;
cols.erase(i);
queue[row] = -1;
}
}
};
class Solution {
public:
bool valid = false;
void solveSudoku(vector>& board) {
vector> rows(9, unordered_set()); //每行出现过的数字
vector> cols(9, unordered_set()); //每列出现过的数字
vector< unordered_set > grids(9, unordered_set()); // 第i个3x3的格子出现过的数字,其索引为(i/3)*3 + j/3
vector> spaces;
for ( int i = 0; i <9; i++){
for (int j = 0; j < 9; j++){
if (board[i][j] == '.')
spaces.push_back(make_pair(i, j));
else{
int digit = int(board[i][j] - '0');
rows[i].insert(digit);
cols[j].insert(digit);
grids[(i/3)*3+j/3].insert(digit);
}
}
}
backtrace(board, rows, cols, grids, spaces, 0);
return;
}
void backtrace(vector>& board, vector>& rows, vector>& cols, vector>& grids, vector>& spaces, int index){
if ( index == spaces.size()) {
valid = true;
return ;
}
int i = spaces[index].first;
int j = spaces[index].second;
// 放置 k 这个数字
for (int k = 1; k <= 9; k++){
if (valid) break;
if ( rows[i].find(k) != rows[i].end() || cols[j].find(k) != cols[j].end() || grids[(i/3)*3+j/3] .find(k) != grids[(i/3)*3+j/3].end() )
continue;
rows[i].insert(k);
cols[j].insert(k);
grids[(i/3)*3+j/3].insert(k);
board[i][j] = (char) ('0' + k);
backtrace(board, rows, cols, grids, spaces, index+1);
rows[i].erase(k);
cols[j].erase(k);
grids[(i/3)*3+j/3].erase(k);
// board[i][j] = '.';
}
}
};
微信公众号: 小哲AI
GitHub地址: https://github.com/lxztju/leetcode-algorithm
csdn博客: https://blog.csdn.net/lxztju
知乎专栏: 小哲AI
AI研习社专栏:小哲AI