排列组合

813 阅读2分钟

排列组合,就是从n个元素中取m个元素,有多少种取法。数学的概率相关的部分。 突然发现有个题目需要用到排列组合,而我又不会,特此记录。 相当实用的一种穷举算法。

基本思路

大概原理就是,一个元素无法有两种情况,取它和不取它。在确定第一个元素的取舍之后,第二个元素面临同样的问题,要还是不要。边界情况就是,元素已经取够了, 或者剩余元素的数量正好就是还需取的数量。

还是拿小球说话吧, 现在一个不透明的袋子里有n个小球,先来个简单的,假设这个袋子里是一排卡槽,正好放n个小球。现在需要从里面取m个小球,我们从第一个槽开始取。 第一个槽分两种情况,取和不取。 在第一个槽取的情况下, 第二个槽也有两种情况取和不取 在第一个槽不取的情况下, 第二个槽也有两种情况取和不取。 这样就有四种情况,不断地分裂下去, 。。。。。。。。

直到,遇到边界情况。比如我们共有 10个小球,要取三个。

情况一, 已经取到了第六个,并且选择取,加上前面的两个小球正好是三个,于是这个情况停止分裂。

情况二, 已经取到了第九个,但是前面才取了一个球,现在只剩两个可以选了,别无他选,都取了, 结束分裂。

这样我们就把所有的情况列出来了。

代码

上面的是比较简单的一种情况,没考虑顺序,考虑顺序就要加上全排列了。不断的分裂,显然我们是要用递归高阶函数。

/* 排列组合 m个小球里取n个 */
function combine(arr : any[], n=0, cb = (n:any[]) => { }) {
    const m = arr.length;
    /*指针的位置 和收集的元素的集合 数组 */
    const help = (ind=0, els: any []) => {
        /* 先判断剩余元素有没有多余,没有就直接把剩余的全拿出来 */
        if (m - ind === n - els.length) {
            for (let i = ind; i < m; i++) {
                els.push(arr[i])
            }
            /* 取完了就执行回调 */
            return cb(els)
        }

        /* 还有判断 是不是已经取满了 */
        if (els.length === n) {

            /* 取完了就执行回调 */
            return cb(els)
        }
        /* 两种选择 */
        help(ind + 1, [...els, arr[ind]])
        help(ind + 1, [...els])

    }
    /* 从零开始 */
    help(0, [])
}
combine([1,2,3,4], 2,console.log)

按这个思路,是写出来了,但是我要刷的题还没解决。它还要去重。

情况是这样的这些小球是编号的,例如[1,2,3,4,4,5,6,7,8,9] 4号小球有两个,那么有可能取到两个不同的[3,4] ,但是从编号上来看没有什么不同,需要去重。等我想明白了,接着写去重的方法。