LeetCode 47. 全排列 II【递归】

难度: 中等

题目描述

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

示例:

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

思路1

记录每个数字还能够被选择的次数numCnt,在选择数字时用numCnt代替used

代码

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<int> now_p;
        vector<vector<int>> all_p;
        vector<pair<int, int>> numCnt;
        
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size();) {
            int num = nums[i], cnt = 1;
            while (i + cnt < nums.size() && nums[i + cnt] == num) ++cnt;
            i += cnt;
            numCnt.emplace_back(num, cnt);
        }
        
        function<void(int index)> generate;
        generate = [&generate, &nums, &numCnt, &now_p, &all_p](int index) {
            if (index == nums.size()) {
                all_p.push_back(now_p);
                return;
            }
            
            for (auto &i : numCnt) {
                if (i.second) {
                    --i.second;
                    now_p.emplace_back(i.first);
                    generate(index + 1);
                    now_p.pop_back();
                    ++i.second;
                }
            }
        };

        generate(0);
        return all_p;
    }
};

思路2

C++标准库中有一个函数next_permutatuin,46和47题都可以用这个水过,这个函数只需要给出一个排列,就可以返回按照字典序的下一个排列,而且即使有重复元素也没问题,它是怎么实现的呢?cppreference 上给出了一种可能的实现:

template<class BidirIt>
bool next_permutation(BidirIt first, BidirIt last)
{
    if (first == last) return false;
    BidirIt i = last;
    if (first == --i) return false;
 
    while (true) {
        BidirIt i1, i2;
 
        i1 = i;
        if (*--i < *i1) {
            i2 = last;
            while (!(*i < *--i2))
                ;
            std::iter_swap(i, i2);
            std::reverse(i1, last);
            return true;
        }
        if (i == first) {
            std::reverse(first, last);
            return false;
        }
    }
}

按照字典序,最小的排列是不递减的排列,最大的排列是不递增的排列。而且对于任意一种排列,它的右侧都有一段不递增的序列(长度可以是1)假设这段排列右侧不递增的部分的长度是n,那么按照字典序,紧挨着这个排列的下一个排列应该是:

  • 从右侧开始数,第n+1个位置之后的元素不变。
  • 第n+1个位置的元素应该变大,但是又要尽可能得小(才能满足紧邻),这就要从右侧的n个元素中选择一个恰好大于a[n+1]的元素,二者互换。
  • 右侧n个位置按照不递减排序(reverse一下就行)。

看懂了这段代码之后我感觉我对这个问题的理解都加深了,把这段代码的思想用到这道题里就有了下面的代码。

代码

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());

        vector<vector<int>> all_p;
        function<void(int index)> generate;
        generate = [&generate, &nums, &all_p](int index) {
            if (index == nums.size() - 1) {
                all_p.push_back(nums);
                return;
            }

            while (true) {
                generate(index + 1);
                if (nums[index] >= nums[index + 1]) {
                    // if (index == 0) reverse(nums.begin(), nums.end());  // 把num恢复
                    break;
                }
                // 使用lower_bound
                // auto out = lower_bound(nums.begin() + index + 1, nums.end(), nums[index], greater<>()) - 1;
                // swap(nums[index], *out);
                int out = nums.size();
                while (nums[--out] <= nums[index]);
                swap(nums[index], nums[out]);
                reverse(nums.begin() + index + 1, nums.end());
            }
        };

        generate(0);
        return all_p;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值