代码随想录1刷--day7回溯

回溯

基础:

回溯常用于解决

  • 组合
    • 77.组合
    • 17.电话号码的字母组合
    • 39.组合总和
    • 40.组合总和2
    • 216.组合总和3
  • 分割
    • 131.分割回文串
    • 93.复原IP地址
  • 子集
    • 78.子集
    • 90.子集2
  • 排列
    • 46.全排列
    • 47.全排列2
  • 棋盘问题
    • 51.N皇后
    • 37.解数独
  • 其他
    • 491.递增子序列
    • 332.重新安排行程

回溯其实就是暴力搜索,回溯是递归的副产品,只要有递归就有回溯
回溯三部曲:

  1. 确定回溯函数返回值以及参数
void backtracking(参数)
  1. 回溯函数终止条件
if(终止条件)
{
    存放结果;
    return;
}
  1. 回溯搜索的遍历过程
for(选择:本层集合中的元素)
{
    处理节点;
    backtracking(路径,选择列表);   // 递归
    回溯,撤销处理结果;
}

整体框架如下:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

组合问题

  • 77:组合
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int n, int k, int startindex)
    {
        if(path.size()==k)
        {
            result.push_back(path);
            return;
        }
        // 当可供选择的不足时,剪枝
        for(int i=startindex;i<=n-(k-path.size())+1;i++)      // 这里涉及到剪枝优化
        {
            path.push_back(i);
            backtracking(n,k,i+1);
            path.pop_back();        // 回退
        }
        return;
    }
public:
    vector<vector<int>> combine(int n, int k) {
        result.clear();
        path.clear();
        backtracking(n,k,1);
        return result;
    }
};
  • 216:组合总和III
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int k, int target, int sum, int startIndex)     // 确定要传递的参数和返回值
    {
        if(sum>target)      // 这里设计一部分剪枝
            return;
        // 确定终止条件并保存结果
        if(path.size()==k) 
        {
            if(sum==target)
                result.push_back(path);
            return;
        }
        // 回溯搜索过程
        for(int i = startIndex; i<=9-(k-path.size())+1;i++)  // 这里涉及一部分剪枝
        {
            sum+=i;
            path.push_back(i);
            backtracking(k,target,sum,i+1);
            sum-=i; // 别忘了减
            path.pop_back();
        }
         return;
    }
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k,n,0,1);
        return result;
    }
};
  • 电话号码的字母组合
    时间复杂度: O(3^m * 4^n),其中 m 是对应四个字母的数字个数,n 是对应三个字母的数字个数
    空间复杂度:O(3^m * 4^n)
class Solution {
private:
    const string lettermap[8] ={
        "abc",
        "def",
        "ghi",
        "jkl",
        "mno",
        "pqrs",
        "tuv",
        "wxyz"
    };
    vector<string> result;
    string s;
    void backtracking(const string& digits, int index)
    {
        if(index==digits.size())
        {
            result.push_back(s);
            return;
        }
        // 确定单层递归逻辑
        int dig = digits[index] - '0';      // 转化成数字
        string letters = lettermap[dig-2];   // 取字符串
        for(int i=0;i<letters.size();i++)
        {
            s.push_back(letters[i]);
            backtracking(digits,index+1);
            s.pop_back();
        }
    }
public:
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0)    // 对空进行处理
            return result;
        backtracking(digits,0);
        return result;
    }
};
  • 39:组合总和
    这题如果需要剪枝的话,需要对数组排序,不一定会提高效率
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int index)
    {
        if(sum>target)
            return;
        // 不再是数字
        if(sum==target)
        {
            result.push_back(path);
            return;
        }
        // 回溯搜索过程
        for(int i = index;i<candidates.size();i++) // 这里剪枝少算一层 && sum+candidates[i]<=target
        {
            sum+=candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i);      // 注意这里i不需要+1
            sum-=candidates[i];
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        // 注意这题需要先排序,否则没办法进行剪枝
        // sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0);
        return result;
    }
};
  • 40:组合总和II
// 这题关键是里面存在重复的元素,所以需要添加一个map来记录,用数组即可
// 每个数字在每个组合中只能使用一次,在同一层上进行去重
// 所以可以先对数组排序,然后就可以方便对相同的元素进行去重
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int index, vector<bool> used)
    {
        if(sum>target)
            return;
        
        if(sum==target)
        {
            result.push_back(path);
            return;
        }
        // 单层递归逻辑
        for(int i=index; i<candidates.size() && sum+candidates[i]<=target; i++) // 这里有一部分剪枝
        {
            // candidates[i]==candidates[i-1]即左边的分支已经用过了
            // used[i-1] == false说明当前是在树枝上,这时不需要去重,这个蛮难理解,用于区分是在同一层还是同一枝
            if(i>0 && candidates[i]==candidates[i-1] && used[i-1]==false)
                continue;
            used[i] = true;
            sum+=candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i+1,used);    // 注意因为不能重复
            sum-=candidates[i];
            path.pop_back();
            used[i] = false;
        }
    }
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(),false);
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0,used);
        return result;
    }
};

