JavaScript递归-啤酒问题
内容纲要
1. 题目
假设啤酒2元1瓶,4个瓶盖可以换一瓶啤酒,2个空瓶可以换一瓶啤酒。请问10元钱总共可以喝多少瓶酒,剩余多少瓶盖和空瓶,以及期间总共置换了几轮。
2. 答案模板
总共执行了xxx轮置换,期间:总共换了xxx瓶啤酒,剩余金钱xxx元,剩余瓶盖xxx个,剩余空瓶xxx个。
最终答案:
总共执行了6轮置换,期间:
总共换了15瓶啤酒,
剩余金钱0元,
剩余瓶盖3个,
剩余空瓶1个。
3. 解题思路
抛砖引玉一下,期待大家的思路...
主要基于面向对象的思想,来分析并解决此问题。
(一)、在这道题目中,总共涉及到5个成员变量,它们分别是:
- 初始的金钱数(以及最终剩余的金钱数),这里用成员变量 money 表示;
- 总共执行了几轮换算,这里用成员变量 round 表示;
- 每轮换算开始时,手里的瓶盖数量(以及最终剩余的瓶盖数量),这里用成员变量 bottleCap 表示;
- 每轮换算开始时,手里的空瓶数量(以及最终剩余的空瓶数量),这里用成员变量 emptyBottle 表示;
- 用来统计总共换了几瓶啤酒的数量,这里用成员变量 total 表示;
因此,可以定义一个名为 Beer
的 class 类,并且声明 constructor
构造函数,接收初始的金钱数后,初始化上述的5个成员变量,示例代码如下:
class Beer {
// 构造函数,初始化基础数据
constructor(money) {
// 初始钱数
this.money = money
// 轮数
this.round = 0
// 每轮可用的瓶盖数量
this.bottleCap = 0
// 每轮可用的空瓶数量
this.emptyBottle = 0
// 用来统计最终的啤酒数量
this.total = 0
}
}
(二)、在 Beer
类中,定义名为 calcTheCountOfBeer
的函数,用来递归地进行每轮的啤酒换算,函数封装思路如下:
- 只要执行了当前的
calcTheCountOfBeer
函数,就证明进行了一轮啤酒置换,需要让成员变量round
累计加1; - 每轮置换期间,需要做如下 5 件事儿:
- 第 1 件事儿:计算本轮的剩余金钱(money)可以换几瓶酒;
- 第 2 件事儿:计算本轮的剩余瓶盖(bottleCap)可以换几瓶酒;
- 第 3 件事儿:计算本轮的剩余空瓶(emptyBottle)可以换几瓶酒;
- 第 4 件事儿:把本轮可以换到的啤酒数量,累加到成员变量 total 中,并计算下一轮可用的金钱、瓶盖、空瓶;
- 第 5 件事儿:如果钱、瓶盖、空瓶数量都不足以置换啤酒,则退出递归;否则把本轮剩余的金钱、瓶盖、空瓶代入到下次递归中。
因此,可以在 Beer
类中,定义名为 calcTheCountOfBeer
的递归函数如下:
calcTheCountOfBeer() {
// 0. 只要执行一次 calcTheCountOfBeer 函数,就让轮数加1
this.round++
// 1.1 计算当前的 money 可以买几瓶酒
let roundCount = Math.floor(this.money / 2)
// 1.2 计算瓶盖可以换几瓶酒,并得到剩余的瓶盖
if (this.bottleCap >= 4) {
roundCount += Math.floor(this.bottleCap / 4)
this.bottleCap = this.bottleCap % 4
}
// 1.3 计算空瓶可以换几瓶酒,并得到剩余的空瓶
if (this.emptyBottle >= 2) {
roundCount += Math.floor(this.emptyBottle / 2)
this.emptyBottle = this.emptyBottle % 2
}
// 2. 把本轮可以购买的啤酒数量累加到 total 中
this.total += roundCount
// 3.1 计算下一轮可用的钱、瓶盖、空瓶
this.money = this.money % 2
// 3.2 本轮换到的啤酒,会产生相同数量的瓶盖和空瓶,在下一轮可以继续用于置换啤酒
// 因此需要把 roundCount 分别累加到 bottleCap 和 emptyBottle 中
this.bottleCap += roundCount
this.emptyBottle += roundCount
// 4.1 如果钱、瓶盖、空瓶数量都不足以置换啤酒,则退出递归
if (this.money < 2 && this.bottleCap < 4 && this.emptyBottle < 2) return
// 4.2 否则,继续执行递归操作,计算下一轮可以换几瓶啤酒,以及剩余多少钱、瓶盖、空瓶
this.calcTheCountOfBeer()
}
4. 参考代码
整体代码在 Node.js
环境下运行,因此用到了 module.exports
和 require
实现模块化处理。
(一)、定义名为 beer.js
的 Node.js 模块,使用 module.exports
向外导出名为 Beer
的 class 类:
module.exports = class Beer {
// 构造函数,初始化基础数据
constructor(money) {
// 初始钱数
this.money = money
// 轮数
this.round = 0
// 每轮可用的瓶盖数量
this.bottleCap = 0
// 每轮可用的空瓶数量
this.emptyBottle = 0
// 用来统计最终的啤酒数量
this.total = 0
}
calcTheCountOfBeer() {
// 0. 只要执行一次 calcTheCountOfBeer 函数,就让轮数加1
this.round++
// 1.1 计算当前的 money 可以买几瓶酒
let roundCount = Math.floor(this.money / 2)
// 1.2 计算瓶盖可以换几瓶酒,并得到剩余的瓶盖
if (this.bottleCap >= 4) {
roundCount += Math.floor(this.bottleCap / 4)
this.bottleCap = this.bottleCap % 4
}
// 1.3 计算空瓶可以换几瓶酒,并得到剩余的空瓶
if (this.emptyBottle >= 2) {
roundCount += Math.floor(this.emptyBottle / 2)
this.emptyBottle = this.emptyBottle % 2
}
// 2. 把本轮可以购买的啤酒数量累加到 total 中
this.total += roundCount
// 3.1 计算下一轮可用的钱、瓶盖、空瓶
this.money = this.money % 2
// 3.2 本轮换到的啤酒,会产生相同数量的瓶盖和空瓶,在下一轮可以继续用于置换啤酒
// 因此需要把 roundCount 分别累加到 bottleCap 和 emptyBottle 中
this.bottleCap += roundCount
this.emptyBottle += roundCount
// 4.1 如果钱、瓶盖、空瓶数量都不足以置换啤酒,则退出递归
if (this.money < 2 && this.bottleCap < 4 && this.emptyBottle < 2) return
// 4.2 否则,继续执行递归操作,计算下一轮可以换几瓶啤酒,以及剩余多少钱、瓶盖、空瓶
this.calcTheCountOfBeer()
}
}
(二)、和 beer.js
模块平级,定义名为 index.js
的测试模块,计算 10 元钱可以置换几瓶啤酒:
// 1. 导入刚才声明的 beer.js 模块,这里使用相对路径,注意访问路径是否正确噢
const Beer = require('./bear')
// 2. 初始化 Beer 类的实例,通过构造函数把初始 money 传递进去
const beer = new Beer(10)
// 3. 调用 beer 实例对象的 calcTheCountOfBeer 函数,递归进行啤酒置换
beer.calcTheCountOfBeer()
// 4. 输出打印置换的结果
console.log(`总共执行了${beer.round}轮置换,期间:\n总共换了${beer.total}瓶啤酒,\n剩余金钱${beer.money}元,\n剩余瓶盖${beer.bottleCap}个,\n剩余空瓶${beer.emptyBottle}个。`)
11条评论
dad
if (this.money < 2 这个条件其实不用判断,第一次买完酒之后剩下的钱肯定不能在用了
泥巴巴
是的👍
yeti
彬哥,我在用vue重写大事件项目的时候发现头像图片以base64发送过去之后重新获取用户信息时头像图片user_pic值却为空,而且在写修改nickname和email时后台返回修改信息成功但再次获取时无变化;上述问题我也通过postman进行了测试,结果还是一样,我想知道是彬哥的接口出现了小问题麻?
使用的接口是:http://www.liulongbin.top:3007
小白
斌哥,https://www.uinav.com 黑马优购接口还有用吗,没有的话就不写了,怕搞到一半,接口没用
泥巴巴
不清楚诶,这是黑马维护的接口,你找下他们的老师问问~
小白
斌哥,https://www.uinav.com 黑马优购接口还有用吗,没有的话就不写了,怕搞到一半,浪费了时间
汤姆布利伯的沙雕之旅
不能发截图,有点可惜了,哈哈哈
汤姆布利伯的沙雕之旅
还望彬哥指正一下,期待彬哥回复
泥巴巴
整体思路是对的哈,细节上存在微小的失误。主要体现在 if (p >= 2) {} 和 else if (g >= 4) {} 这里。因为 if … else if 分支属于2选一的结构,因此如果瓶盖的数量和空瓶的数量同时满足条件,会只执行 if 分支,而导致重复累加 numbers 置换次数的情况噢。
汤姆布利伯的沙雕之旅
感谢彬哥的的指正,嘿嘿
汤姆布利伯的沙雕之旅
// 2元1瓶酒、4个瓶盖换1瓶酒、2个空瓶换1瓶酒
// 10元可以喝多少瓶?剩余多少个瓶盖、多少钱、置换了几轮、还剩几个瓶子
function getNums(Money) {
let count = parseInt(Money / 2) // 表示一开始总共买的啤酒数
let p = parseInt(Money / 2) // 表示一开始空瓶的数量
let g = parseInt(Money / 2) // 表示一开始瓶盖的数量
let numbers = 0 // 置换的次数
console.log(getPg(p, g));
function getPg(p, g) {
if (p >= 2) {
let temp1 = parseInt(p / 2) // 临时变量用来表示置换的瓶酒数等于新增加的空瓶数
numbers += temp1
g += temp1
p -= 2 * temp1
p += temp1
return getPg(p, g)
} else if (g >= 4) {
let temp2 = parseInt(g / 4)
numbers += temp2
p += temp2
g -= 4 * temp2
g += temp2
return getPg(p, g)
} else {
return `总共执行了:${numbers}轮置换,期间共换了:${numbers + count}瓶酒,剩余:${g}个瓶盖,剩余: ${p}个空瓶子,剩余:${Money % 2 === 0 ? 0 : Money % 2}元`
}
}
}
getNums(10)
// 总共执行了:10轮置换,期间共换了:15瓶酒,剩余:3个瓶盖,剩余: 1个空瓶子,剩余:0元