leetcode 47. 全排列 II(去重的两种思路)

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

示例:

输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

题目链接

如果不考虑去重,那么这道题就是一道非常基础的递归题目,比较基础。这里主要说一下去重的过程,去重的思想倒是不难想到,每一位每一种数字只能出现一次

比如数组[1,1,2],递归时让第一位是1,然后第二位是1,第三位是2,一次递归完成没有问题,得到排列[1,1,2],于是保存答案进行回溯,回溯到第二位,让第二位为2,注意第二位是第一次出现了2,所以没问题,再让第三位为1,于是又有了一个正确的排列[1,2,1],保存回溯。

接着回溯到了第二位,第二位已经没有可以放的数字了,于是又回溯到第一位,这时候第一位应该选择第二个数,也就是1,可是第一位已经出现过数字1了,这种情况就不能选择,也就是说只要遇到这种情况能正确退出我们就可以去重了。

根据这种思路就能得到第一种去重,定义一个occur[i][j]数组,代表第i位是否出现过j这个数字。

add是整个给定数组中的最小值,然后取反,再加回到原数组就能保证整个数组都是正数了,如果不这样数组中有负数occur数组会越界。

这样写的关键是考虑occur数组清空的时机,我一开始认为occur数组根本不用清空(即不写下面代码中的memset),但是我发现[1,1,2]这个样例我就过不去,不清空就不能输出[2,1,1]这个答案,这是因为第二位已经出现过1这个数字了,当第一位选择2这个数字时,不清空就代表我第二位不能选择1这个数字,这显然不合理。

因为第二位刚才出现1时,第一位也是1,现在我第一位已经变成数字2了,我第二位当然可以选择数字1了。所以每次当回溯的时候,我们要把occur[now]清零(now代表现在是第now位在选择数字),因为前面now-1位的数字不同,now位可选择的数字当然也不同。

这是我的想法,想法比较好想到,但是占用的空间较大。

class Solution {
public:
    int occur[105][1005];
    int vis[105];
    int num[105];
    vector<vector<int>>ans;
    int add;
    void dfs(int now,int sum,int size,vector<int> nums)
    {
        if(sum==size)
        {
            vector<int>tmp;
            for(int i=0;i<size;i++)
                tmp.push_back(num[i]);
            ans.push_back(tmp);
            return;
        }
        for(int i=0;i<size;i++)
        {
            if(vis[i]==1||occur[now][nums[i]+add])
                continue;
            num[now]=nums[i];
            vis[i]=1;
            occur[now][nums[i]+add]=1;
            dfs(now+1,sum+1,size,nums);
            vis[i]=0;
        }
        memset(occur[now],0,sizeof(occur[now]));//**注意清空occur的时机,这一位全部遍历完要回溯时就清空。**
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        int size=nums.size();
        for(int i=0;i<size;i++)
            add=min(add,nums[i]);
        add=-add;
        dfs(0,0,size,nums);
        return ans;
    }
};

下面说说答案的做法,答案是这么想的,对于一串连续相同的数字,我们选择数字的顺序只能有一种,比如[1,1,1,2]这组数,假设现在从第0位到第2位要选择这三个连续相同的1,我让第0位到第2位分别选择第1,2,3个1即可,当然你可以让第0位到第2位分别选择第3,2,1个1,甚至你可以让0到2位选择第3,1,2个1,总之不论按照什么顺序,你只需要确定这种顺序只有一种,那么就不会产生重复。

假如我选择了两种顺序,我让0到2位既选择1,2,3个1,又选择3,2,1个1,那么必然会出现重复。

官方题解给了这种去重的代码,如果我们按照1,2,3的顺序选择1,那么代码就是这样的:

if(i>0&&nums[i]==nums[i-1]&&!vis[i-1])
   continue;

如果前一位还被没有访问过,那么肯定不是1,2,3的顺序,所以continue。

当然也可以按照3,2,1的顺序选择,这样的代码就变成了:

if(i>0&&nums[i]==nums[i-1]&&vis[i-1])
   continue;

如果前一位被选择过,这就说明肯定不是3,2,1的顺序,直接跳出即可。

完整代码如下:

class Solution {
public:
    int vis[105];
    int num[105];
    vector<vector<int>>ans;
    void dfs(int now,int sum,int size,vector<int> nums)
    {
        cout<<now<<" "<<sum<<endl;
        if(sum==size)
        {
            vector<int>tmp;
            for(int i=0;i<size;i++)
                tmp.push_back(num[i]);
            ans.push_back(tmp);
            return;
        }
        for(int i=0;i<size;i++)
        {
            if(vis[i]==1||(i>0&&nums[i]==nums[i-1]&&vis[i-1]))
                continue;
            cout<<i<<endl;
            num[now]=nums[i];
            vis[i]=1;
            dfs(now+1,sum+1,size,nums);
            vis[i]=0;
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        int size=nums.size();
        sort(nums.begin(),nums.end());
        dfs(0,0,size,nums);
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值