17. 电话号码的字母组合(回溯)
回溯法,用map记录数字和字母的映射关系,可以用大括号嵌套的方式对map进行列表初始化
class Solution {
public:
//map放在类内,方便成员函数调用
map<char, string> phoneNum = {
{'2', "abc"},{'3', "def"},{'4', "ghi"},{'5', "jkl"},{'6', "mno"},{'7', "pqrs"},{'8', "tuv"},{'9', "wxyz"}
};
vector<string> ans;
string temp;
void DFS(int index, string digits) {
if (index == digits.size()){
ans.push_back(temp);
return;
}
else{
char now= digits[index]; //不能写成now= digits[index]-'0';!因为map需要输入字符,否则程序自动结束!
for (int i = 0; i < phoneNum[now].size(); i++) {
//string支持字符类型弹入弹出
temp.push_back(phoneNum[now][i]);
DFS(index+1, digits);
temp.pop_back();
}
}
}
vector<string> letterCombinations(string digits) {
if (digits.empty())
return ans;
DFS(0, digits);
return ans;
}
};
22. 括号生成(回溯)
回溯法枚举所有括号情况,如果左括号等于右括号,只能增加左括号,如果左括号多都可以加
class Solution {
public:
vector<string> ans;
void generate(string str, int left, int right)
{
if (left==0&&right==0){
ans.push_back(str);
return;
}
else if (left == right)
generate(str + '(', left - 1, right);
else if (left < right){
if (left > 0)
generate(str + '(', left - 1, right);
generate(str + ')', left, right - 1);
}
}
vector<string> generateParenthesis(int n) {
string str;
generate(str, n, n);
return ans;
}
};
39. 组合总和(回溯)
1、递归函数包括原数组、已组合数组、目标值和当前序号
2、每次递归包括两种选择,选择当前序号的数,不选择当前序号的数跳过
3、边界判断包括最后目标值正好等于0,序号越界
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx) {
if (idx == candidates.size())
return;
if (target == 0) {
ans.emplace_back(combine);
return;
}
// 直接跳过
dfs(candidates, target, ans, combine, idx + 1);
// 选择当前数
if (target - candidates[idx] >= 0) {
combine.emplace_back(candidates[idx]);
dfs(candidates, target - candidates[idx], ans, combine, idx);
combine.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> combine;
dfs(candidates, target, ans, combine, 0);
return ans;
}
};
40. 组合总和 II(回溯)
1、上一题是给的数不重复,但是数可以不限制选,这题是给的数重复,只能选一次,还是用回溯
2、回溯前先排序,把给的数重复的放在一块,然后扫一遍,统计每个数出现的次数
3、递归前要么跳过该数,要么递归使用该数的最多次,用统计过的二维数组判断还能用几次递归
class Solution {
private:
vector<pair<int, int>> freq;
vector<vector<int>> ans;
vector<int> sequence;
public:
void dfs(int pos, int rest) {
if (rest == 0) {
ans.push_back(sequence);
return;
}
if (pos == freq.size() || rest < freq[pos].first)
return;
dfs(pos + 1, rest);
int most = min(rest / freq[pos].first, freq[pos].second);
for (int i = 1; i <= most; ++i) {
sequence.push_back(freq[pos].first);
dfs(pos + 1, rest - i * freq[pos].first);
}
for (int i = 1; i <= most; ++i)
sequence.pop_back();
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
for (int num: candidates) {
if (freq.empty() || num != freq.back().first) {
freq.emplace_back(num, 1);
} else
++freq.back().second;
}
dfs(0, target);
return ans;
}
};
46. 全排列(回溯)
1、用二维数组代替输出数组。
2、用左右标记代替标记数组,但这样的操作输出不是有序,若有序应使用标记数组。
void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){
// 所有数都填完了
if (first == len) {
res.emplace_back(output);
return;
}
for (int i = first; i < len; ++i) {
// 动态维护数组
swap(output[i], output[first]);
// 继续递归填下一个数
backtrack(res, output, first + 1, len);
// 撤销操作
swap(output[i], output[first]);
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int> > res;
backtrack(res, nums, 0, (int)nums.size());
return res;
}
48. 旋转图像(辅助数组)
找规律得到数组旋转后的通项公式,建立一个辅助数组保存
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// C++ 这里的 = 拷贝是值拷贝,会得到一个新的数组
auto matrix_new = matrix;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix_new[j][n - i - 1] = matrix[i][j];
}
}
// 这里也是值拷贝
matrix = matrix_new;
}
};
51. N 皇后(回溯)
1、N皇后使用回溯法,逐个判断当前点行列、正反对角线都没有重复的点就往下搜索
2、行用遍历保证不同,列和正反对角线用三个数组标记,正反对角线的规律是行列差、行列和相同,值相同说明重复了
class Solution {
public:
void dfs(int row, int n, vector<vector<string>>& res, vector<string> a, vector<int> col, vector<int> dg, vector<int> adg){
if(row == n){ //符合条件,直接存进数组
res.push_back(a);
return ;
}
for(int i = 0; i < n; i++){
if(!col[i] && !dg[row - i + n] && !adg[row + i]){//说明该位置合法
//这里注意一下,因为行与列的差值可能为负,所以dg下标要加一个n
col[i] = dg[row - i + n] = adg[row + i] = 1; //将该位置的列,正对角线,反对角线的值都置1
a[row][i] = 'Q'; //设置Q
dfs(row + 1, n, res, a, col, dg, adg); //深度优先搜索
col[i] = dg[row - i + n] = adg[row + i] = 0; //重置,以便下次查找该行合适位置
a[row][i] = '.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> res; //存储最终结果
vector<string> a(n, string(n, '.')); //临时结果
vector<int> col(n, 0); //存储已有的列值
vector<int> dg(2 * n, 0); //存储已有的正对角线的值
vector<int> adg(2 * n, 0); //存储已有的反对角线的值
dfs(0, n, res, a, col, dg, adg); //深度优先搜索回溯
return res;
}
};
54. 螺旋矩阵(模拟)
用二维数组存四个方向,vector<vector>,用now记录当前方向,一直走到矩阵边界换方向
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
//按照题目要求搜一遍即可
//判空必须放在开头,并且不能用变量去表示矩阵的长宽
if(matrix.size()==0||matrix[0].size()==0) return {};
int m=matrix.size(),n=matrix[0].size();
vector<vector<int>> flag(m,vector<int>(n,0));
vector<vector<int>> dir={{0,1},{1,0},{0,-1},{-1,0}};
int count=0,now=0,x=0,y=0;
vector<int> ans;
while(count<n*m){
ans.push_back(matrix[x][y]);
flag[x][y]=1;
if(x+dir[now][0]<0||x+dir[now][0]>=m||y+dir[now][1]<0||y+dir[now][1]>=n||flag[x+dir[now][0]][y+dir[now][1]]==1)
now=(now+1)%4;
x+=dir[now][0];
y+=dir[now][1];
count++;
}
return ans;
}
};
59. 螺旋矩阵 II(模拟)
碰到边界或者已存在则换方向
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int maxNum = n * n;
int curNum = 1;
vector<vector<int>> matrix(n, vector<int>(n));
int row = 0, column = 0;
vector<vector<int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上
int directionIndex = 0;
while (curNum <= maxNum) {
matrix[row][column] = curNum;
curNum++;
int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] != 0) {
directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向
}
row = row + directions[directionIndex][0];
column = column + directions[directionIndex][1];
}
return matrix;
}
};
78. 子集(回溯)
由于给的不存在重复,也不存在是否重复添加问题,所以就选择加或不加两种情况
class Solution {
public:
vector<int> t;
vector<vector<int>> ans;
void dfs(int cur, vector<int>& nums) {
if (cur == nums.size()) {
ans.push_back(t);
return;
}
t.push_back(nums[cur]);
dfs(cur + 1, nums);
t.pop_back();
dfs(cur + 1, nums);
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(0, nums);
return ans;
}
};
79. 单词搜索(深搜)
遍历所有格子作为起点深搜,只要找到就返回
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
for(int i=0;i<board.size();i++)
for(int j=0;j<board[0].size();j++)
if(dfs(board,word,i,j,0))
return true;
return false;
}
bool dfs(vector<vector<char>>& board, string word,int i,int j,int k)
{
if (i<0||i>=board.size()||j<0||j>=board[0].size()||board[i][j]!=word[k]) return false;
//k等于word大小不是board大小
if (k==word.size()-1) return true;
int res;
char temp=board[i][j];
board[i][j]='0';
vector<int> x={-1,1,0,0};
vector<int> y={0,0,1,-1};
//用数组记录变化,不用写四个dfs
for(int l=0;l<4;l++) {
//用m,n记录暂时变化,保证不改变原i,j
int m=x[l]+i,n=y[l]+j;
if (dfs(board,word,m,n,k+1)) return true;
}
board[i][j]=temp;
return false;
}
};
90. 子集 II(排序+回溯)
1、与上一个子集问题区别在于,可能给定元素中包含重复的元素
2、带重复元素问题必须先排序,把重复元素挨在一起方便判断
3、排序后回溯时进行剪枝,如果当前元素和之前相同,并且上一个元素没有选择,则不回溯了返回
4、回溯函数加上一个bool判断上一个值是否已经被选择
class Solution {
public:
vector<int> t;
vector<vector<int>> ans;
void dfs(bool choosePre, int cur, vector<int> &nums) {
if (cur == nums.size()) {
ans.push_back(t);
return;
}
dfs(false, cur + 1, nums);
if (!choosePre && cur > 0 && nums[cur - 1] == nums[cur]) {
return;
}
t.push_back(nums[cur]);
dfs(true, cur + 1, nums);
t.pop_back();
}
vector<vector<int>> subsetsWithDup(vector<int> &nums) {
sort(nums.begin(), nums.end());
dfs(false, 0, nums);
return ans;
}
};
93. 复原 IP 地址(回溯)
1、用string存结果,用数组存临时的四个数,用回溯查找,如果找到第四段并且刚好遍历完字符串,就把数组转成字符串返回
2、如果没找到四串就到结尾则返回,碰到前导0立马下一轮
3、剩下的进行回溯,一个个数字加入并且回溯
class Solution {
private:
static constexpr int SEG_COUNT = 4;
private:
vector<string> ans;
vector<int> segments;
public:
void dfs(const string& s, int segId, int segStart) {
// 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
if (segId == SEG_COUNT) {
if (segStart == s.size()) {
string ipAddr;
for (int i = 0; i < SEG_COUNT; ++i) {
ipAddr += to_string(segments[i]);
if (i != SEG_COUNT - 1)
ipAddr += ".";
}
ans.push_back(move(ipAddr));
}
return;
}
// 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯
if (segStart == s.size())
return;
// 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
if (s[segStart] == '0') {
segments[segId] = 0;
dfs(s, segId + 1, segStart + 1);
}
// 一般情况,枚举每一种可能性并递归
int addr = 0;
for (int segEnd = segStart; segEnd < s.size(); ++segEnd) {
addr = addr * 10 + (s[segEnd] - '0');
if (addr > 0 && addr <= 0xFF) {
segments[segId] = addr;
dfs(s, segId + 1, segEnd + 1);
} else
break;
}
}
vector<string> restoreIpAddresses(string s) {
segments.resize(SEG_COUNT);
dfs(s, 0, 0);
return ans;
}
};
200. 岛屿数量(深搜)
1、遍历碰到所有为1的岛屿设为起点,岛屿数量加1,传入该点行列开始深搜
2、将这点附近能走到的所有岛屿设为0,返回
class Solution {
private:
void dfs(vector<vector<char>>& grid, int r, int c) {
int nr = grid.size();
int nc = grid[0].size();
grid[r][c] = '0';
if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c);
if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c);
if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1);
if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1);
}
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
};
240. 搜索二维矩阵 II(线性查找)
1、从左上角开始搜索,判断大小不知道走哪边,深搜广搜时间复杂度较高
2、从右上角开始搜索,可以根据大小进行线性搜索,取巧方式较深搜复杂度较低
bool searchMatrix(vector<vector<int>>& matrix, int target) {
//前面必须先判断vector是否为空!!否则对vector取长度会造成内存崩溃
if (matrix.empty()) return false;
int n = matrix.size(),m = matrix[0].size(),x = 0, y = m - 1;
while (x < n && y >= 0){
if (matrix[x][y] == target) return true;
if (matrix[x][y] > target) y--;
//这里必须写else!!!!不能用多个if进行判断,否则造成xy已经修改,内存崩溃
else x++;
}
return false;
}
301. 删除无效的括号(深搜)
class Solution {
public:
unordered_set<string> sets;
/*
* @param s: 源字符串
* @param index: 当前源字符串索引
* @param str: 记录结果
* @param el: 当前可以删除的'('的数量
* @param er: 当前可以删除的')'的数量
* @param cntl: 记录当前str中错误的'('的数量
* @param cntr: 记录当前str中错误的')'的数量
*/
void dfs(string &s, int index, string &str, int el, int er, int cntl, int cntr){
// 剪枝
if(cntr > cntl || el < 0 || er < 0) return;
// 结束条件
if(index == s.length()){
if(cntl == 0 && cntr == 0)
sets.insert(str);
return;
}
// 当前字符不是括号,直接跳过
if(s[index] != '(' && s[index] != ')'){
str += s[index];
dfs(s, index+1, str, el, er, cntl, cntr);
str.erase(str.length()-1, 1);
}else{
// 不删除当前括号,需要记录当前str中错误的左右括号的数量
str += s[index];
int cl = cntl, cr = cntr;
if(s[index] == '(') cl++;
else{
if(cl == 0) cr++;
else cl--;
}
dfs(s, index+1, str, el, er, cl, cr);
str.erase(str.length()-1, 1);
// 删除当前括号,修改可删除的左右括号数量
if(s[index] == '(') --el;
else --er;
dfs(s, index+1, str, el, er, cntl, cntr);
}
}
vector<string> removeInvalidParentheses(string s) {
vector<string> res;
// 统计源字符串中无效括号数目
int el = 0, er = 0;
for(int i = 0; i < s.length(); ++i){
if(s[i] == '(') el++;
else if(s[i] == ')'){
if(el == 0) er++;
else el--;
}
}
string str = "";
dfs(s, 0, str, el, er, 0, 0);
for(auto it = sets.begin(); it != sets.end(); ++it)
res.push_back(*it);
return res;
}
};
498. 对角线遍历(矩阵模拟)
1、向右上遍历时(判断顺序不可颠倒,因为在遍历到右上角元素时,先判断“第一行”的话下标会溢出)
元素在最后一列,向下走
元素在第一行,向右走
2、向左下遍历时
元素在最后一行,向右
元素在第一列,向下
3、其余情况就是正常的向右上或者左下遍历
class Solution
{
public:
vector<int> findDiagonalOrder(vector<vector<int>>& matrix)
{
//判空
if (matrix.size() == 0) return {};
int m = matrix.size(),n = matrix[0].size(),x = 0,y = 0;
int count = m * n;
vector<int> ans;
for (int i = 0; i < count; ++i){
ans.push_back(matrix[x][y]);
if ((x+y) % 2 == 0){
if (y == n-1) //最后一列,向下
x++;
else if (x == 0)//第一行,向右
y++;
else //向右上{
x--;
y++;
}
}else{
if (x == m-1) //最后一行,向右
y++;
else if (y == 0)//第一列,向下
x++;
else //向左下{
x++;
y--;
}
}
}
return ans;
}
};
695. 岛屿的最大面积(深搜)
每次访问过的位置设置为0,记录访问过的最大面积
class Solution {
int dfs(vector<vector<int>>& grid, int cur_i, int cur_j) {
if (cur_i < 0 || cur_j < 0 || cur_i == grid.size() || cur_j == grid[0].size() || grid[cur_i][cur_j] != 1)
return 0;
grid[cur_i][cur_j] = 0;
int di[4] = {0, 0, 1, -1};
int dj[4] = {1, -1, 0, 0};
int ans = 1;
for (int index = 0; index != 4; ++index) {
int next_i = cur_i + di[index], next_j = cur_j + dj[index];
ans += dfs(grid, next_i, next_j);
}
return ans;
}
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int ans = 0;
for (int i = 0; i != grid.size(); ++i)
for (int j = 0; j != grid[0].size(); ++j)
ans = max(ans, dfs(grid, i, j));
return ans;
}
};