回溯

枫长...大约 6 分钟

46.全排列open in new window

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

解题思路

  • 这道题是dfs的经典题目
  • dfs的时候,我们用一个tmp保存正在搜索的排列,用visit记录搜索过的位,注意在dfs中,我们尽量传引用而不是传值,不然会炸,所以我们需要一直利用一些变量,dfs前设置好,dfs完再清掉。
  • 每次搜索时,分别去遍历每一位,如果这一位没遍历,就将这一位压入tmp,visit记录tmp然后进入下次dfs。
  • 注意在dfs返回后,要给visit的相应位归0,tmp同时也要pop()掉,好让其接着用于下一个分支。
  • 递归退出的条件是找到一个全排列,也就是tmp大小为n
class Solution {
public:
    vector<vector<int>> ans;
    void dfs(vector<int>& nums, vector<int>& visit, vector<int>& tmp) {
        int n = nums.size();
        if(tmp.size() == n) {
            ans.emplace_back(tmp);
            return;
        }
        for(int i = 0; i < n; i++) {
            if(visit[i]) {
                continue;
            }
            tmp.emplace_back(nums[i]);
            visit[i] = 1;
            dfs(nums, visit, tmp);
            tmp.pop_back();
            visit[i] = 0;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        int n = nums.size();
        vector<int> visit(n);
        vector<int> tmp;
        dfs(nums, visit, tmp);
        return ans;
    }
};

39.组合总和open in new window

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

解题思路

  • dfs问题,就是回溯的思路,递归返回的边界是,要么target为0,即找到了结果返回,要么用完了所有数,idx = n返回。
  • 注意一下对于每个数,是尝试拿多少个当前数直到target超了,所以dfs完后也要pop相应个数。
class Solution {
public:
    vector<vector<int>> ans;
    void dfs(vector<int>& candidates, vector<int>& t,int target, int idx) {
        int n = candidates.size();
        if(target == 0) {
            ans.emplace_back(t);
            return;
        } else if(target < 0) {
            return;
        }
        if(idx >= n) {
            return;
        }
        for(int cnt = 0; cnt*candidates[idx] <= target; cnt++) {
            for(int i = 0;i < cnt; i++) {
                t.emplace_back(candidates[idx]);
            }
            dfs(candidates, t, target - cnt*candidates[idx], idx + 1);
            for(int i = 0;i < cnt; i++) {
                t.pop_back();
            }            
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<int> t;
        dfs(candidates, t, target, 0);
        return ans;
    }
};

78.子集open in new window

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

解题思路

  • 这道题当然可以用dfs回溯来做,但我推荐另一种做法,按位枚举
  • 可以用一个二进制数来表示子集中的拿取情况,二进制数的位数等于子集的大小,每一位来表示拿或者没拿,所以每种子集拿取情况都有一个唯一的二进制数来表示。
  • 二进制数的边界是2n - 1。即n个1组成的二进制数,开始当然是0,n个0组成的二进制数。
  • 然后遍历从0到2n - 1中的每一个数,按照当前数的二进制位生成子集。
  • 这样做法的好处是不用递归
class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n = nums.size();
        int boundary = pow(2,n);
        vector<vector<int>> ans;
        vector<int> tmp;
        for(int i = 0; i < boundary; i++) {
            for(int j = 0; j < n; j++) {
                if((i >> j) & 1) {
                    tmp.emplace_back(nums[j]);
                }
            }
            ans.emplace_back(tmp);
            tmp.clear();
        }
        return ans;
    }
};

40.组合总和 IIopen in new window

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

解题思路

  • 这道题和组合总和1相比限制了每个数字只能使用1次,看上去变简单了;但是candidates的大小很大,如果直接dfs会爆掉。
  • 我们可以变化一下,题目说了每个数字有重复,且数字范围是0-50,再考虑到target只有30,所以有用的数字范围就是0-30,我们可以用一个hash表记录个数,这样又回到组合总和1了。
  • 不过稍微变化的是,这次对一个数不光是尝试拿1个,2个,拿到target为0为止,还有你可能提前把这个数拿空的情况,所以有两个限制。
class Solution {
public:
    void dfs(vector<int>& hash,int target,int index,vector<int>& tmp,vector<vector<int>>& ans){
        if(target <= 0){
            if(target == 0) ans.emplace_back(tmp);
            return;
        }
        int n = hash.size();
        //当前位置找到非0的
        while(index < hash.size() && hash[index] == 0){
            index++;
        }
        //超出边界
        if(index >= n) {
			return;
		} 
        dfs(hash, target, index + 1, tmp, ans);
        for(int i=0; i<hash[index]; i++){
            for(int j = 0; j <= i; j++){
                tmp.emplace_back(index);
            }
            target -= index;
            dfs(hash, target, index+1, tmp, ans);
            for(int j = 0; j <= i; j++){
                tmp.pop_back();
            }
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> hash(31);
        for(int i=0;i<candidates.size();i++){
            if(candidates[i]<=30) hash[candidates[i]]++;
        }
        vector<int> temp;
        vector<vector<int>> ans;
        dfs(hash,target,0,temp,ans);
        return ans; 
    }
};

47.全排列 IIopen in new window

给定一个可包含重复数字的序列 nums ,按任意顺序返回所有不重复的全排列。

解题思路

  • 这道题跟全排列相比多了一个去重,去重也好办
  • 先给nums排序,然后取数的时候注意一下,如果前一个和当前数相同,且前一个没访问过这种情况,直接跳过
  • 也就是说,对于重复的数,我们只会从前往后一个一个取,这样就不会出现重复的排列。
class Solution {
public:
    vector<vector<int>> ans;
    void dfs(vector<int>& nums,vector<int>& visit,vector<int>& t){
        int n = nums.size();
        if(t.size() == n) {
            ans.emplace_back(t);
            return;
        }
        for(int i=0;i<n;i++){
            if(!visit[i]){
                if(i > 0 && !visit[i-1] && nums[i-1] == nums[i]) {
					continue;
				}
                visit[i] = 1;
                t.emplace_back(nums[i]);
                dfs(nums, visit, t);
                t.pop_back();
                visit[i] = 0;
            }
            
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int n=nums.size();
        vector<int> visit(n);
        vector<int> t;
        dfs(nums,visit,t);
        return ans;
    }
};
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.14.8