分割

  • 131:分割回文串
class Solution {
private:
    vector<vector<string>> result;
    vector<string> path;
    bool isPalindrome(const string& s, int start, int end)
    {
        for(int i=start, j=end; i<j; i++,j--)
        {
            if(s[i]!=s[j])
                return false;
        }
        return true;
    }
    void backtracking(const string& s, int index)
    {
        if(index>=s.size())
        {
            result.push_back(path);
            return;
        }
        // 单层搜索逻辑
        for(int i=index; i<s.size();i++)
        {
            if(isPalindrome(s,index,i))
            {
                string str = s.substr(index,i-index+1); // 取出子串
                path.push_back(str);
            }
            else
                continue;
            
            backtracking(s,i+1);
            path.pop_back();
        }
    }
public:
    vector<vector<string>> partition(string s) {
        backtracking(s,0);
        return result;
    }
};
  • 93.复原ip地址
// 时间复杂度O(3^4)
// 空间复杂度O(n)
class Solution {
private:
    vector<string> result;
    bool isValid(const string& s, int start, int end)
    {
        if(start>end)
            return false;
        if(s[start]=='0' && start!=end) // 开头为0并且不止一位数
            return false;
        int num=0;
        for(int i=start;i<=end;i++) // 不能大于255
        {
            if(s[i]>'9' || s[i]<'0')  return false;
            num = num*10+(s[i]-'0');
        }
        if(num>255)
            return false;
        return true;
    }
    void backtracking(string& s, int index, int num)
    {
        if(num==3)  // 已经插入3个.
        {
            if(isValid(s,index,s.size()-1)) // 需要检查第四段是否合法
                result.push_back(s);
            return;
        }
        // 单层搜索逻辑
        for(int i=index;i<s.size();i++)
        {
            if(isValid(s,index,i))  // 检查[index,i]区间是否合法
            {
                s.insert(s.begin()+i+1,'.');    // 插入.
                num++;
                backtracking(s,i+2,num);    // s[i]是本段的最后一个元素,s[i+1]是.
                num--;  // 回退
                s.erase(s.begin()+i+1);//把插入的.删除
            }
            else
                break;
        }

    }
public:
    vector<string> restoreIpAddresses(string s) {
        if(s.size()<13)         // 容易忽视
            backtracking(s,0,0);
        return result;
    }
};

子集问题

组合问题和分割问题都是收集树的叶子节点,而子集问题是找到树的所有节点。

  • 78:子集
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int index)
    {
        // 因为每个节点都存,这里不需要判断index<nums.size();
        result.push_back(path);

        // 单层搜索逻辑
        for(int i=index;i<nums.size();i++)
        {
            path.push_back(nums[i]);
            backtracking(nums,i+1);     // 传i+1
            path.pop_back();
        }
        return;     // 在这返回
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums,0);
        return result;
    }
};
  • 90:子集II
// 提高难度,需要排除重复的,所以引入一个map,但是需要先对nums排序,和前面的排列组合问题一个思路
// 排序O(nlogn),子集最多2^n个,每个子集加入答案时需要拷贝一份,耗时O(n),最后时间复杂度为O(n*2^n);
// 空间复杂度O(n)
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int index, vector<bool>& used)
    {
        result.push_back(path);

        // 单层搜索逻辑
        for(int i=index; i<nums.size(); i++)
        {
            // nums[i]==nums[i-1]说明左边的分支用过了
            // used[i-1]==flase说明当前不是在树枝上,用于区分在同一层还是同一树枝
            // 因为假设第二层选了[1,2],这棵树继续选2是合法的,这个时候used[i-1]==true,但是在[1,2]同一层继续选[1,2]就不合法了,这个时候used[i-1]==false;
            if(i>0 && nums[i]==nums[i-1] && used[i-1]==false)
                continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums,i+1,used);
            used[i]=false;
            path.pop_back();
        }
        return;
    }
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<bool> used(nums.size(),false);
        backtracking(nums,0,used);
        return result;
    }
};

排列问题

排列问题也是递归到叶子节点

  • 46:全排列
