LeetCode (18) Permutations I & II (排列一、二)

不存在重复的情况:题目描述

Given a collection of 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], and [3,2,1].

本题要求输入一数组,输出数组的所有排列方式。题目中给定的为int类型的数组,有些题目中给定的为字符类型的,两种思路是一样的。在LeetCode中有两道关于Permutation的题目,第一题给定的数组不存在duplicate,也就是没有重复元素,第二题中存在重复元素。

递归解题思路

对于排列可以通过交换元素得到。例如,输入为[1,2,3]可以由以下思路得到:

  • [1,2,3]本身为一个排列
  • 交换后两个元素得到新排列:[1,3,2]
  • 对于原组合交换第一个和第三个元素得到[3,2,1]
  • 对上一个组合交换后两个元素[3,1,2]
  • 对原组合交换第一个和第二个元素[2,1,3]
  • 交换后两个元素得到[2,3,1]

也就是说可以通过从后往前交换元素得到新的排列,但是在对交换后的排列进行交换的时候会打乱原来的顺序,因此可能得到重复的排列方式。所以可以再交换后恢复到原始组合,再对原始组合进行交换,避免重复的发生。从下面这张图上可以参考排列的思路:
Permutation

代码如下:

class Solution {
public:
    void Perm(vector<int>& num, int i, vector<vector<int> > &r)
    {
        if (i == num.size())
        {
            r.push_back(num);
        }

        for (int k = i; k != num.size(); ++k)
        {
            swap(num[k], num[i]);
            Perm(num, i + 1, r);
            swap(num[k], num[i]);
        }
    }
    vector<vector<int> > permute(vector<int> &num) {
        if (!num.size())
        {
            vector<vector<int> > r;
            return r;
        }

        vector<vector<int> > r;
        Perm(num, 0, r);
        return r;
    }
};

从上述代码中可以看出,递归的调用了Perm函数得到新的排列,且排列的实际实现是从后往前交换得到的,在每次交换完之后恢复顺序避免了数组被打乱。

非递归解题思路

上面给出了递归的解决办法,这里给出非递归的方法。在STL中已经给出了实现排列的函数next_permutation得到下一个排列,循环调用函数,直到没有新的排列为止,代码如下:

class Solution {
public:
    vector<vector<int> > permuteUnique(vector<int> &num) {
        vector<vector<int> > permu;
        sort(num.begin(), num.end());
        permu.push_back(num);
        while (next_permutation(num.begin(), num.end()))
        {
            permu.push_back(num);
        }
        return permu;
    }
};

当然,直接调用STL函数其实也算是“作弊”的做法了,毕竟这里考的就是算法嘛。那既然STL存在这样的一个函数,我们很自然的想到,我们能否自己写一个类似的函数呢。答案当然是肯定的(O(∩_∩)O~~~),下面就来介绍一下next_permutation算法。

若输入数组为[3,2,1,4]

  • 对给定的数组进行排序,这里排序后即[1,2,3,4];
  • 从后往前找第一个顺序对,所谓顺序对即第一个数小于第二个数,如(3,4);
  • 顺序对中较小的数字对应的位置即为交换点,如上面例子中3的位置。交换交换点上的数字与从后往前第一个比交换点上数值大的数字。因为交换点数字位于一个顺序对中,因此肯定是可以找到这样的数字的(上面例子中为4);
  • 将交换后的交换数字后面的所有数字顺序颠倒。(上面例子中交换后3为最后一个数字,因此不需要交换)
  • 重复上面的步骤,直到数组中不存在顺序对为止(此时的数组变为[4,3,2,1])

该算法不仅对非重复数组有用,对重复的是一样的,因为两个元素相等是为非顺序对,所以不会交换,也就不会出现重复的情况了。

根据上面的描述我们很容易写出下面的代码:

class Solution {
public:
    void reverse(vector<int>& num, int i)
    {
        int j = num.size() - 1;
        while (i < j)
        {
            swap(num[i++], num[j--]);
        }
    }

    bool nextPerm(vector<int>& num)
    {
        int i = num.size() - 1;
        int j;
        while (i)
        {
            j = i;
            i--;
            if (num[i] < num[j])
            {
                int k = num.size() - 1;
                while (num[k] <= num[i])
                    k--;
                swap(num[i], num[k]);
                reverse(num, j);
                return true;
            }
        }
        reverse(num, i);
        return false;
    }

    vector<vector<int> > permute(vector<int> &num) {
        if (!num.size())
        {
            vector<vector<int> > r;
            return r;
        }

        vector<vector<int> > r;
        sort(num.begin(), num.end());
        int count(0);
        do 
        {
            r.push_back(num);
        } while (nextPerm(num));
        return r;
    }
};

数组中存在重复数字的题目描述

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], and [2,1,1].

递归解法

在使用上面的递归解法的时候是无法处理数组存在重复数值的情况的。例如对题目描述中的例子,如果存在重复数字,则递归调用仍然会得到六种排序,其中有三种重复。因此,在递归调用中我们有以下两种思路:

1、 使用sort排序,然后排除相邻的重复数字。注意没排序的不可以这么判断
2 、利用一个新的容器,装已经处理的数字,不重复处理数字,利用map和set等都可以。

第一种解法代码如下所示:

vector<int> mediRes;
vector<vector<int> > res;
vector<vector<int> > permuteUnique(vector<int> &num) {      
        sort(num.begin(), num.end());
        permuII(num);
        return res;
    }

    void permuII(vector<int> &num)
    {
        int m = num.size();
        if (m == 1) 
        {
            mediRes.push_back(num[0]);
            res.push_back(mediRes);
            mediRes.pop_back();
            return;
        }
        //如果已经排序了的就可以值处理相邻的重复了
        for (int i = 0; i < m; i++)
        {
            while (i < m-1 && num[i] == num[i+1]) 
                i++;
            mediRes.push_back(num[i]);
            vector<int> cur = num;
            cur.erase(cur.begin()+i);
            permuII2(cur);
            mediRes.pop_back();//注意:别忘记了这里需要弹出,不要等到循环结束
        }
    }

思路二的解法为:

vector<vector<int> > res;
vector<int> mediRes;
vector<vector<int> > permuteUnique(vector<int> &num) {
    permuII(num);
    return res;
}

void permuII(vector<int> &num)
{
    int m = num.size();
    if (m == 1) 
    {
        mediRes.push_back(num[0]);
        res.push_back(mediRes);
        mediRes.pop_back();
        return;
    }
    unordered_set<int> used;
    for (int i = 0; i < m; i++)
    {
        if (used.find(num[i]) == used.end())
        {
            mediRes.push_back(num[i]);
            vector<int> cur = num;
            cur.erase(cur.begin()+i);
            permuII(cur);
            mediRes.pop_back();
            used.insert(num[i]);
        }
    }
}

非递归解法

非递归解法可以参考第一题的方法。

参考资料:http://blog.csdn.net/kenden23/article/details/17166105

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值