专题复习:回溯法

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

回溯算法的模板:

1private void backtrack("原始参数") {
2     //终止条件(递归必须要有终止条件)
3     if ("终止条件") {
4     //一些逻辑操作(可有可无,视情况而定)
5     return;
6     }
7
8     for (int i = "for循环开始的参数"; i < "for循环结束的参数"; i++) 
      {
9      //一些逻辑操作(可有可无,视情况而定)
10
11     //做出选择
12
13     //递归
14     backtrack("新的参数");
15     //一些逻辑操作(可有可无,视情况而定)
16
17     //撤销选择
18     }
19 }

(1)leetcode 17. 电话号码的字母组合

class Solution {
    string a[10] = {
        "",
        "",
        "abc",
        "def",
        "ghi",
        "jkl",
        "mno",
        "pqrs",
        "tuv",
        "wxyz"
    };
    //这里的index指的是digits中的按键数字
    void dfs(string digits,vector<string>&res, string temp, int index)
    {
        if(temp.length() == digits.size())
        {
            res.push_back(temp);
            return;
        }
        //获取digits中的数 
        char c = digits[index];
        string letters = a[c - '0'];
        for(int i = 0; i <letters.length(); i++)
        {
            dfs(digits, res, temp + letters[i], index+1);
        }
        return;
    }
public:
    vector<string> letterCombinations(string digits) {
        vector<string> res;
        if(digits.length() == 0) return res;
        string temp;
        dfs(digits,res, temp, 0);
        return res;
    }
};

(2)leetcode39. 组合总和

注意:candicates中的元素可以重复使用,

错误示例

class Solution {
    //start为candidates数组中元素的索引,为了避免重复
    void dfs(vector<vector<int>>&res, vector<int> temp, vector<int> candidates,int remain)
    {
        if(remain == 0) //找到了恰好为target的数组
        {
            res.push_back(temp);
            return;
        }
        else if( remain < 0) return;
        else
        {
            for(int i = 0; i < candidates.size(); i++)
            {
                temp.push_back(candidates[i]);
                dfs(res, temp, candidates, remain - candidates[i]);
                temp.pop_back();
            }
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;
        dfs(res, temp, candidates,target);
        return res;
    }
};

输入 [2,3,6,7]    7

预期结果为:[[2,2,3],[7]]

上述代码的结果为: [[2,2,3],[2,3,2],[3,2,2],[7]]

出现了重复,原因是在第一个数字选3之后,又返回去选了2.为了去重,在dfs函数的入参中,加入begin参数用来,使得当选择了3, 那么后一个数只能选择3 以及3以后的数。

class Solution {
    //begin为candidates数组中元素的索引,为了避免重复
    void dfs(vector<vector<int>>&res, vector<int> temp, vector<int> candidates,int begin,int remain)
    {
        if(remain == 0) //找到了恰好为target的数组
        {
            res.push_back(temp);
            return;
        }
        else if( remain < 0) return;
        else
        {
            for(int i = begin; i < candidates.size(); i++)
            {
                temp.push_back(candidates[i]);
                dfs(res, temp, candidates, i,remain - candidates[i]);
                temp.pop_back();
            }
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;
        dfs(res, temp, candidates,0,target);
        return res;
    }
};

(3)leetcode40. 组合总和 II

先对candicates进行排序,然后在进行回溯

class Solution {

    void dfs(vector<vector<int>>& res, vector<int> temp, vector<int> candidates,int begin, int remain)
    {
        if(remain == 0)
        {
            res.push_back(temp);
            return;
        }
        else if(remain < 0) return;
        else
        {
            for(int i = begin; i < candidates.size(); i++)
            {
                //注意是 i > begin 
                if(i > begin && candidates[i] == candidates[i-1]) continue;
                else
                {
                    temp.push_back(candidates[i]);
                    dfs(res, temp, candidates, i+1, remain - candidates[i]);
                    temp.pop_back();
                }
            }
        }

    }

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;
        sort(candidates.begin(), candidates.end());
        dfs(res, temp, candidates, 0, target);
        return res;
    }
};

(4) leetcode 22. 括号生成

class Solution {
public:
    //leftCount 表示左括号的数量 rightCount表示右括号的数量
    void dfs(vector<string>& res, string temp, int leftCount, int rightCount, int n)
    {
        if(leftCount == n && rightCount == n)
        {
            res.push_back(temp);
            return;
        }
        if(leftCount < rightCount) return;
        if(leftCount < n) dfs(res, temp+'(', leftCount+1, rightCount, n);
        if(rightCount < n) dfs(res, temp+')', leftCount, rightCount + 1, n);
    }

    vector<string> generateParenthesis(int n) {
        vector<string> res;
        string temp;
        dfs(res, temp, 0, 0, n);
        return res;
    }
};

(5) 剑指 Offer 38. 字符串的排列

本题难点在于:结果中去重复,在本解法中,利用set (set底层实现采用的是二叉搜索树)

每次往res中添加结果时,需要判断set中是否已经包含了该元素。

class Solution {
    unordered_set<string>  set;
    void dfs(string s, vector<string>&res, string temp, vector<bool>&isUsed)
    {
        if(temp.length()== s.length() && set.find(temp) == set.end())
        {
            set.insert(temp);
            res.push_back(temp);
            return;
        }
        for(int i = 0; i < s.length(); i++)
        {
            if(!isUsed[i])
            {
                isUsed[i] = true;
                dfs(s, res, temp+s[i], isUsed);
                isUsed[i] = false;
            }
        }
    }

public:
    vector<string> permutation(string s) {
        vector<string> res;
        string temp;
        vector<bool> isUsed(s.length(), false);
        dfs(s, res, temp, isUsed);
        return res;        
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值