打印正整数n拆分的所有情况

Posted 仲翎逸仙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了打印正整数n拆分的所有情况相关的知识,希望对你有一定的参考价值。

题目:把一个正整数n拆分成若干个正整数的相加,列出所有组合 例如:
4=4
4=1+3=2+2
4=1+1+2
4=1+1+1+1
动机:网上有好多解答,大部分都是给出拆分结果的个数,不能把把每一种的情况打印出来,或者效率低,本人接触这个题目很久了,最近心血来潮想搞定它,嘿嘿

思路一:
设置一个递归方法recursive(int last, int curSum, int n),该方法包含以下几个步骤:
0. 递归出口: 当 last+curSum > n ,返回
1. 该递归方法包含三个参数
a. last:上个递归传递给它的起始数字
b.curSum:当前累加和
c. n:待拆分的数字
2. 设置变量i从last开始到n遍历:
a. curSum=curSum+i;
b. list(成员变量)加入i
c. 判断curSum==n ,如果成立,打印list
3. 递归调用 recursive(i, curSum,n);
4. 还原curSum,list,消除下一层递归对本层递归在成员变量curSum,list的影响。

实现代码
package com.uestc.miaoshi;

import java.util.ArrayList;
import java.util.Scanner;
public class BreakN2 
    public static void main(String[] args) 
        Scanner in = new Scanner(System.in);
        while (in.hasNextInt()) 
            int n = in.nextInt();
            count = 0;
            recursive(1, 0 , n);
            System.out.println("所有组合总数" + count);
        
    

   static int count = 0;
   static ArrayList<Integer> list = new ArrayList<>();
   static void recursive(int last, int curSum, int n) 
        if (last + curSum > n) return;

        for (int i = last; i <= n ; i++) 
            curSum = curSum + i;
            list.add(i);
            if (curSum == n) 
                count ++;
                System.out.println(list);
            
            recursive(i, curSum, n);
            curSum = curSum - i;
            Integer o = i;
            list.remove(o);
        
    



输入 5 ,打印出以下结果:
这种思路的优点是容易思考,缺点是当n很大时,耗时大,归根是算法效率低。下面我们继续介绍一种效率高的解决方法。
思路二:动态规划,设置一个函数 f(n, m) ,该函数的功能就是把正整数分成 m等分, 并且m 份中不允许为0,也就是说每一份至少大于等于1. 我们可以得到状态转移方程:
f(n ,m ) = f(n-m,m) + f(n-1, m-1)
看到这里如果,你能经过自己的思考就明白这个公式,我只能膜拜,因为你的天赋很高,哈哈,好了,言归正传。
方程的第一项 f(n-m, m) 代表m份全部都大于1的分法,也可以这样理解,先把 m 拆分成 m 个 1 ,每一份都是1 ,然后 再把 n - m 分到 m 份中
方程的第二项是 f(n-1, m-1)代表至少存在一份为1的情况,那么怎么保证呢?先从n中拿出1,放到m份中的1份,然后把 n-1分到 m-1份
总结: f(n,m)=f(n-m, m)不包含1的组合+f(n-1, m-1)包含1的组合,一分为二的思想
实现代码:

package com.uestc.miaoshi;
import java.util.Scanner;
public class BreakN 
    public static void main(String[] args) 
        Scanner sc = new Scanner(System.in);
        while (sc.hasNextInt()) 
            long start = System.currentTimeMillis();
            int res = 0;
           int n = sc.nextInt();
            for (int i = 1; i <= n; i++) 
                res = res + f(n, i);
            
            System.out.println("总共组合数: " + res);
            long end = System.currentTimeMillis();
            System.out.println("耗时秒数:"+ (end - start)/1000);
        
    
    /**
     * 函数功能,把正整数 n 分成 m 等份  4 = 1+3 = 2+2
     * @param n
     * @param m
     *  f(n) = f(n-m, m) + f(n-1, m-1)
     */
    private static int f(int n, int m) 
        if ( n <=0 || m <= 0 || n < m) 
            return 0;
        
        if (m == 1) return 1;
        return f(n - m, m) + f(n-1, m-1);
    


