算法学习 回溯法解题框架

回溯法解题框架

回溯问题就是一个决策树的遍历过程

考虑三个问题:

  1. 路径:也就是已经做出的选择
  2. 选择列表:当前可以做的选择
  3. 结束条件:到达决策树的底层,无法再做选择的条件

回溯法框架

result[]
def backtrack()
	if满足结束条件
		添加路径
		return
	for 选择in选择列表
		选择
		backtrack()
		撤回选择

全排列问题(不包含重复数字)

在这里插入图片描述
路径就是已经做过的选择,选择列表就是当前可以做的选择,结束条件就是遍历到树的底层也就是选择列表为空的时候。
通过算法框架写出代码(用list时间效率比vector高)

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<int>track;
        if(nums.size()<1)
            return res;
        backtrack(nums,track);
        return res;
    }

    void backtrack(vector<int>&nums,vector<int>track)
    {
        //if满足决策条件
        if(track.size()==nums.size())
            {
                //添加路径
                res.push_back(track);
                //return
                return;
            }

        //for 选择 in 选择列表
        for(auto num:nums)
        {
            //做选择
            auto ishave=find(track.begin(),track.end(),num);
            if(ishave==track.end())
                {
                    track.push_back(num);
                    //backtrack()
                    backtrack(nums,track);
                     //撤销选择 
                    track.pop_back();
                }
            else
                continue;
           
        }
               
    }
private:
    vector<vector<int>>res;
};

N皇后问题

这个问题很经典了,简单解释一下:给你一个 N×N 的棋盘,让你放置 N 个皇后,使得它们不能互相攻击。
PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。
这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。
直接套用框架:

class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<string>board(n,string(n,'.'));
        if(n<1)return res;
        backtrack(0,board);//细节错误,0写成了n找了很久
        return res;
    }
    void backtrack(int row,vector<string>board)
    {
        //if满足结束条件
        if(row==board.size())
            {
                //添加结果
                res.push_back(board);
                //return
                return;
            }
        //for 选择in选择列表
        for(int i=0;i<board.size();i++)
           {
            //判断选择
            if(isvalid(board,row,i))
                {
                    board[row][i]='Q';
                    //backtrack(row+1,board)
                    backtrack(row+1,board);
                    //撤销选择
                    board[row][i]='.';
                }
           } 
           
    }

    bool isvalid(vector<string>board,int row,int col)//只要判断当前节点上、左上和右上这三个方向就可以了
    {
        for(int currow=0;currow<board.size();currow++)//判断一列
        {
            if(board[currow][col]=='Q')
                return false; 
        }
        for(int currow=row-1,curcol=col-1;currow>=0&&curcol>=0;currow--,curcol--)
        {
             if(board[currow][curcol]=='Q')
                return false;
        }
        for(int currow=row-1,curcol=col+1;currow>=0&&curcol<board.size();currow--,curcol++)
        {
             if(board[currow][curcol]=='Q')
                return false;
        }
        return true;
    }
private:
    vector<vector<string>>res;
};

动态规划:状态 dp定义 选择 base case
回溯:路径 选择 结束条件
某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。

例题

1.组合总和

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。 
示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]
示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]
  1. 套用框架
result[]
def backtrack()
	if结束条件
		result.add(track)
		return;
	for 选择in选择列表
		选择
		backtrack()
		撤销选择
  1. 发现有重复组合(解集的各种排列组合)
    解决:首先对原始数组进行排序;用一个索引数组标识路径中已有的索引;用prenum记录每一次选择循环当前元素的前一个元素,如果重复就剪去这个元素;用fathernum记录上一个路径节点,判断子节点如果小于父节点就剪去这个元素;
class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int>nums=candidates;
        sort(nums.begin(),nums.end());
        vector<int>track;
        vector<bool>havevisit(candidates.size(),false);
        backtrack(nums,havevisit,track,target,0);
        return res;

    }
    void backtrack(vector<int>& nums,vector<bool>&havevisit,vector<int>track,int target,int fathernum)
    {
        int prenum=0;
        //if结束条件
        if(target==0)
        {
             //添加路径
            res.push_back(track);
             //return
            return;
        }
        else if(target<0)
            return;


        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]==prenum||havevisit[i]==true||nums[i]<fathernum)
                continue;
            else 
            {
                havevisit[i]=true;
                prenum=nums[i];
                track.push_back(nums[i]);
                backtrack(nums,havevisit,track,target-nums[i],nums[i]);
                track.pop_back();
                havevisit[i]=false;
            }
        }   
        //for 选择in选择列表
            //选择
            //backtrack
            //撤销选择
    }
private:
    vector<vector<int>> res;
};
  1. 优化:发现时间复杂度和空间复杂度都很高,想用哈希表替代排序、
class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        //vector<int>nums=candidates;
        //sort(nums.begin(),nums.end());
        vector<int>track;
        vector<bool>havevisit(candidates.size(),false);
        backtrack(candidates,havevisit,track,target,0);
        return res;

    }
    void backtrack(vector<int>& nums,vector<bool>&havevisit,vector<int>track,int target,int fathernum)
    {
        //int prenum=0;
        //if结束条件
        if(target==0)
        {
             //添加路径
            res.push_back(track);
             //return
            return;
        }
        else if(target<0)
            return;

        unordered_set<int> Myset;
        for(int i=0;i<nums.size();i++)
        {

            //if(nums[i]==prenum||havevisit[i]==true||nums[i]<fathernum)
            if(Myset.find(nums[i])!=Myset.end()||havevisit[i]==true||nums[i]<fathernum)
                continue;
            else 
            {
                havevisit[i]=true;
                Myset.insert(nums[i]);
                track.push_back(nums[i]);
                backtrack(nums,havevisit,track,target-nums[i],nums[i]);
                track.pop_back();
                havevisit[i]=false;
            }
        }   
        //for 选择in选择列表
            //选择
            //backtrack
            //撤销选择
    }
private:
    vector<vector<int>> res;
};

时间复杂度反而更高
在这里插入图片描述

2.有重复字符串的排列组合

有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。

示例1:

 输入:S = "qqe"
 输出:["eqq","qeq","qqe"]
示例2:

 输入:S = "ab"
 输出:["ab", "ba"]
提示:

字符都是英文字母。
字符串长度在[1, 9]之间。
  • 回溯法框架
	//result[]
	//def backtrack
		//if 结束条件
			//result.add(track)
			//return;
		//for 选择in选择列表
			//选择
			//backtrack
			//撤回选择
class Solution {
public:
	vector<string> permutation(string S) {
		string track;
		vector<bool>hasInTrack(S.size(), false);
		backtrack(S, track, hasInTrack);
		return result;
	}
	void backtrack(string s, string & track, vector<bool>&hasInTrack)
	{
		
		int n = s.size();
		if (track.size() >= s.size())
		{
			result.push_back(track);
			return;
		}
		unordered_set<char>hasappear;
		for (int i = 0; i < n; i++)
		{
			if (auto iter = hasappear.find(s[i]) == hasappear.end()&& hasInTrack[i] == false)
			{
				hasappear.insert(s[i]);
					hasInTrack[i] = true;
					track.push_back(s[i]);
					backtrack(s, track, hasInTrack);
					track.pop_back();
					hasInTrack[i] = false;
			}
			else
				continue;

		}
	}
private:
	vector<string>result;
};

减枝问题
1.已经存在于路径的节点:用一个数组记录哪一个位置的元素已被访问过,需要回溯
2.同一层的节点避免重复:用一个哈希表记录for循环时哪些元素是重复的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值