【Py/Java/C++三种语言OD独家2024D卷真题】20天拿下华为OD笔试之【DP】2024D-分月饼【欧弟算法】全网注释最详细分类最全的华为OD真题题解

有LeetCode算法/华为OD考试扣扣交流群可加 948025485
可上 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1441了解算法冲刺训练(备注【CSDN】否则不通过)

从2024年4月15号开始,OD机考全部配置为2024D卷
注意两个关键点:

  1. 会遇到C卷复用题。虽然可能存在幸存者偏差,但肯定还会有一大部分的旧题。
  2. 现在又支持做完题目之后倒回去改了。就是可以先做200的再做100的,然后可以反复提交。
    在这里插入图片描述

题目描述与示例

题目描述

中秋节,公司分月饼,m个员工,买了n个月饼,m <= n,每个员工至少分1个月饼,但可以分多个,单人分到最多月饼的个数是Max1,单人分到第二多月饼个数是Max2Max1-Max2 <= 3,单人分到第n-1多月饼个数是Max(n-1),单人分到第n多月饼个数是Max(n)Max(n-1)- Max(n) <= 3,问有多少种分月饼的方法?

输入描述

每一行输入m n,表示m个员工,n个月饼,m<=n

输出描述

输出有多少种月饼分法

示例

输入

2 4

输出

2

说明

分法有2种:

4=1+3
4=2+2

注意: 1+33+1算一种分法

解题思路

用比较严谨的数学语言表达上述问题为:挑选出m个非递减的数,要求相邻两个数的差值不超过3,这m个数的和为n,问一共有多少种挑选方式。

由于在挑选完第i个数之后,第i+1个数的取值和第i个数的取值相关,很容易想到用动态规划来解决上述问题。思考动态规划三部曲:

  1. dp数组的含义是什么?

我们需要考虑三个因素,选择了第几个数,这个数取了什么数值,当前总和为多少。因此需要构建一个三维的dp数组。

考虑dp[i][j][k]表示,第i个数取值为j时,前i个数的总和为k的方法数。

分别考虑ijk三个数的取值范围来确定dp数组的大小。

ik的定义比较明确,范围分别是[1, m][1, n]

每个数字的取值j的最小为1,最大的情况为m-1个数字都选择最小数1,剩余一个最大数选择n-(m-1) = n-m+1。故j的取值范围是[1, n-m+1]

故构建dp数组是一个大小为(m+1)*(n-m+2)*(n+1)的三维数组。

  1. 动态转移方程是什么?

假设第i个数的取值为j,那么第i+1个数只能在jj+1j+2j+3中进行挑选。

若此时前i个数的总和为k,那么当第i+1个数

  • 取了j时,前i+1个数的总和为k+j。存在dp[i+1][j][k+j] += dp[i][j][k]
  • 取了j+1时,前i+1个数的总和为k+j+1。存在dp[i+1][j+1][k+j+1] += dp[i][j][k]
  • 取了j+2时,前i+1个数的总和为k+j+2。存在dp[i+1][j+2][k+j+2] += dp[i][j][k]
  • 取了j+3时,前i+1个数的总和为k+j+3。存在dp[i+1][j+3][k+j+3] += dp[i][j][k]

上述四个式子可以合并为一个式子,即dp[i+1][j+d][k+j+d] += dp[i][j][k],其中d的取值为[0,3]

先从小到大遍历i,再从小到大遍历j,再从小到大遍历k,则代码如下

for i in range(1, m):
    for j in range(1, n-m+2):
        for k in range(i, n+1):
            for d in range(4):
                if j+d < n-m+2 and k+j+d < n+1:
                    dp[i+1][j+d][k+j+d] += dp[i][j][k]
  1. dp数组如何初始化?

i = 0没有实际意义,不考虑。

考虑i = 1的情况,第1个数字的取值j最小为1,最大为n // m(即所有数字尽可能接近的情况),即此时j的取值为[1, n // m]

同时,由于只选择了一个数字,因此此时前i个数字的总和k = j

故对于i = 1,做如下初始化

dp = [[[0] * (n+1) for j in range(n-m+2)] for i in range(m+1)]
for j in range(1, n//m+1):
    dp[1][j][j] = 1

dp[1][j][j] = 1表示只对应1种方法数。

代码

python

# 题目:【DP】2023C-分月饼
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:DP
# 代码看不懂的地方,请直接在群上提问


# 输入员工人数m,月饼总数n
m, n = map(int, input().split())

# dp数组是一个大小为(m+1)*(n-m+2)*(n+1)的三维数组
# dp[i][j][k]表示,第i个数取值为j时,前i个数的总和为k的方法数
dp = [[[0] * (n+1) for j in range(n-m+2)] for i in range(m+1)]
# 第1个数字选了j,此时总和为j
# j的取值范围是[1, n//m]
# 因为m个数的和需要为n,那么最小那个数的最大值是n//m
for j in range(1, n//m+1):
    dp[1][j][j] = 1

# i的最大取值为m-1
for i in range(1, m):
    # j的最小取值为1,最大取值为n-m+1
    for j in range(1, n-m+2):
        # k的最小取值为i(前i个数都选了1,和为i),最大取值为n
        for k in range(i, n+1):
            # 增量d的取值范围为0,1,2,3
            for d in range(4):
                # 条件为j+d和k+j+d都没有超过对应的最大范围
                if j+d < n-m+2 and k+j+d < n+1:
                    dp[i+1][j+d][k+j+d] += dp[i][j][k]

ans = 0
# dp[m][j][n]表示第m个数(最后一个数)选了j后,总和为n的方法数
# 将所有的dp[m][j][n]加在一起即为答案
for j in range(n-m+2):
    ans += dp[m][j][n]

print(ans)

java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int m = scanner.nextInt();
        int n = scanner.nextInt();

        // dp array initialization
        int[][][] dp = new int[m + 1][n - m + 2][n + 1];
        for (int j = 1; j <= n / m; j++) {
            dp[1][j][j] = 1;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j <= n - m + 1; j++) {
                for (int k = i; k <= n; k++) {
                    for (int d = 0; d < 4; d++) {
                        if (j + d < n - m + 2 && k + j + d <= n) {
                            dp[i + 1][j + d][k + j + d] += dp[i][j][k];
                        }
                    }
                }
            }
        }

        int ans = 0;
        for (int j = 1; j <= n - m + 1; j++) {
            ans += dp[m][j][n];
        }

        System.out.println(ans);
    }
}

cpp

#include <iostream>
using namespace std;

int main() {
    int m, n;
    cin >> m >> n;

    // dp array initialization
    int dp[m + 1][n - m + 2][n + 1] = {0};
    for (int j = 1; j <= n / m; j++) {
        dp[1][j][j] = 1;
    }

    for (int i = 1; i < m; i++) {
        for (int j = 1; j <= n - m + 1; j++) {
            for (int k = i; k <= n; k++) {
                for (int d = 0; d < 4; d++) {
                    if (j + d < n - m + 2 && k + j + d <= n) {
                        dp[i + 1][j + d][k + j + d] += dp[i][j][k];
                    }
                }
            }
        }
    }

    int ans = 0;
    for (int j = 1; j <= n - m + 1; j++) {
        ans += dp[m][j][n];
    }

    cout << ans << endl;

    return 0;
}

时空复杂度

时间复杂度:O(NM(N-M))。三重循环所需时间复杂度。

空间复杂度:O(NM(N-M))。三维dp数组所需空间。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务300+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值