排列组合

Index

排列

下一个排列

LeetCode - 31. 下一个排列

题目描述

思路

  • 相邻的两个排列有最长公共前缀,然后找到需要交换的高位低位

  • 根据字典序的定义,依照如下步骤寻找下一个排列

    字典序

  • 从后往前找需要改变的高位 hi,即第一个降序元素的位置

  • 从后往前找需要交换的低位 lo,即第一个大于 nums[hi] 的位置

  • 交换 nums[lo] 与 nums[hi]

  • 反转 hi 之后的序列,即 nums[hi+1: n)

    C++

上一个排列

LintCode - 51. 上一个排列

问题描述

思路

  • 实际上就是下一个排列的逆过程

  • 从右往左找第一个升序的位置 hi

  • 从右往左找第一个小于 nums[hi] 的位置 lo

  • 交换 nums[lo] 和 nums[hi]

  • 反转 hi 之后的位置

C++

STL 提供的实现(下一个排列、上一个排列) TODO

  • STL 提供了两个函数用于生成排列

  • 这两个函数均以字典序比较函数 lexicographical_compare()为基础生成下一个或上一个排列

  • 因此在使用这两个函数前,需要先对原序列进行排序

C++

第 k 个排列

LeetCode - 60. 第k个排列

问题描述

思路

  • 因为字典序的性质,实际不需要求出前 k-1 个序列

  • 整体思路有点像桶排序

  • {1 2 3 4 5} 为例,找出其中第 14 个序列

C++

全排列(无重复)

LeetCode 46. 全排列

题目描述

思路 1

  • 利用下一个排列,先对数组排序,然后不断生成下一个排列

思路 2

  • 深度优先搜索

  • 易知,当序列中的元素不重复时,存在 n! 种不同的排列;

  • 考虑第一个位置,有 n 种可能

  • 当选定了第一个位置,第二个位置有 n-1 种可能

  • 因为每次搜索的状态数是递减的,所以这里的 dfs 是一个循环递归的过程

基于插入的写法

  • 代码量多一点,但比较好理解

    ```C++ class Solution { vector > ret; vector tmp; vector used; int n = 0;

    void dfs(vector& nums, int step) { if (tmp.size() == n) { ret.push_back(tmp); return; }

    }

public: vector > permute(vector& nums) { n = nums.size(); used.resize(n, 0);

};

全排列(有重复)

LeetCode - 47. 全排列 II

题目描述

思路 1

  • 使用无重复时的方法,用 set 剔除重复(不推荐)

思路 2

  • 先对原序列排序,使相同的元素相邻;此时只处理第一个相同元素,其余跳过;

基于插入的写法

基于交换的写法

【注】全排序的时间复杂度

  • 不重复情况下,n 个元素的不同全排列为 n! 个,所以算法的时间复杂度至少为 O(N!)

  • 因此,全排列算法对大型的数据是无法处理的

组合

组合(n 选 k,无重复)

LeetCode - 77. 组合

问题描述

思路

C++

组合(n 选 k,有重复)

(未验证)

  • 如果要求每个组合中不重复,则可以先去重,再按照无重复的做法

  • 如果不要求去重,则直接按照无重复的做法即可

组合总和(数字不重复但可重复使用)

LeetCode - 39. 组合总和

思路

  • 深度优先搜索

  • 关键在于每个数字可以重复使用

C++

【注】关于 dfs(step+1)dfs(i+1)dfs(i) 的说明

组合总和 2(存在重复数字但每个数字只能使用一次)

LeetCode - 40. 组合总和 II

思路

  • DFS,关键是如何去除重复情况

C++

代码说明 1

  • 为例

  • 这段代码实际上并不会过滤 ——i == step 的情况

  • 真正重复的情况是 ,而这段代码的目的是过滤 ——i > step 的情况

组合总和 3(数字不重复且指定数量)

LeetCode - 216. 组合总和 III

问题描述

思路

C++

【说明】

字典序

  • 在处理排列问题时,通常时根据字典序来生成下一个排列

  • 在字典序中,记序列的升序为第一个排列,降序为最后一个排列

高位与低位

  • 对序列中任意两个位置而言,靠近左侧的为高位,靠近右侧的为低位

  • 生成排列的过程就是不断增大高位,减小低位的过程

关于 for(i=0;..)for(i=step;..) 的说明

【注】关于 dfs(step+1)dfs(i+1)dfs(i) 的说明

(以下均为个人小结,并没有严格验证)

dfs(step+1)dfs(i+1)

  • 简单来说,dfs(step+1) 指的是生成 tmp 序列中的第 step+1 个位置;dfs(i+1) 指的是使用 nums 中的第 i+1 个元素

  • 以不重复集合 {1 2 3 4} 为例说明:

    • 排列问题中用过的元素还可能被再次使用;

    • 而组合问题中使用过的元素,之后不再使用

    • 正是由于这个区别导致排列中应该使用 dfs(step+1),而组合中应该使用 dfs(i+1)

dfs(i+1)dfs(i)

  • 组合总和问题中,还用到了 dfs(i)

    • 一方面,它跟组合问题类似,用过的数字不再使用;因此使用的是 i 而不是 step

    • 另一方面,每个数字可以重复使用,因此使用的是 dfs(i) 而不是 dfs(i+1)

最后更新于

这有帮助吗?