最近在一些算法题中,遇到了不少次借助排列组合得到结果的情况。相较于数学中求得排列组合数量的问题,算法中常常需要得到所有的排列组合项,以便在一些枚举步骤中辅助求解。下面给出简化版的排列组合问题的定义:排列问题:在 n 个数字中选择 m 个,其中 m <= n,考虑所选数字的顺序
组合问题:在 n 个数字中选择 m 个,其中 m <= n,不考虑所选数字的顺序注:以下不考虑数字重复的情况,如果实际问题中需要考虑,可借助 HashMap 过滤。
1. 排列( Permutation )
1.1 参考思路
我们可以将问题这么看待:
从数组 s 中取 m 个数 = 取出数字 N + 从去除 N 的数组 {s - N}
中取 m - 1
个数
这是明显的递归思路,核心在于:
- 对于任意一层递归,数字 N 的选取按照固定顺序,这里采用从左到右,即按数组下标递增的顺序;
- 递归边界在于
{s - N}
中元素的个数为 1 时。
在编码时,我们需要为递归函数准备一些变量:
- 给定的待选数组;
- 可选个数;
- 前面递归层级中已经选择的 N,N',N''... 的元素集合;
- 用于存储可选类型的容器。
1.2 参考代码
按照上述参考思路,我们可以得到排列问题解法中,递归部分的代码:
/**
* 递归函数
*
* @param count 可选个数
* @param candidates 给定的待选数组
* @param bag 前面递归层级中已经选择的 N,N',N''... 的元素集合
* @param container 用于存储可选类型的容器
*/
def traverse(count: Int, candidates: ArrayBuffer[Int], bag: ArrayBuffer[Int], container: ArrayBuffer[ArrayBuffer[Int]]): Unit = {
val candidatesLength = candidates.length
if (candidatesLength >= count) {
if (count.equals(1)) {
for (item <- candidates) {
val finalBag = bag.clone()
finalBag += item
container += finalBag
}
} else {
for ((item, index) <- candidates.zipWithIndex) {
val nextBag = bag.clone()
nextBag += item
val nextCandidates = candidates.slice(0, index) ++ candidates.slice(index + 1, candidatesLength)
traverse(count - 1, nextCandidates, nextBag, container)
}
}
}
}
则排列问题的入口方法为:
def getAllPermutations(count: Int, candidates: ArrayBuffer[Int]): ArrayBuffer[ArrayBuffer[Int]] = {
val container = ArrayBuffer[ArrayBuffer[Int]]()
traverse(count, candidates, ArrayBuffer[Int](), container)
container
}
代码 Gist 地址:Select all permutations of N elements from the collection. · GitHub
2. 组合( Combination )
2.1 参考思路
由于不考虑数字的顺序,相较于排列问题,组合问题仅有一处不同,即:
从数组 s 中取 m 个数 = 取出数字 N + 从去除 N 的数组 {s - N}
中取 m - 1
个数。但当递归的上一层级由左至右遍历时,下一层中的 {s - N}
仅包含上一层级右侧的数。如下图:
在排列问题中,如果当前层级选择的是箭头所指向的数字,则其左侧和右侧的数字都会被当做 candidates
被传入下一层级:
涉及代码为:
val nextCandidates = candidates.slice(0, index) ++ candidates.slice(index + 1, candidatesLength)
traverse(count - 1, nextCandidates, nextBag, container)
而在组合问题中,只有右侧的数字会被传入下级( 当按照由左至右的顺序遍历时 ):
涉及代码为:
val nextCandidates = candidates.slice(index + 1, candidates.length)
traverse(count - 1, nextCandidates, nextBag, container)
2.2 参考代码
递归代码:
/**
* 递归函数
*
* @param count 可选个数
* @param candidates 给定的待选数组
* @param bag 前面递归层级中已经选择的 N,N',N''... 的元素集合
* @param container 用于存储可选类型的容器
*/
def traverse(count: Int, candidates: ArrayBuffer[Int], bag: ArrayBuffer[Int], container: ArrayBuffer[ArrayBuffer[Int]]): Unit = {
val candidatesLength = candidates.length
if (candidatesLength >= count) {
if (count.equals(1)) {
for (item <- candidates) {
val finalBag = bag.clone()
finalBag += item
container += finalBag
}
} else {
for ((item, index) <- candidates.zipWithIndex) {
val nextBag = bag.clone()
nextBag += item
val nextCandidates = candidates.slice(0, index) ++ candidates.slice(index + 1, candidatesLength)
traverse(count - 1, nextCandidates, nextBag, container)
}
}
}
}
入口方法:
def getAllPermutations(count: Int, candidates: ArrayBuffer[Int]): ArrayBuffer[ArrayBuffer[Int]] = {
val container = ArrayBuffer[ArrayBuffer[Int]]()
traverse(count, candidates, ArrayBuffer[Int](), container)
container
}
代码 Gist 地址:Select all combinations of N elements from the collection. · GitHub
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。