0%

排列、组合、子集

排列:
LC31 Next Permutation[找到一个大于,不能是大于等于]
LC46 Permutations[别忘了排序]
LC60 Permutation Sequence

子集:
LC78 子集

组合:
LC77 组合

排列

下一排列/上一排列

STL中的排列操作

  • std::is_permutation: 判断一个序列是否为另一个序列的排列
  • std::next_permutation: 变换范围 [first, last) 为来自所有按相对于 operator< 或 comp 的字典序的下个排列。若这种排列存在则返回 true ,否则变换范围为首个排列(如同用 std::sort(first, last) )并返回 false 。
  • std::prev_permutation: 变换范围 [first, last) 为来自于相对于 operator< 或 comp 的字典序的所有排列集合的上个排列。若这种排列存在则返回 true ,否则变换范围为末排列(如同用 std::sort(first, last); std::reverse(first, last); )并返回 false 。

简单实现

  • 下一排列

    • 找到序列a的最长不增后缀,即从后往前遍历序列,找到i, 使得a[i - 1] < a[i], 该后缀记为a[i:]。如果找不到这样的i,说明当前排列已经是最大的排列了。比如[5,4,3,2,1],可以看到该序列中不存在这样一个元素a[i]使得a[i - 1] < a[i]。最大排列为排序的逆序。
    • a[i:]已经是不增的序列了。换句话说,a[i:]已经是自己的最大排列了。我们不能靠交换a[i:]中的元素来获得更大的排列。我们尝试交换a[i:]中的元素和a[:i]中的元素来获得更大的排列。
    • 为了让修改后的序列是下一排列,我们不能让a[i - 1]变得太大。我们考虑更改a[i:]前一个元素a[i - 1]。我们找到a[i:]中第一个比a[i - 1]的元素,用它和a[i - 1]交换。[注意此处必须是大于,不能是大于等于]
    • ❓交换后的后缀a[i:]仍然是一个不增的后缀。为了获得最小的,有新的前缀的序列,我们把a[i:]逆序即可。[对于最后两步的原理还不是很明白]
  • 例子[1,3,5,4,2]:

    • 找到最长不增后缀:[5,4,2] —> i = 2, a[i] = 5
    • 找到该后缀中第一个大于后缀前一个元素(a[1])的元素 —> a[3] = 4。
    • 交换a[4]和a[1] —> [1,4,5,3,2]
    • 逆序后缀 —> [1,4,2,3,5]

详细解释见: Next lexicographical permutation algorithm, Project Nayuki

产生所有排列

递归法

一个序列$S = \{1, 2, 3, \cdots , n\}$的所有排列为
固定$1$, $S - \{1\}$的所有排列
固定$2$, $S - \{2\}$的所有排列
$\cdots$
固定$n$, $S - \{n\}$的所有排列

使用下一排列产生所有排列:

该方法较采用递归法效率较高。但是如果要产生所有排列,需要先把序列排序称为最小排列。

第k个排列

LC60, Permutation Sequence
详细解法见:全排列生成算法:next_permutation, 程序控, cnblogs

子集

LC78 子集

$\varnothing$的幂集为$\{\varnothing\}$
$\{a\}$的幂集为$\{\varnothing, \{a\}\}$ = 空集的幂集 并 空集的幂集中所有的集合并a产生的集合
同理:$\{a,b\}$的幂集为$\{\varnothing, \{a\}, \{b\}, \{a,b\}\}$

推论:求一个集合A的幂集,我们可以先从这个集合中去掉一个元素a。求去掉元素a的集合$A - {a}$的幂集P,然后将再将a和幂集P中所有集合求并得到集合Q。A的幂集为P+Q。

  • 为了防止迭代器失效,我们先将结果幂集vector<vector<int>>初始化为我们想要的大小。
  • C++11中提供计算2的整数次幂的函数exp2,我们可以用它来计算幂集的大小。
  • 此时幂集中的vector<int>全为空集,我们初始化一个迭代器last,让他指向第一个空集之后。
  • 对于nums中的元素n,不断的将区间[begin, last)之间的vector拷贝,push_back(n),之后再放回集合。可以通过transform实现。

nums数组总元素个数为N,每次构造一个子集最坏需要$O(N)$的时间,一共有$\Theta(2^N)$个集合,因此复杂度为$O(N2^N)$

组合

回溯#组合