LC46, 47 Permutation I and II 回溯

LC46 Permutation

Given a collection of distinct numbers, return all possible 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]
]

回溯的思路,具体实现有两种方法。

方法一:在nums里面挨个挑数字放到目标排列的第一个位置上,然后把选中的数从nums里面删除,循环这个过程。nextPermute()中nums就是剩下的可放在第一个位置的备选数组,每次从里面挑一个数然后缩短这个备选数组传到下一次挑用。

void nextPermute(vector<int> nums, vector<vector<int>>& res, vector<int>& cur){
    if(nums.size()==0){
        res.push_back(cur);
        return;
    }
    vector<int> nextNums=nums, nextCur=cur;
    for(int i=0;i<nums.size();i++){
        nextCur.push_back(nums[i]);
        nextNums.erase(nextNums.begin()+i);
        nextPermute(nextNums, res, nextCur);
        nextNums=nums;
        nextCur=cur;
    }
}
vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> res;
    vector<int> cur;
    nextPermute(nums, res, cur);
    return res;
}

这个方法比较直观,我第一次就想到了这么做。缺点:1)频繁的数组erase操作比较耗时;2)nums的传值拷贝比较浪费空间

方法二:和方法一类似,还是每次从数组后面挑选各个数放到最前面。使用swap()实现这个过程。

void myPermute(vector<int>& nums, vector<vector<int>>& res, int pos) {
    if(pos==nums.size()){
        res.push_back(nums);
        return;
    }
    for(int i=pos;i<nums.size();i++){
        swap(nums[i], nums[pos]);
        myPermute(nums, res, pos+1);
        swap(nums[i], nums[pos]);
        //不停的把pos后面的各个数字替换到pos的位置。观察从pos开始的子串,这个过程保证了首个位置(pos)可以是后面的任意一个只值
    }
}
vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> res;
    myPermute(nums, res, 0);
    return res;
}

比较好的地方是可以直接通过引用传递传nums。注意myPermute()之后也要再把之前swap的两个数swap回来。理由如下,考虑[1,2,3],在没有第二个swap的情况下依次得出的排列是:[1,2,3]->[1,3,2]->[3,1,2]->[3,2,1]->下一个会是什么呢?因为没有swap回来并且使用引用传递,下一步pos=0,i=2,将第一个位置的数和最后一个位置的数进行换位,也就是得到->[1,2,3]->[1,3,2],也就出现了重复。可以看到,使用引用传值这个trick还是很tricky的tricky的,在一开始写代码的时候就像弄对还是比较难的,一次次的传值就不需要考虑递归调用的层次对nums的干扰、对递归的其他分支的干扰。


LC47 Permutation II

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

For example,
[1,1,2] have the following unique permutations:

[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

问题和LC46基本一样,多了一个条件就是nums里面可能存在重复值。有了LC46的swap方法,很直接的就能想到在每次将后面的值swap到前面的过程中多一个判断跳过已经swap到首位的值,这样避免了重复。为了顺序的判断,先把nums排序。代码:

void myPermute(vector<int> nums, vector<vector<int>>& res, int startPos){
    if(startPos==nums.size()-1){
        res.push_back(nums);
        return;
    }
    for(int i=startPos;i<nums.size();i++){
        if(i!=startPos&&nums[i]==nums[startPos])
            continue;
        swap(nums[i],nums[startPos]);
        myPermute(nums,res,startPos+1);
        //myPermute函数不能用引用传递传nums,这一点和Permutation那道题不同
    }
}
vector<vector<int>> permuteUnique(vector<int>& nums){
    sort(nums.begin(),nums.end());
    vector<vector<int>> res;
    myPermute(nums, res, 0);
    return res;
}

要注意的点比较多,1)nums必须用pass-by-value;2)myPermute只有不能再swap。通过一些test case能发现不这么做会出错,例如[1,1,2,2]。比较科学的解释暂时还没有想好,有待继续做深入的研究。

回溯的方法在涉及到pass-by-reference时要谨慎考虑不同分支间可能存在的相互影响,但是回溯递归的程序一般又难以清晰的理解其运行顺序。可能我题目练的还是太少了,或者没有掌握别的分析回溯的黑科技。加油吧。

当然这个题目可以直接用set来排出重复,那样的话就简单多了,直接套LC46的代码再加一步去重就行了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值