其实说到这里我们只是把问题解决了一半,我们可以轻松的得出所有组合的总数,我们还不能把每一种组合的具体情况打印出来。咋办呢,山人自有妙计。
思路我就照着下面这个图讲吧
首先定义树的数据结构:

class TreeNode 
    int n, m;
    ArrayList<Integer> res = new ArrayList<>();
    boolean addAll;
    TreeNode left, right, parent;

    public TreeNode(int n, int m, boolean addAll) 
        this.n = n;
        this.m = m;
        this.addAll = addAll;
    

addAll =false表示f(n-1, m-1), 也就是代表m份包含1的分法,由下而上回溯时,上结点的res=下结点的res加上一个元素1
addAll =true表示f(n-m, m), 也就是代表m份不包含1的组合的分法,由下而上回溯时,上结点的res=下结点的res每个元素加上1
res表示具体的分法
我们可以把图看成是二叉树的升级版本(每一个节点都有父指针),叶子节点表示具体的分法,比如f(1,1) = 1 ,f(3,1)=3。
我们可以从叶子结点回溯到根结点,例如
f(1,1) = 1, addAll=false,向上回溯到f(2,2)结点,导致f(2,2)结点的res =[1,1]
f(2,2),res=[1,1],addAll=true,向上回溯到f(4,2)结点,导致f(4,2)结点的res=[2,2]
f(3,1),res=[3],addAll=false,向上回溯到f(4,2)结点,导致f(4,2)结点的res=[1,3]

下面看具体实现代码:

package com.uestc.miaoshi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

class TreeNode 
    int n, m;
    ArrayList<Integer> res = new ArrayList<>();
    boolean addAll;
    TreeNode left, right, parent;
    public TreeNode(int n, int m, boolean addAll) 
        this.n = n;
        this.m = m;
        this.addAll = addAll;
    

public class BreakNToM 
    static ArrayList<TreeNode> leafs = new ArrayList<>();
    static ArrayList<ArrayList<Integer>> result = new ArrayList<>();

    public static void main(String[] args) 
        Scanner in = new Scanner(System.in);
        while (in.hasNextInt()) 
            long start = System.currentTimeMillis();
            int n = in.nextInt();
            int m = in.nextInt();
            leafs.clear();
            f(n, m, true);

            for (TreeNode leaf : leafs) 
                recursive(leaf);
            
            long end = System.currentTimeMillis();
            System.out.println( (end - start)/1000);

            System.out.println(result);
        
    
    private static void recursive(TreeNode leaf) 
        if (leaf.parent == null) 
            Collections.sort(leaf.res);
            result.add(leaf.res);
            return;
        
        ArrayList<Integer> res = leaf.res;
        if (leaf.addAll) 
            ArrayList<Integer> temp = new ArrayList<>();
            for (int x : res) 
                temp.add(x + 1);
            
            leaf.parent.res = temp;
         else 
            res.add(1);
            leaf.parent.res = res;
        
        recursive(leaf.parent);
    

    static TreeNode f(int n, int m, boolean allAdd) 
        if (n < m || n <= 0 || m <= 0) 
            return null;
        
        TreeNode node = new TreeNode(n, m, allAdd);
        if (n == m) 
            TreeNode leaf = new TreeNode(n, n, allAdd);
            for (int i = 0; i < m; i++) 
                leaf.res.add(1);
            
            leafs.add(leaf);
            return leaf;
        
        if (m == 1) 
            TreeNode leaf = new TreeNode(n, 1, allAdd);
            leaf.res.add(n);
            leafs.add(leaf);
            return leaf;
        

        node.left = f(n - m, m, true);
        if (node.left != null)
            node.left.parent = node;
        node.right = f(n - 1, m - 1, false);
        if (node.right != null)
            node.right.parent = node;
        return node;
    




 输入 8  3  


搞定

以上是关于打印正整数n拆分的所有情况的主要内容,如果未能解决你的问题,请参考以下文章

整数的lqp拆分

bzoj2173 整数的lqp拆分

[bzoj2173]整数的lqp拆分

LeetCode-343. 整数拆分

c语言:实现对于给定的正整数N,依次打印出小于等于N的所有素数。两种方法及其优化

用java将一个正整数拆分成若干个正整数的和,问有多少种分法?