算法题放苹果:把M个相同的苹果放到N个完全相同的盘子里,有多少种放法?

题目描述

在这里插入图片描述
链接:点我做题

题解

思路1 暴力递归

  我们利用递归来解决这个问题,不妨这样思考,假设apples表示当前剩余的苹果数,plates表示当前剩余的盘子数,ways(apples, plates)表示以apples个苹果放进plates个盘子里的方法数。
  如果apples==0,说明当前苹果都已经放完了,既然放完了,不管剩不剩盘子,这都是一种放的方法,向上一层返回1;
  否则apples不为0的时候,如果plates==0了,盘子已经被用完了但是苹果没放完,这种情况显然是摆放失败了,不是一种摆放的方法,向上一层返回0;
  再考虑一种特殊情况,当盘子数大于苹果数时,那么苹果怎样也不可能放到多出来的盘子上,这种情况下的摆放方法数显然和盘子减少为 p l a t e s − a p p l e s plates-apples platesapples个,apples个苹果的摆放方法数是一样多的,因此
i f ( a p p l e s < p l a t e s ) , w a y s ( a p p l e s , p l a t e s ) = w a y s ( a p p l e s , p l a t e s − a p p l e s ) if (apples <plates),\\ways(apples, plates)=ways(apples,plates-apples) if(apples<plates),ways(apples,plates)=ways(apples,platesapples)
  当剩余的苹果数量大于盘子数量时,我们有两种互斥的摆法
  一种,我们用上所有的盘子,也就是先让所有的盘子都摆上一个苹果,那么这种方式的摆法数量a显然等于 w a y s ( a p p l e s − p l a t e s , p l a t e s ) ways(apples - plates, plates) ways(applesplates,plates);
  另一种方法,我们不用上所有的盘子,那么怎么才能不用上所有的盘子呢?先放弃一个盘子,剩下的盘子是放弃还是保留交给递归来讨论。也就是说,不完全使用所有盘子的摆放方法数量b等于 w a y s ( a p p l e s , p l a t e s − 1 ) ways(apples, plates - 1) ways(apples,plates1).
  这两种情况显然是互斥的,所以他们的方法数可以直接相加,也就是说
i f ( a p p l e s > p l a t e s ) w a y s ( a p p l e s , p l a t e s ) = w a y s ( a p p l e s − p l a t e s , p l a t e s ) + w a y s ( a p p l e s , p l a t e s − 1 ) if (apples>plates)\\ ways(apples,plates)=ways(apples-plates,plates)\\+ways(apples, plates-1) if(apples>plates)ways(apples,plates)=ways(applesplates,plates)+ways(apples,plates1)
代码:

int ways(int apples, int plates)
{
	//apples个完全相同的苹果放到plates个完全相同的盘子里
	//有多少种方法
	//如果苹果用完了 说明摆放结束了 返回一种摆法
	if (apples == 0) {
		return 1;
	}
	//如果plates等于0了 apples不等于0还 那么说明没有盘子用了
	//0种摆法
	if (plates == 0) {
		return 0;
	}
	//如果盘子数大于苹果数 那么多出来的盘子不论什么摆法一定空着
	//这种情况下的摆法数等于把多出来的盘子都不考虑的摆法数
	if (plates > apples) {
		return ways(apples, apples);
	}
	//当苹果数大于盘子数的时候 有两种方法
	//1) 我们把所有的盘子都使用 那么相当于先往所有盘子上都放一个苹果
	// 这种方法的方法数等于ways(apples - plates, plates)
	int a = ways(apples - plates, plates);
	//2) 我们让盘子不全都使用 那么就敲碎一个盘子 
	//剩下的盘子全用还是敲碎一个 总可能方法数用递归去讨论
	int b = ways(apples, plates - 1);
	//这两种情况显然是互斥的 所以数量可以直接加
	return a + b;
}

思路2:缓存思想——动态规划来优化暴力递归

  所有的暴力递归的展开过程都可以用动态规划来优化.我们想想递归是怎么解决问题的,比如遇到ways(7, 5),它是展开为ways(7,4)和ways(2,5)来解决的,然后ways(2,5)和ways(7,4)再接着展开,那如果我们之前早就计算过ways(2,5)了呢,如果已经计算过一次了,还有必要再接着展开吗?直接用上次计算的结果不就好了,这样不就可以把递归层数变浅了吗。
  那么怎么记录上次计算的结果呢?这里可以用一个二维数组dp缓存存起来,并且考虑到多组输入问题,我们可以把创建二维数组dp的过程放到执行ways方法外头,这样下一组输入也可以使用我们上一轮计算过的值。
  为了和0种方法区分,我们不妨先让dp的所有值都赋-1,表示尚未计算过,在递归的每一层都检查当前层中dp的是否不等于-1,如果是,说明这层没必要继续展开下去了,已经计算过了,返回 d p [ a p p l e s ] [ p l a t e s ] dp[apples][plates] dp[apples][plates],否则再执行展开过程,并且最后把这一层的计算结果记录到dp中作为缓存。

代码:

#include <vector>
#include <iostream>
using namespace std;
int ways(int apples, int plates, vector<vector<int>>& dp)
{
    if (dp[apples][plates] != -1)
    {
        return dp[apples][plates];
    }
    int ans = 0;
    if (apples == 0)
    {
        ans = 1;
    }
    else if (plates == 0)
    {
        ans = 0;
    }
    else if (apples < plates)
    {
        ans = ways(apples, apples, dp);
    }
    else
    {
        int a = ways(apples - plates, plates, dp);
        int b = ways(apples, plates - 1, dp);
        ans = a + b;
    }
    dp[apples][plates] = ans;
    return ans;
}

int main()
{
    int apples, plates;
    vector<vector<int>> dp(11, vector<int>(11, -1));
    while (cin >> apples >> plates)
    {
        cout << ways(apples, plates, dp) << endl;
    }
    return 0;
}
  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值