一.引言

 

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

示例:

输入: n = 4 k = 2
输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4]]

n=4 代表待组合 list = [1,2,3,4]

二.递归调用

1.方法参数

combine 为主方法,getCombine 为实际递归调用的方法,主要有5个参数:

res[list] : 保存存储结果

prefix[lsit] : 保存当前前缀

n : 给定数字,代表1-n的数字范围

k : 代表组合的个数

start : 标识当前起始位置

2.方法思路

采用递归的方式遍历

(1) 从1开始固定位置作为前缀,随后遍历1之后的元素并添加到前缀中

(2) 当 k = 0 时代表此时前缀的长度已达到预定长度 k,此时将 prefix 添加到 result

(3) 随后执行 pop 操作,删掉前缀最后一位并将 start 加1,相当于固定另外一个数字继续遍历

(4) 当 start 大于 n 时代表遍历完全部数组元素,

class Solution(object):
    def combine(self, n, k):
        res = []
        self.get_combine(res, [], n, k, 1)
        return res

    def get_combine(self, res, prefix, n, k, start):
        if k == 0:
            res.append(list(prefix))
        elif start <= n:
            prefix.append(start)
            self.get_combine(res, prefix,
                             n, k - 1, start + 1)
            prefix.pop()
            self.get_combine(res, prefix,
                             n, k, start + 1)

上面的思路其实还是比较晦涩,下面结合运行日志理一下:

计算多种可能的Python函数 python 所有可能组合_计算多种可能的Python函数

A.代表最开始固定第一位并向后寻找

前进:函数从 k = 2 , start = 1 开始,此时前缀 append 得到 [1],这里注意,当前长度 k 和 前缀 prefix 有一个关系,即 当前长度 K + 前缀 len(prefix) = k = 2,所以当前长度 K 为0是,代表此时 len(prefix) = k = 2,即找到一个满足条件的结果,所以添加到 result 

回退:当 k = 0 时,结束当前结构体,返回上一层递归,随后执行 prefix.pop 删除最后一个元素,并将 start + 1,继续向后探索,所以 prefix = [1,2] => [1],然后 start = start + 1 = 3,此时 k=1,继续递归 start = 3 加入 prifix [1] => [1,3] ,k = 1 -> 0 继续保存,随后寻找下一个数字

B.代表固定位置的数字的所有可能遍历完毕,继续固定下一个数字

[1,2],[1,3],[1,4] 添加完毕后,1 的所有可能已经结束了,一直回退程序直到 prefix -> [],此时开始执行 start + 1,即前缀变为 [2],然后固定2,往后找3,找4,随后回退直到 prefix -> [],再从3开始

C.代表任务结束

当 [3,4] 添加到结果并且 prefix pop 到 [] 时,要开始固定下一个位置,此时 k = 2,start = 5 始终 > n = 4,所以程序不再递归,直到退出并返回 res

三.全排列

1.方法思路

如果上面不好理解,其实还有偷懒的方法 : 对 1-n 的数字全排列随后截断去重,给定 n = 4, k = 2

(1) 对数组 [1,2,3,4] 全排列得到全排列全部结果:

[[1, 2, 3, 4], 
 [1, 2, 4, 3], 
 [1, 3, 2, 4],
 ...
 [4, 1, 2, 3]]

(2) 随后将取全排列数组 top-k:

[[1, 2], 
 [1, 2], 
 [1, 3],
 ...
 [4, 1]]

(3) 然后去重 "12","12","13",...,"41" 得到:

{'12', '24', '34', '23', '14', '13'}

2.实现

全排列之前介绍了几种方法在: 求数组全排列 中,这里给出其中一种方法,回朔法:

def solveByBackTrack(nums):
    def backtrack(position, end):

        if position == end:
            res.append(nums[:])
            return

        for index in range(position, end):
            nums[index], nums[position] = nums[position], nums[index]
            backtrack(position + 1, end)
            nums[index], nums[position] = nums[position], nums[index]

    res = []
    backtrack(0, len(nums))
    return res

def getArrangeByBackTrack(n, k):
    nums = list(range(1, n + 1))
    candidates = set() # 去重
    results = solveByBackTrack(nums) # 全排列
    for i in results:
        tmp = i[:k] # 截断
        tmp.sort()
        candidates.add(''.join(list(map(lambda x: str(x), tmp))))
    return candidates

# 全排列
n = 4
k = 2
re = getArrangeByBackTrack(n, k)
print(re)
{'12', '13', '24', '14', '23', '34'}

全排列截断虽然思路清晰,但是随着 n 的增加,堆栈的递归深度和线程占用非常严重,这里仅供偷懒,不做实际使用

四.API-itertools

itertools 提供 combinations 方法可以直接获取对应排列,如果不是做题而是学习工作中有需求可以直接调用该方法:

import itertools
   
    n = 4
    k = 2
    nums = list(range(1, n+1))
    can = list(itertools.combinations(nums, k))
    print(can)
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]

五.耗时

常规 n, k

n

k

cost

递归

4

2

1.9311904907226562e-05

全排列

4

2

4.1961669921875e-05

API

4

2

4.76837158203125e-06

n >> k 

n

k

cost

递归

20

2

0.00011992454528808594

全排列

20

2

so loooooooooooooooong...

API

20

2

3.910064697265625e-05

该用哪个不该用哪个很明显了吧 ~