写在前面

“这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情、专注,决定了在做某件事时能走多远,能做多好。” 该系列文章由python编写,所刷题目共三个来源:之前没做出来的 ;Leetcode中等,困难难度题目; 周赛题目;某个专题的经典题目,所有代码已AC。每日1-3道,随缘剖析,希望风雨无阻,作为勉励自己坚持刷题的记录。

整数划分Ⅰ

周打卡 每日打卡 java功能实现_算法


因为题目中有不允许空份的要求,所以n一定大于等于k。

动态规划

这是排列组合的思路:将n个球放在k个盒子中,球和盒子没有区别,不允许空盒。将上述情况划分为两部分:

  • 至少有1个盒子只有一个小球。此时dp[n][k]=dp[n-1][k-1]
  • 不存在1个盒子只有一个小球,即所有盒子都至少有一个小球。此时我们可以从每个盒子中都拿出1个盒子,则动态转移方程为 dp[n][k]=dp[n-k][k]

注意,在n==k的时候,只满足第一种情况,否则出现空盒。

n,k = tuple(input().split(" "))
	n,k = eval(n),eval(k)
	dp = [[0 for _ in range(k+1)] for _ in range(n+1)]
	# 放置到1个盒子中只有1种
	for _ in range(1, n+1):
	    dp[_][1] = 1
	# 空盒的情况不存在,即0,i的情况应该是0种方案
	# 1个球放置到大于自己盒子的情况不存在
	for _ in range(2, k+1):
	    dp[1][_] = dp [0][_] = 0
	for ni in range(2,n+1):
	    # k一定满足小于等于n
	    for ki in range(1,k+1):
	        dp[ni][ki] = dp[ni-1][ki-1]+dp[ni-ki][ki]
	    if k==ni:
	        dp[ni][ni] = dp[ni-1][ni-1]
	print(dp[n][k])

深度遍历做法

import math
	def dfs(tmp, s, cur):
	    global ans
	    if cur == k:
	        if s == n:
	            ans += 1
	        return
	    # 防止重复,每次枚举要比上一次大
	    # 剪枝,枚举到i*(k-cur)+s<=n即可
	    for _ in range(tmp, math.ceil((n-s+1)/(k-cur))):
	        dfs(_, s+_, cur+1)
	
	dfs(1, 0, 0)
	print(ans)

整数划分Ⅱ

周打卡 每日打卡 java功能实现_周打卡 每日打卡 java功能实现_02

动态规划

与上一题的区别在于,将n个球放在k个盒子中,球和盒子没有区别,允许空盒,k不知道。所以此时的动态规划方程为n个球,n为最大值,递推方程为:

周打卡 每日打卡 java功能实现_整数划分_03


第三个式子是将n拆成n只有一种情况,第四个式子是将n分成最大值为m的式子有两种情况:

  • 含m,则划分此刻相当于去掉一个m后,按照m再次划分(允许放重复个数)
  • 不含m,则划分中所有值都比m小,即n的(m-1)划分,个数为f(n,m-1)

整数划分Ⅲ:其它变体

  • N划分成若干个不同正整数之和:只需要将含m的情况中,最大值去掉m即可,dp[n][m]=dp[n][m-1]+dp[n-m][m-1]
  • N划分成若干个奇/偶正整数之和:使用f[i][j]g[i][j]分别表示划分成j个奇数/偶数,此处按照奇数为例子给出动态转移方程。
  • 对g而言,因为划分后每个都是偶数,所以有g[i][j] = f[i-j][j] ,每个偶数减去1变成奇数,共j个
  • 划分中包含1和不包含1(此时从每个数中拿出1来)的情况,f[i][j] = f[i-1][j-1] + g[i-j][j]

其实这里也可以基于g求是否包含2的情况,只不过i和j两个变量需要都有递归方程递归到起始条件(0,1这样的边界)就可以。