LeetCode中级算法之回溯算法

回溯算法回顾

先来复习一下回溯算法,摘自百度百科:

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

说白了,跟剪枝差不多,直接开工~

电话号码的字母组合

Question:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例:

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。

Solution:

map<char,string> mapletter;
    vector<string> letterCombinations(string digits) {
        mapletter.insert(pair<char, string>('2',"abc"));  
        mapletter.insert(pair<char, string>('3',"def"));  
        mapletter.insert(pair<char, string>('4',"ghi"));  
        mapletter.insert(pair<char, string>('5',"jkl")); 
        mapletter.insert(pair<char, string>('6',"mno"));
        mapletter.insert(pair<char, string>('7',"pqrs"));  
        mapletter.insert(pair<char, string>('8',"tuv"));  
        mapletter.insert(pair<char, string>('9',"wxyz"));  
    	vector<string> res;
    	string path;
    	if(digits.empty())return res;
    	letterdfs(res,digits,path);   
    	return res;   
    }
    void letterdfs(vector<string> &res, string digits, string path)
    {
    	int d = digits.size(), p = path.size(); 
    	if(p == d) {res.push_back(path); return;}
        for(int i = 0; i < mapletter[digits[p]].size(); i++)
        {
        	path.push_back(mapletter[digits[p]][i]);
        	letterdfs(res,digits,path);
        	path.pop_back();
        }
    }

在letterdfs中动态记录path,每次向前进一步调用新的回溯函数,再退回,遍历下一个字母。

生成括号

Question :
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

Solution:
我第一次写的代码是超时的,因为的出来的结果有重复的现象,我筛选了非重复值,这直接让时间复杂度多了一个量级:

vector<string> generateParenthesis(int n) {
    	vector<string> res;
    	if(n == 0) return res;
        string path;
        parendfs(res,path,n,n,0);
        return res;
    }
    void parendfs(vector<string> &res, string path, int n, int left, int right)
    {
    	if(path.size() == n * 2) {
            vector<string>::iterator it = find(res.begin(),res.end(),path); 
            if(it == res.end()) 
                res.push_back(path); 
            return;
        }
    	for(int i = 0; i < left; i++)
    	{
    		path.push_back('(');
    		parendfs(res,path,n, left - 1, right + 1);
    		path.pop_back();
    	}
    	for(int i = 0; i < right; i++)
    	{
    		path.push_back(')');
    		parendfs(res,path,n,left, right - 1);
    		path.pop_back();
    	}
    }

那么为什么会出现重复呢?考虑n的2的情况,我输出的答案是["(())","(())","()()","(())","(())","()()"],为什么会出现五次重复呢,初始left为2,剩余两个左括号需要使用,发现这道题跟上道题是不同的,上道题的字母组合,需要遍历每个位置的三个数字,但是这道题不一样,使用for循环导致重复输出了目标结果,修改后就可以了:

    vector<string> generateParenthesis(int n) {
    	vector<string> res;
    	if(n == 0) return res;
        string path;
        parendfs(res,path,n,n,0);
        return res;
    }
    void parendfs(vector<string> &res, string path, int n, int left, int right)
    {
    	if(path.size() == n * 2) {
                res.push_back(path); 
            return;
        }
        if(right > 0)
    	{
    		path.push_back(')');
    		parendfs(res,path,n,left, right - 1);
            path.pop_back();
    	}
    	if(left > 0)
        {
            path.push_back('(');
    		parendfs(res,path,n, left - 1, right + 1);
        }
    }

全排列

Question:
给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:
输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

Solution:

    vector<vector<int>> permute(vector<int>& nums) {
    	vector<vector<int>> res;
    	vector<int> path;
    	if(nums.size() == 0) return res;
        numsdfs(res,path,nums);
        return res;
    }
    void numsdfs(vector<vector<int>> &res, vector<int> path, vector<int> nums)
    {
    	int p = path.size(), n = nums.size();
    	if(p == n){
    		res.push_back(path);
    		return;
    	}
        for(int i = p; i < n; i++)
        {
            int temp = nums[p];
            nums[p] = nums[i];
            nums[i] = temp;
            path.push_back(nums[p]);
    	    numsdfs(res,path,nums);
    	    path.pop_back();
        }
    }

子集

Question:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

示例:
输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

Solution:

    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> path;
        int pos = 0;   
        subsetsdfs(res,path,nums,pos);
        return res;
    }
    void subsetsdfs(vector<vector<int>> &res, vector<int> path, vector<int> nums,int pos)
    {
        //cout << pos << endl;
    	if(pos == nums.size())
    	{
    		res.push_back(path);
    		return;
    	}
    	subsetsdfs(res,path,nums,pos + 1);
    	path.push_back(nums[pos]);
    	subsetsdfs(res,path,nums,pos + 1);
    }

单词搜索

Question:
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:
board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

给定 word = "ABCCED", 返回 true.
给定 word = "SEE", 返回 true.
给定 word = "ABCB", 返回 false.

Solution:
这道题有点像迷宫问题:

bool exist(vector<vector<char>>& board, string word) {
    	if(word.length() == 0) return true;
    	for(int i = 0; i < board.size(); i++)
    	{
    		for(int j = 0; j < board[0].size(); j++)
    		{
                if(word[0] == board[i][j])
                    if(worddfs(board,word,i,j,0)) return true;
    		}
    	}
        return false;
    }
    bool worddfs(vector<vector<char>> board, string word, int i, int j, int pos)
    {
    	if(word[pos] == board[i][j]) 
    	{
    		board[i][j] = '0';
    		pos++;
            if(pos == word.size()) return true;
    		if(i > 0 && board[i-1][j] != '0') 
    		  if(worddfs(board,word,i-1,j,pos)) return true;
    		if(j > 0 && board[i][j-1] != '0') 
    		  if(worddfs(board,word,i,j-1,pos)) return true;
    		if(i < board.size() - 1&& board[i+1][j] != '0') 
    		  if(worddfs(board,word,i+1,j,pos)) return true;
    		if(j < board[0].size() - 1 && board[i][j+1] != '0') 
    		  if(worddfs(board,word,i,j+1,pos)) return true;
    	}
    	return false;
    }

写在后面

回溯法应该是最简单的一种算法了,记得之前老师说回溯法是最笨的方法,所以这个以后还是少用。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值