算法思维方式—— 由排列组合想到的

最近算法题也刷了不少,小有感悟。

我觉得刷题时一般的思维方式是迭代思维。就是我们老是想着通过循环,通过顺序解决下一个来迭代解决整个问题。

典型事例有:2Sum, 3Sum, 排序问题,以及用双指针或快慢指针法解决的问题。

迭代思维是一种很直接的思维方式,但绝不简单,因为找到正确的循环方式并不是一件容易的事情。

但有些问题用迭代思维方式是很难解决的,或者说这些问题本身就不适合用循环来解。

比如求组合数问题。C(n, 2)还能用2层循环来解,但C(n, m)呢?用迭代就很难求解了,不自然。这是用递归思维方式却很自然。

先取一个,把问题化为C(n, m-1), 再取一个,把问题化为C(n, m-2), 如此递归即可。

    public static int combinationNum = 0;
    public static void combine(int[] target, int[] result, int st, int index,int m){
        if (m == 0){
            combinationNum++;
            for (int i : result){
                System.out.printf(i + " ");
            }
            System.out.println();
            System.out.println(combinationNum);
        }else {
            for (int i = index; i < target.length - m + 1; i++) {
                result[st] = target[i];
                combine(target, result, st+1, i+1,m-1);
            }
        }
    }

  求排列数也是如此,把A(n,n) 化为A(n-1,n-1),直至化为A(1,1)。

    public static void swap(int[] result, int st, int ele){
        int tmp = result[st];
        result[st] = result[ele];
        result[ele] = tmp;
    }

    //轮番把各个元素放在第一位,然后递归求解。注意复位。
    public static void arrange(int[] result, int st){
        if (st == result.length-1){
            for (int i : result){
                System.out.printf(i + " ");
            }
            System.out.println();
        }else {
            for (int i = st; i < result.length; i++) {
                swap(result, i, st);
                arrange(result, st+1);
                swap(result,i , st);
            }
        }
    }

  对于这类型的问题用递归的感觉就是干净,简洁,有一种逻辑的美感。

不过把问题递归化并不是一件容易的事。有一种常用的技巧是“一子动天下”。就是针对一个元素的有无进行分类讨论,这个元素常常是最后一个。这时往往可以把问题二分递归化。

上面两个例子实际上也是针对一个元素进行讨论,不过它们是把问题多分化,所以外层有循环来遍历。下面给出一个二分递归化的例子。

给出一个数组,里面是不重复的int 数字。 求满足和为S的所有数字组合。(数组[2, 3, 6, 7],  和为 7, 结果为:[ [7], [2, 2, 3] ] )。也就是换硬币问题。

这时我们可以针对最后一个元素是否包含,把问题二分化。然后递归遍历整个解法空间:

    private List<List<Integer>> result = new ArrayList<>();

    //一子动天下的典型事例,递归思维方式的典型。dfs遍历整个解法空间,自然得出结果。干净,简洁。
    public void combinations(int[] candidates, int target, List<Integer> combination, int limit){
        if (limit < 0 ){
            return;
        }else if (target == 0){
            result.add(combination);
        }else {
            if (target - candidates[limit] >= 0){
                combination.add(candidates[limit]);
                combinations(candidates, target - candidates[limit], new ArrayList<>(combination), limit);
                combination.remove(combination.size()-1);
            }
            combinations(candidates, target, combination, limit-1);
        }
    }

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        int limit = candidates.length-1;
        combinations(candidates, target, new ArrayList<Integer>(), limit);
        return result;
    }

  

算法问题多变,复杂。如果迭代不行,不妨试试递归方式。“一子动天下”是一种较好的讨论方式。

时间: 2025-01-02 15:14:33

算法思维方式—— 由排列组合想到的的相关文章

算法练习:排列组合之子集合

问题描述 输入一个含有不同数字的序列,输出其所有子集合(含空集).要求:1)集合里元素有序排列:2)输出结果不含有重复集合 举例 输入序列{3,1,2} 输出:{},{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3} 问题分析 可以使用排列组合问题求解的第一种方法:分期摊还.初始化时,结果集合里含有一个空集.当扫描数列时,保留原有集合,同时将当前元素插入现有的所在集合中,从而形成新的集合.详见后面代码的GetSubSetsAmortized函数. 也可以使用第二种方法:f

