【算法学习】一个典型的递推问题分析

题目描述

In the United Kingdom the currency is made up of pound (£) and pence §. There are eight coins in general circulation:

1p, 2p, 5p, 10p, 20p, 50p, £1 (100p), and £2 (200p).

It is possible to make £2 in the following way:

1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p

How many different ways can £2 be made using any number of coins?

题目分析1

f(n,m)表示前n中钱币拼凑m元钱的方法总数 这种问题就是递推问题

对于递推问题我们首先需要推导他的公式

对于这道题,是否包含了第n种钱币(即前n-1种就可以拼凑出m元钱)是一个关键点

所以
f ( n , m ) = f ( n − 1 , m ) + f ( n , m − w [ n ] ) ( w [ n ] 表 示 第 n 中 钱 币 的 面 值 ) f(n,m) = f(n-1,m) + f(n,m-w[n])(w[n]表示第n中钱币的面值) f(n,m)=f(n1,m)+f(n,mw[n])(w[n]n)
将前n种钱币凑成m元钱拆成前n-1种钱币凑m元钱和一定用到第n种钱币的方法的加和

这两部分是两两相互独立的,所以满足等式,若两者不是相互独立的,我们需要用容斥原理将公共部分减掉

通过上述公式,要想知道f(n,m)必须先知道f(n-1,m)和f(n,m-w[n]),所以需要不断的向前递推

题目代码1

//递推
#include<stdio.h>
#define max_n 8
#define max_m 200

int w[max_n + 5] = {
    1, 2, 5, 10, 20, 50, 100, 200
};

int f[max_n + 5][max_m + 5]; 

int main(){
    for(int i = 0; i < max_n; i++){
        f[i][0] = 1;//递推的初始条件 用前i种货币凑0元的方法为一种
        for(int j = 1; j <= max_m; j++){
           //f[i][j] = 0;
           if(i >= 1) f[i][j] += f[i - 1][j]; //f[0][j]全部为1 即用1元生成j的方法 
           if(j >= w[i])  f[i][j] += f[i][j - w[i]];//用1 5块的生成2块 显然5不可能用得到
        }
    }
    printf("%d\n", f[max_n - 1][max_m]);
    return 0;
}

题目分析2

我们接着来看公式
f ( n , m ) = f ( n − 1 , m ) + f ( n , m − w [ n ] ) ( w [ n ] 表 示 第 n 中 钱 币 的 面 值 ) f(n,m) = f(n-1,m) + f(n,m-w[n])(w[n]表示第n中钱币的面值) f(n,m)=f(n1,m)+f(n,mw[n])(w[n]n)
在这里插入图片描述

我们可以看到当计算f(n,m)时,我们只需要上一行的同列数据和这一行前面的某个数据即可,所以我们可以将数组的行数减少,我们只需要两行数组即可,采用滚动数组的思想
在这里插入图片描述

题目代码2

//递推 滚动数组
#include<stdio.h>
#define max_n 8
#define max_m 200

int w[max_n + 5] = {
    1, 2, 5, 10, 20, 50, 100, 200
};

int f[2][max_m + 5]; //公式f(n,m) = f(n-1,m) + f(n,m-w[n]) f(n,m)只需要上一行的同一列值和这一行前面某列的值,所以只需要两行的值

int main(){
    for(int k = 0; k < max_n; k++){
        int i = k % 2;//i指针用来滚动数组0->0 1->1 2->0 3->1 4->0 5->1 6->0 7->1
        f[i][0] = 1;
        for(int j = 1; j <= max_m; j++){
           f[i][j] = 0;//这里记得必须要清零,不然会在原有数的基础上继续+
           f[i][j] += f[i ^ 1][j];//数组只有两行,按照公式,f[i][j] 的第一部分f[i - 1][j]肯定在另一行
           if(j >= w[k])  f[i][j] += f[i][j - w[k]];//如果2块一定要用5块来凑,则不存在
        }
    }
    printf("%d\n", f[(max_n - 1) % 2][max_m]);
    return 0;
}

题目分析3

在这里插入图片描述
由上图我们可以分析出只需要一维的数组即可,实现降为f[j]重新刷新i次,表示用前i种货币凑j元钱,只要在原有的基础上加上f(j-w[i])即可实现f(j)的计算。

当数组刷新了i次时,此时f(j)表示f(i,j),当进行第i+1次刷新时,在原有f(j)的基础上加上前面的f(j - w[i + 1])(此时的f(j-w[i+1])已经刷新为f(i+1,j-w[i+1]))即可得到f(i+1,j)

降维既省空间又省时间

题目代码3

#include<stdio.h>
#define max_n 8
#define max_m 200

int w[max_n] = {
    1, 2, 5, 10, 20, 50, 100, 200
};

int f[max_m + 5] = {0};

int main(){
    for(int i = 0; i < max_n; i++){
        f[0] = 1;
        for(int j = 1; j <= max_m; j++){
            if(j >= w[i]) f[j] += f[j - w[i]];
        }
    }
    printf("%d\n", f[max_m]);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沙diao网友

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值