什么是回溯
通过探索所有可能的解,找到正确解的算法;如果当前解不满足条件,那么将舍弃该解并返回到上一步;
本质就是一个树形结构,利用深度优先搜索一条路走到黑的特性,不满足条件就舍弃该节点,并放回其父节点;
回溯&递归&贪心
回溯模板
void blacktrack(路径,选择列表){
if(满足条件){
保存路径
return;
}
for(选择:选择列表){
做选择;
blacktrack(路径,选择列表);
抛弃刚才的选择;
}
}
常见回溯算法题
- 组合总和 https://leetcode-cn.com/problems/combination-sum/
- 括号生成 https://leetcode-cn.com/problems/generate-parentheses/
- N皇后 https://leetcode-cn.com/problems/n-queens/
- 全排列 https://leetcode-cn.com/problems/permutations/
- ……
回溯题目
电话号码的字母组合
-
题目
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
-
示例
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”] -
要求
0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。 -
题目地址
https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/ -
分析
- 用容器将数字与字母对应起来,形成一个键值对;
2)方法1; 最暴力的方式是逐个用digits[0]中的某一个字母去对应digits[1]中的字母,即双重循环;时间复杂度为,O(n^2),可能会导致超时;
3) 方法2: 回溯法,当将digits[1]中的字母全部组合后,digits[0]回退到上一步,digits[1]回退到原始;
- 用容器将数字与字母对应起来,形成一个键值对;
-
代码
#include<iostream> #include<vector> #include<unordered_map> using namespace std; class Solution{ public: // C++11新特性 Solution() = default; ~Solution() = default; unordered_map<char, string> phoneMap = { {'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"}, {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"} }; void backTrack(string digits, unordered_map<char, string> phoneMap, vector<string> &combinations, int index, string tmpCombination){ //1. 定一个出口条件: digits到头就全部结束了 if( index == digits.length() ){ combinations.push_back(tmpCombination); return; } char digit = digits[index]; const string& letter = phoneMap.at(digit); //2. 回溯一定是有进出的,使用完之后必须及时抛弃 for( const char& lett : letter ){ tmpCombination.push_back(lett); backTrack(digits, phoneMap, combinations, index+1, tmpCombination); tmpCombination.pop_back(); } } vector<string> letterCombinations(string digits){ vector<string> combinations; if( digits.empty() ) return combinations; //回溯使用深度优先搜索 backTrack(digits, phoneMap, combinations, 0, ""); return combinations; } }; int main() { Solution so; //这里也可以使用键盘输入方式 vector<string> result = so.letterCombinations("23"); vector<string>::iterator it = result.begin(); for(;it != result.end();it++){ cout<<*it<<endl; } system("pause"); return 0; } /* 结果为: ad ae af bd be bf cd ce cf */
剩下的先写代码,后补充
括号生成
void dfsParenthesis(vector<string>& result, int n, int left, int right, string generate){
//括号左右数量相等;任意前缀左括号>=右括号数量
if(left>n || right >n || left<right) return;
if(left == n && right == n){ // n对括号已完成
result.push_back(generate);
}else{
dfsParenthesis(result, n, left+1,right, generate+"(");
dfsParenthesis(result, n, left,right+1, generate+")");
}
}
vector<string> leetcode::generateParenthesis(int n) {
vector<string> result;
int left = 0;
int right = 0;
string generate = "";
dfsParenthesis(result, n,left,right,generate);
return result;
}
组合
void dfsComBine(vector<vector<int>>& result, vector<int> nums, int k , int pos, vector<int>& tmp){
if(tmp.size() == k){
result.push_back(tmp);
return;
}
for(int i= pos; i<nums.size();i++){
tmp.push_back(nums[i]);
dfsComBine(result, nums, k, i+1, tmp);
tmp.pop_back();
}
}
vector<vector<int>>::leetcode combine(int n, int k) {
vector<vector<int>> result;
vector<int> nums;
vector<int> tmp;
for(int i=1;i<=n;i++)
nums.push_back(i);
dfsComBine(result, nums, k, 0, tmp);
return result;
}
组合总和
void dfsSum(vector<vector<int>>& result, int index, vector<int>& candidates, int target,vector<int>& candidate){
if(target < 0) return;
if(target == 0){ result.push_back(candidate); return;}
//这里还差一个条件
for(int i=index; i<candidates.size();i++){
candidate.push_back(candidates[i]);
//i不变是因为可以重复取值
dfsSum(result, i, candidates, target-candidates[i], candidate);
candidate.pop_back();
}
}
vector<vector<int>> leetcode::combinationSum(vector<int>& candidates, int target) {
//这里的出口是数字差
vector<vector<int>> result;
vector<int> candidate;
dfsSum(result, 0, candidates, target, candidate);
return result;
}
组合总和 II
void dfsSum2(vector<vector<int>>& result, int index, vector<int>& candidates, int target,vector<int>& candidate){
if(target < 0) return;
if(target == 0){ result.push_back(candidate); return;}
//这里还差一个条件
for(int i=index; i<candidates.size();i++){
//当前数字不能出现过
if(candidates[i]<=target){
if(i>index && candidates[i] == candidates[i-1]) continue;
candidate.push_back(candidates[i]);
//这里不可以取重复值
dfsSum2(result, i+1, candidates, target-candidates[i], candidate);
candidate.pop_back();
}
}
}
vector<vector<int>> leetcode::combinationSum2(vector<int>& candidates, int target) {
//去重复最简单的就是先排序,否则就需要一个flag去单独定义
sort(candidates.begin(), candidates.end());
vector<vector<int>> result;
vector<int> candidate;
dfsSum2(result, 0, candidates, target, candidate);
return result;
}
全排列
void dfsPremute(vector<int>& nums, vector<vector<int>>& result, vector<int>& tmpNum,vector<int>& flagNums){
if(tmpNum.size() == nums.size()){
result.push_back(tmpNum);
return;
}
//或者借助单队列执行
for(int i=0;i<nums.size();i++){
if(flagNums[i] == 1){continue;} //这里会浪费时间,因为被多次执行了
else{
tmpNum.push_back(nums[i]);
flagNums[i] = 1;
dfsPremute(nums, result, tmpNum, flagNums);
flagNums[i] = 0;
tmpNum.pop_back();
}
}
}
vector<vector<int>> leetcode::permute(vector<int>& nums) {
// 题目给出的是不含重复数字,所以这里就不需要排序啦
vector<vector<int>> result;
vector<int> tmp;
vector<int> flagNums(nums.size(), 0);
dfsPremute(nums, result, tmp,flagNums);
return result;
}
全排列 II
子集
void dfsSubsets(vector<vector<int>>& result, int pos, vector<int>& nums, vector<int>& tmp){
result.push_back(tmp);
for(int i=pos;i<nums.size();i++){
tmp.push_back(nums[i]);
dfsSubsets(result, i+1, nums, tmp);
tmp.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
vector<int> tmp;
dfsSubsets(result, 0, nums, tmp);
/*
result.push_back({});
for(int i = 0;i<nums.size();i++){
int tmpSize = result.size();
for(int j=0;j<tmpSize;j++){
vector<int> tmp(result[j]);
tmp.push_back(nums[i]);
result.push_back(tmp);
}
}*/
return result;
}
子集 II
void dfsSubsets(vector<vector<int>>& result, int pos, vector<int>& nums, vector<int>& tmp){
result.push_back(tmp);
//一个循环里面,pos是不会改变的
for(int i=pos;i<nums.size();i++){
if(i != pos && nums[i] == nums[i-1]) continue;
tmp.push_back(nums[i]);
dfsSubsets(result, i+1, nums, tmp);
tmp.pop_back();
}
}
vector<vector<int>>::leetcode subsetsWithDup(vector<int>& nums){
sort(nums.begin(),nums.end());
vector<vector<int>> result;
vector<int> tmp;
dfsSubsets(result, 0, nums, tmp);
return result;
}
单词搜索
bool dfsSourch(vector<vector<char>>& board, string word, int x, int y, vector<vector<int>>& visited, int pos){
//1. 找到满足的值 - 出口
if(pos == word.size()) return true;
//2. 剔除边界值 - 出口 注意前两个一定要有等号,否则影响最后一个不等式
if(x>=board.size()|| y>=board[0].size() || x<0 || y<0 || board[x][y] != word[pos]) return false;
//3. 修改标记
if(visited[x][y] == false){
visited[x][y] = true;
//4. 上下左右依次遍历
if(dfsSourch(board, word, x-1, y, visited, pos+1)
||dfsSourch(board, word, x+1, y, visited, pos+1)
||dfsSourch(board, word, x, y-1, visited, pos+1)
||dfsSourch(board, word, x, y+1, visited, pos+1))
return true;
//5. 回溯前做了什么,回溯后做相反操作
visited[x][y] = false;
}
return false;
}
bool leetcode::exist(vector<vector<char>>& board, string word) {
//也就是前后左右都要查到,致命问题来了
int row = board.size();
int col= board[0].size();
int totalSize =row*col;
if(word.length()>totalSize) return false;
vector<vector<int>> visited(row, vector<int>(col, 0));
//主要
for(int i=0;i<row;i++){
for(int j=0;j<col;j++){
if(dfsSourch(board, word, i, j, visited, 0)){
return true;
}
}
}
return false;
}