常用算法-回朔法

1、概念

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

2、基本思想

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。

而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

3、算法框架

主要看看递归框架:

//一个保存最终结果的数组或容器,一般是vector,假设命名为 rvec
//一个保存临时单个结的数组或容器,一般也是vector,假设命名为 temp

void backtrack(.....) // 参数一般是包含源数组,最红结果数组,临时数组,以及你的目标,数组下标等
{
     if(条件) //看实际情况,有可能有条件也可能没有
        rvec保存这个结果;
     else
     {
        for(j = 下界; j <= 上界; j=j+1)  // 枚举i所有可能的路径
        {
            if(条件)  //看实际情况,有可能有条件,也可能没有
            {
                temp.push_back(值); //temp保存这个值
                backtrack(.....);  //主意看你是否可重复用数组元素,下标分别为原下标,或下边加一。
                temp.pop_back(); //回溯前的清理工作(如a[i]置空值等);
            }
       }
    }
}

上述框架基本可以解决大部分的问题了,下面看看用这个框架解决实际问题。

4、实际案例

1、Subsets

地址:https://leetcode.com/problems/subsets/

问题描述:给一个不存在重复数字的数组,找到所有可能的子数组。比如:
If nums = [1,2,3], a solution is:[ [3],[1],[2],[1,2,3],[1,3],[2,3],[1,2],[] ]

代码:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) 
    {
        vector<vector<int>> resvec;
        vector<int> tmpvec;
        sort(nums.begin(),nums.end());
        backtrack(resvec,tmpvec,nums,0);
        return resvec;
    }

    void backtrack(vector<vector<int>> &resvec,vector<int> tmpvec,vector<int> nums,int start)
    {
        resvec.push_back(tmpvec);
        for(int i=start;i<nums.size();i++)
        {
            tmpvec.push_back(nums[i]);
            backtrack(resvec,tmpvec,nums,i+1);
            tmpvec.pop_back();
        }
    }
};

2、Subsets II

地址:https://leetcode.com/problems/subsets-ii/

问题描述:给一个包含重复数字的数组,找到所有可能的子数组。比如:
If nums = [1,2,2], a solution is:[ [2],[1],[1,2,2],[2,2],[1,2],[] ]

代码:

class Solution {
public:

    vector<vector<int>> subsetsWithDup(vector<int>& nums) 
    {
        vector<vector<int>> rvec;
        vector<int> temp;
        sort(nums.begin(),nums.end());
        backtrace(rvec,temp,nums,0);
        return rvec;

    }

    void backtrace(vector<vector<int>> &rvec,vector<int> &temp,const vector<int> &nums,int start)
    {
        rvec.push_back(temp);
        for (int i=start;i<nums.size();i++)
        {
            if(i==start||nums[i]!=nums[i-1])
            {
                temp.push_back(nums[i]);
                backtrace(rvec,temp,nums,i+1);
                temp.pop_back();
            }

        }
    }

};

3、Permutations

地址:https://leetcode.com/problems/permutations/

问题描述:给一个不存在重复数字的数组,找到所有的排列。比如:
For example,[1,2,3] have the following permutations: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

代码:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) 
    {
        vector<vector<int>> revec;
        vector<int> temp;
        backtrace(nums,temp,revec);
        return revec;
    }

    void backtrace(vector<int>& nums,vector<int> temp,vector<vector<int>> &revec)
    {
        if(temp.size()==nums.size())
           revec.push_back(temp);
        else
        {
            for(int i=0;i<nums.size();i++)
            {
                if(find(temp.begin(),temp.end(),nums[i])==temp.end())
                {
                    temp.push_back(nums[i]);
                    backtrace(nums,temp,revec);
                    temp.pop_back();
                }
            }
        }
    }
};

4、Permutations II (contains duplicates)

地址:https://leetcode.com/problems/permutations-ii/

问题描述:给一个包含重复数字的数组,找到所有的排列。比如:
For example, [1,1,2] have the following unique permutations: [ [1,1,2], [1,2,1], [2,1,1] ]

代码:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) 
    {
        vector<vector<int>> revec;
        vector<int> temp;
        vector<bool> used(nums.size());
        sort(nums.begin(), nums.end());
        backtrace(nums, temp, revec,used);
        return revec;
    }

    void backtrace(vector<int>& nums, vector<int> temp, vector<vector<int>> &revec, vector<bool> used)
    {
        if (temp.size() == nums.size())
            revec.push_back(temp);

        for (int i = 0; i<nums.size(); i++)
        {
            if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
            temp.push_back(nums[i]);
            used[i] = true;
            backtrace(nums, temp, revec,used);
            used[i] = false;
            temp.pop_back();

        }
    }

};

5、Combination Sum

地址:https://leetcode.com/problems/combination-sum/

问题描述:给一个不包含重复数字的数组和一个目标数字,找到所有和等于目标数字的排列。比如:
For example, given candidate set [2, 3, 6, 7] and target 7, A solution set is: [ [7], [2, 2, 3] ]

代码:

class Solution {
public:
    void backtracing(vector<int> &candidates, vector<int> temp,vector<vector<int>> &result,int target, int start )
    {
        if (target<0) return;
        else if (target == 0)   result.push_back(temp);
        else
        {
            for (int i = start; i < candidates.size(); i++)
            {
                temp.push_back(candidates[i]);
                backtracing(candidates,temp,result, target-candidates[i],i); // not i + 1 because we can reuse same elements
                temp.pop_back();
            }
        }       
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) 
    {
        vector<vector<int>> result;
        vector<int> temp;
        sort(candidates.begin(), candidates.end());
        backtracing(candidates, temp,result,target, 0);
        return result;
    }
};

6、Combination Sum II

地址:https://leetcode.com/problems/combination-sum-ii/

问题描述:给一个包含重复数字的数组和一个目标数字,找到所有和等于目标数字的排列,每个数字不能用多次,但是出现在数组中的可以用。比如:
For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8, A solution set is: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]

代码:

class Solution {
public:
    void backtracing(vector<int> &candidates,vector<int> temp, vector<vector<int>> &result, int target, int start)
    {
        if (target <0)  return;
        else if (target == 0) result.push_back(temp);
        else
        {
            for (int i = start; i < candidates.size(); i++)
            {
                if(i>start&&candidates[i]==candidates[i-1]) continue; // skip duplicates otherwise will like this [1,7] [1,7]
                temp.push_back(candidates[i]);
                backtracing(candidates, temp, result,target-candidates[i], i+1 );
                temp.pop_back();
            }
        }       
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) 
    {
        vector<vector<int>> result;
        vector<int> temp;
        int sum=0,levle = 0;
        sort(candidates.begin(), candidates.end());
        backtracing(candidates,temp, result, target, 0);
        return result;
    }
};

7、Combination Sum III

地址:https://leetcode.com/problems/combination-sum-iii/

问题描述:给一个包含重复数字的数组和一个目标数字,以及一个排列长度,找到所有和等于目标数字的规定长度的排列,每个数字不能用多次,但是出现在数组中的可以用。比如:
Input: k = 3, n = 7 ,Output:[[1,2,4]]
Input: k = 3, n = 9 ,Output:[[1,2,6], [1,3,5], [2,3,4]]

代码:

class Solution {
public:
    void backtracing(const vector<int> &candidates,vector<int> temp, vector<vector<int>> &result, int k,int target, int start)
    {
        if (target<0) return;
        else if (target==0)
        {
            if(temp.size()==k)
               result.push_back(temp);
            return;
        }
        else
        {
            for (int i = start; i < candidates.size(); i++)
            {
                if(i>start&&candidates[i] == candidates[i + 1]) continue;
                {
                    temp.push_back(candidates[i]);
                    backtracing(candidates,temp, result, k,target-candidates[i], i+1);
                    temp.pop_back();
                }
            }
        }       
    }

    vector<vector<int>> combinationSum3(int k, int n) 
    {
        vector<vector<int>> result;
        vector<int> temp;
        vector<int> candidates = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        backtracing(candidates,temp, result,k,n, 0);
        return result;
    }
};

8、Palindrome Partitioning

地址:https://leetcode.com/problems/palindrome-partitioning/

问题描述:给一个字符串,将s分成每个子串都是回文的所有排列组合。比如:
For example, given s = “aab”,Return :[ [“aa”,”b”], [“a”,”a”,”b”] ]

代码:

class Solution {
public:
    vector<vector<string>> partition(string s) 
    {
        vector<vector<string>> rvec;
        if(s.empty()) return rvec;
        vector<string> temp;
        backtrace(s,temp,rvec,0);
        return rvec;
    }

    void backtrace(const string &s,vector<string> temp,vector<vector<string>> &rvec,int index)
    {
        if(index==s.length())
        {
            rvec.push_back(temp);
            return;
        }

        for(int i=index;i<s.length();i++)
        {
            if(isPalindrome(s,index,i))
            {
                temp.push_back(s.substr(index,i-index+1));
                backtrace(s,temp,rvec,i+1);
                temp.pop_back();
            }

        }

    }

    bool isPalindrome(const string &s,int low,int high)
    {
        while(low<=high)
        {
            if(s[low++]!=s[high--])
            return false;
        }
        return true;
    }
};

上面都是C++版的,leetcode上有java版的,见下面参考资料。

5、参考资料

本文章主要来源于LeetCode,这里对这个平台表示感谢,强烈推荐大家可以去刷刷题,不仅仅为找工作,更重要的锻炼自己的解决问题的能力,同时也可以跟各路算法大神互动交流。地址:A general approach to backtracking questions in Java (Subsets, Permutations, Combination Sum, Palindrome Partioning)

开始的对于回溯算法概念以及框架的来源于网上一位名叫“红脸书生”的大神,我稍稍对算法框架做了修改。
地址:五大常用算法之四:回溯法

    原文作者:五大常用算法
    原文地址: https://blog.csdn.net/yixianfeng41/article/details/55684782
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