整数划分问题(python)--递归 and 动态规划(m个盘里放n个苹果思想类似)

这篇博客旨在对正整数划分的多种题目就递归和动态规划进行讨论与总结

以下将正整数划分分为三种题型:1.一般性,即对个数以及大小以及重复性不加约束 2.对重复性有约束 3.对元素的个数有约束。至于每个元素的大小则可以归并到第一类题:通过更改q(n,m)的参数m即可。

1.

问题描述:

描述
将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。 
正整数n 的这种表示称为正整数n 的划分。正整数n 的不同的划分个数称为正整数n 的划分数。
输入
标准的输入包含若干组测试数据。每组测试数据是一个整数N(0 < N <= 50)。
输出
对于每组测试数据,输出N的划分数。
样例输入
5
样例输出
7
提示:
5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1

解题思路

如题,令p(n)表示正整数n的划分数,则难以找到递归关系,因此增加一个自变量m,设定最大加数n1不大于m的划分个数。即p(n)=q(n,m),则有以下关系:

1.首先当n=0:return 1:表示已经被分完

2. m=0:则无法实现,即return 0

3.当m=1 or n=1:则return 1(n=1:则由1组成;m=1:则由n个1组成这两种情况都是一种方式)

4.当n<m则满足:q(n,m)=q(n,n) :n的划分由n1<=n的划分组成

5.当n=m:q(n,m)=1+q(n,m-1):正整数n的划分由n1=n的划分和n1<=n-1的划分组成

6.n>m>1:q(n,m)=q(n,m-1)+q(n-m,m):正整数n的最大加数不大于m的划分由n1=m的划分和n1<=m-1的划分组成

其中第一条用于控制划分的情况,第二条用于可以划分的终止条件

代码实现:

1.递归:则根据以上的解题思路进行写代码即可:

#-*-coding:utf-8 -*-
def dfs(n,m):
    if n==0:
        return 1
    elif m==0:
        return 0
    elif n==1 or m==1:
        return 1
    elif n<m:
        return dfs(n,n)
    elif n==m:
        return dfs(n,m-1)+1
    else:
        return dfs(n,m-1)+dfs(n-m,m)
if __name__ == '__main__':
    n,m=map(int,raw_input().split()) #如果没有限制,即m<=n即可,则将下一行中的m改成n即可
    print dfs(n,m)

2.动态规划:利用动态规划解题最重要的就是状态转移方程,还有边界控制,本题为体现一般性,则先进行利用动态规划打表即(m=n),然后根据实际的m输出dp[n][m]即可

状态转移方程:即解题思路的3、4、5,边界条件:dp[0][0]=1,注意建立(n+1)*(n+1)dp二维列表

#-*-coding:utf-8 -*-
#求整数n的划分数
while True:
    try:
        n=int(raw_input())
        dp=[[0 for i in range(n+1)] for j in range(n+1)]
        dp[0][0]=1
        for i in range(1,n+1):
            for j in range(1,n+1):
                if i < j:
                    dp[i][j]=dp[i][i]
                elif i==j:
                    dp[i][j]=1+dp[i][j-1]
                else:
                    dp[i][j]=dp[i][j-1]+dp[i-j][j]
        print dp[n][n]
    except:
        break

2.

问题描述大致1,增加限制条件,每一种划分方案的组成元素不重复

解题思路:

同题1不同的是:本题增加了限制条件不可重复性,有如下递归关系:

1.n=0:return 1,当n=0时,表示已经分完

1.m<1 :return 0:m=0的时候无法继续划分

3.n<m:p(n,m)=p(n,n) 

4.p(n,m)=p(n-m,m-1)+p(n,m-1) :p(n-m,m-1)表示先拿出一个m,剩下的n-m在n1<=m-1的划分数;p(n,m-1)指n在n1<=m-1的划分数

代码实现:

1.递归:注意递归的终止调价

2.动态规划:状态转移方程:dp[i][j]=dp[i-j][j-1]+dp[i,j-1]

#-*-coding:utf-8 -*-
#划分为不同整数
while True:
    try:
        n=int(raw_input())
        dp=[[0 for i in range(n+1)] for j in range(n+1)]
        dp[0][0]=1
        for i in range(n+1):
            for j in range(1,n+1):
                if j>i:
                    dp[i][j]=dp[i][i]
                else:
                    dp[i][j]=dp[i][j-1]+dp[i-j][j-1]
        print dp[n][n]
    except:
        break

3.

问题描述同1,增加限制条件,每一种划分方案规定元素个数

解题思路:

同题1不同的是:该题限定了元素个数m,则m<=n,因此p(n,m)的理解是n划分为m个元素,有如下递归关系:

1.m=1 or m=n:return 1:n划分为一个元素或者划分为n个元素,很明显只有一种情况

2.m<1 or n<1:return 0:0无法划分以及无法划分为0个元素

3.p(n,m)=p(n-m,m)+p(n-1,m-1) :p(n-m,m)是指m个都分1,然后n-m在m个元素上进行分;p(n-1,m-1)指n-1划分为m-1个数

代码实现:

1.递归:根据解题思路建立递归方程,注意终止条件

#-*-coding:utf-8 -*-
def dfs(n,m):
    if n==m or m==1:
        return 1
    elif n<1 or m<1:
        return 0
    else:
        return dfs(n-1,m-1)+dfs(n-m,m)
if __name__ == '__main__':
    n,m=map(int,raw_input().split())
    print dfs(n,m)

2.动态规划:状态转移方程:dp[i][j]=dp[i-1][j-1]+dp[i-j][j]

#-*-coding:utf-8 -*-
#求整数n划分成k个整数的划分数
while True:
    try:
        n,k=map(int,raw_input().split())
        dp=[[0 for i in range(n+1)] for j in range(n+1)]
        dp[0][0]=1
        for i in range(1,n+1):
            for j in range(i+1):
                if j==1:
                    dp[i][j]=1
                else:
                    dp[i][j]=dp[i-1][j-1]+dp[i-j][j]
        print dp[n][k]
    except:
        break

以上是本人对整数划分的几种情况就递归以及动态规划进行的分析,经实践证明,如果数据较大,递归运算时间比动态规划慢。虽然递归结构清晰,可读性强,而且易用数学归纳法证明算法的正确性,但是运行效率很低。因此,递归是设计算法的有效工具,优化算法时可以将递归转化为非递归算法。

ps:分苹果、分蛋糕问题也可以根据本文的思路解决。根据实际情况增加相应的限制条件即可






    原文作者:动态规划
    原文地址: https://blog.csdn.net/carson0408/article/details/77600862
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