算法:C++排列组合

题目:给定1-n数字,排列组合. 解法:递归.第一个数字有n种选择,第二个数字有n-1种选择,依次递归排列输出.用数组表示n个数字,用过的数字置0. 实现语言:C++ #include <iostream> using namespace std; /************************************************************************/ /* num : 需要排列的数组 count : 数组总数 numC: 已经排列的数组 iUse:

算法基础:排列组合问题(Golang实现)

[排列组合问题] 一共N辆火车(0<N<10),每辆火车以数字1-9编号,要求以字典序排序输出火车出站的序列号. 输入: 包括N个正整数(0<N<10),范围为1到9,数字之间用空格分割,字符串首位不包含空格. 输出: 输出以字典序排序的火车出站序列号,每个编号以空格隔开,每个输出序列换行. 样例输入: 1 2 3 样例输出: 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 代码实现: package huawei import ( "fmt&qu

算法练习:排列组合之全排列

问题描述 输入一个不含相同数字的序列,输出所有可能的排列. 问题分析 与之前的"求解子集合"类似,使用递归方法:典型的在for循环内调用递归函数.不同的是,必须等到所有的数字均在集合里才能输出.为了记录每个数字的使用情况,还需一个辅助数组记录每个数字的使用情况.详见代码部分的FullPermutation函数. 扩展问题 如果数列中含有重复的数字,并且输出的结果不含重复组合,那么怎么处理?比如,输入{1,1,2},输出{1,1,2}, {1,2,2}, {2, 1, 1}.我们在挑选数

算法练习:排列组合之组合和

问题描述 给出一组不同的正整数序列和一个目标值,求出所有可能的组合,使得组合里所有元素和为目标值.要求: 1)每个组合里的元素按照升序排列. 2)输出组合里不含有重复的组合. 3)输入序列中的整数可以多次使用. 举例: 输入{2,3,4,7},目标值为7 输出{7},{2,2,3},{3,4} 问题分析 为了让输出元素按升序排列,可对输入序列进行排序.同这里我们使用递归的方法来解决这个组合问题,即典型的for语句内调用递归函数.需要注意以下几点: 1)记录剩余目标值和,只有当该值为0时,组合才是

【BZOJ】4555: [Tjoi2016&amp;Heoi2016]求和 排列组合+多项式求逆 或 斯特林数+NTT

[题意]给定n,求Σi=0~nΣj=1~i s(i,j)*2^j*j!,n<=10^5. [算法]生成函数+排列组合+多项式求逆 [题解]参考: [BZOJ4555][Tjoi2016&Heoi2016]求和-NTT-多项式求逆 $ans=\sum_{i=0}^{n}\sum_{j=0}^{i}s(i,j)*2^j*j!$ 令$g(n)=\sum_{j=0}^{n}s(n,j)*2^j*j!$ 则ans是Σg(i),只要计算出g(i)的生成函数就可以统计答案. g(n)可以理解为将n个数划分

迷人的算法-排列组合

需求 最近工作中碰到一个需求:我们的数据表有多个维度,任意多个维度组合后进行 group by 可能会产生一些”奇妙”的反应,由于不确定怎么组合,就需要将所有的组合都列出来进行尝试. 抽象一下就是从一个集合中取出任意元素,形成唯一的组合.如 [a,b,c] 可组合为 [a].[b].[c].[ab].[bc].[ac].[abc]. 要求如下: 组合内的元素数大于 0 小于等于 数组大小: 组合内不能有重复元素,如 [aab] 是不符合要求的组合: 组合内元素的位置随意,即 [ab] 和 [ba

STL_算法(17)_排列组合 next_permutation() perv_permutation()

next_permutation() prev_permutation() #include<iostream> #include<algorithm> #include<vector> // 排列组合开始之前一定要先排序 using namespace std; int main() { vector<int> ivec; ivec.push_back(1); ivec.push_back(2); ivec.push_back(3); for (vecto

java排列组合算法代码实现

原文:java排列组合算法代码实现 源代码下载地址:http://www.zuidaima.com/share/1550463479024640.htm java排列组合算法,有需要研究的童鞋可以下载,运行结果如下: package com.zuidaima.test; /** *@author www.zuidaima.com **/ public class Pailie { public static void main(String[] args) { int[] ia = {1, 2,