// 时间复杂度O(n*n!)
// 空间复杂度O(n)
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;
    }
};
  • 47:全排列II
// 时间复杂度O(n*n!)
// 空间复杂度O(n)
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++)
        {
            // 注意这里的剪枝条件
            // used[i-1]==false 确保了重复是出现在同一层, used[i]==true用于防止同一个元素被二次选择
            if((i>0 && nums[i]==nums[i-1] && used[i-1]==false) || used[i]==true)
                continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums,used);
            used[i] = false;
            path.pop_back();
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return result;
    }
};

棋盘问题

  • 51:N皇后
class Solution {
private:
    vector<vector<string>> result;
    bool isvalid(int row, int col, vector<string>& chessboard,int n)
    {
        // 检查列
        for(int i=0;i<row;i++)
        {
            if(chessboard[i][col]=='Q')
                return false;
        }

        // 检查45°
        for(int i=row-1, j=col-1;j>=0&&i>=0;j--,i--)
        {
            if(chessboard[i][j]=='Q')
                return false;
        }

        // 检查135°
        for(int i=row-1,j=col+1;i>=0&&j<n;i--,j++)
        {
            if(chessboard[i][j]=='Q')
                return false;
        }
        return true;
    }
    void backtracking(int row, int n, 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(row+1,n,chessboard);
                chessboard[row][col] = '.';     // 回退
            }
        }
    }
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<string> chessboard(n,string(n,'.'));
        backtracking(0,n,chessboard);
        return result;
    }
};
  • 37:解数独
class Solution {
private:
    bool isValid(int row, int col, char val, vector<vector<char>>& board)
    {
        // 判断行内是否重复
        for(int i=0; i<9; i++)
        {
            if(board[row][i]==val)
                return false;
        }
        // 判断列内是否重复
        for(int j=0; j<9; j++)
        {
            if(board[j][col]==val)
                return false;
        }
        // 判断3*3宫格内是否重复
        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]!='.') continue;  
                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;   // 9个数都试完毕,直接返回,这就是为什么没有写终止条件也不会无限递归
            }
        }
        return true;    //  没有返回false,说明找到了符合的,就返回true
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

其他

  • 491:递增子序列
    这题由于nums是无序的,不能对其排序,所以无法使用子集II那种方法来排除重复,用set来做或者数组来做是更好的选择,u1s1,也更好理解
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int index)
    {
        // 第二层开始的所有节点
        if(path.size()>1) // 将合法的结果存
            result.push_back(path);

        // unordered_set<int> used;
        // 用数组更好
        vector<bool> used(201,false);
        // 单层搜索逻辑,也要考虑重复问题
        for(int i=index; i<nums.size(); i++)
        {
            // if((!path.empty() && nums[i]<path.back()) || used.find(nums[i])!=used.end())    // 本层已经用过了
            if((!path.empty() && nums[i]<path.back()) || used[nums[i]+100]==true)    // 本层已经用过了
                continue;
            // used.insert(nums[i]);   // used是在每层新建的
            used[nums[i]+100] = true;
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        // vector<bool> used(nums.size(),false);   // 惯性思维,nums是乱序的呀
        backtracking(nums,0);
        return result;
    }
};
  • 332:重新安排行程(hard),难啊
// 这个题可以用回溯来解决
// 需要借助一个映射来记录起点和终点,并且要记录从起点到终点的剩余票数
// unordered_map<string, map<string, int>> targets; //unordered_map<出发机场,map<到达机场,航班数>>
class Solution {
private:
// 要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。
// 选择了unordered_map<string, map<string, int>> targets 来做机场之间的映射。
    unordered_map<string,map<string,int>> targets;
    bool backtracking(int ticketNum, vector<string>& result)
    {
        if(result.size() == ticketNum+1)    // 结束行程时,经过的机场个数==航班数+1
            return true;
        
        // 单层遍历逻辑
        for(pair<const string, int>& target : targets[result[result.size()-1]]) // 遍历所有以result最后一个机场为起点的所有航班,这里用到迭代器
        {
            // 判断一下这个航班是否飞过
            if(target.second > 0)
            {
                result.push_back(target.first);
                target.second--;
                if(backtracking(ticketNum,result))  return true;
                result.pop_back();
                target.second++;
            }
        }
        return false;
    }
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        vector<string> result;
        // 建立映射关系
        for(const vector<string>& vec:tickets)
            targets[vec[0]][vec[1]]++;
        
        result.push_back("JFK");    // 别忘了定义初始
        backtracking(tickets.size(),result);
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值