Javascript

JavaScript递归-啤酒问题

内容纲要

1. 题目

假设啤酒2元1瓶,4个瓶盖可以换一瓶啤酒,2个空瓶可以换一瓶啤酒。请问10元钱总共可以喝多少瓶酒,剩余多少瓶盖和空瓶,以及期间总共置换了几轮。

2. 答案模板

总共执行了xxx轮置换,期间:总共换了xxx瓶啤酒,剩余金钱xxx元,剩余瓶盖xxx个,剩余空瓶xxx个

最终答案:

总共执行了6轮置换,期间:
总共换了15瓶啤酒,
剩余金钱0元,
剩余瓶盖3个,
剩余空瓶1个。

3. 解题思路

抛砖引玉一下,期待大家的思路...

主要基于面向对象的思想,来分析并解决此问题。

(一)、在这道题目中,总共涉及到5个成员变量,它们分别是:

  1. 初始的金钱数(以及最终剩余的金钱数),这里用成员变量 money 表示;
  2. 总共执行了几轮换算,这里用成员变量 round 表示;
  3. 每轮换算开始时,手里的瓶盖数量(以及最终剩余的瓶盖数量),这里用成员变量 bottleCap 表示;
  4. 每轮换算开始时,手里的空瓶数量(以及最终剩余的空瓶数量),这里用成员变量 emptyBottle 表示;
  5. 用来统计总共换了几瓶啤酒的数量,这里用成员变量 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 的函数,用来递归地进行每轮的啤酒换算,函数封装思路如下:

  1. 只要执行了当前的 calcTheCountOfBeer 函数,就证明进行了一轮啤酒置换,需要让成员变量 round 累计加1;
  2. 每轮置换期间,需要做如下 5 件事儿:
  3. 第 1 件事儿:计算本轮的剩余金钱(money)可以换几瓶酒;
  4. 第 2 件事儿:计算本轮的剩余瓶盖(bottleCap)可以换几瓶酒;
  5. 第 3 件事儿:计算本轮的剩余空瓶(emptyBottle)可以换几瓶酒;
  6. 第 4 件事儿:把本轮可以换到的啤酒数量,累加到成员变量 total 中,并计算下一轮可用的金钱、瓶盖、空瓶;
  7. 第 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.exportsrequire 实现模块化处理。

(一)、定义名为 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条评论

  • yeti

    彬哥,我在用vue重写大事件项目的时候发现头像图片以base64发送过去之后重新获取用户信息时头像图片user_pic值却为空,而且在写修改nickname和email时后台返回修改信息成功但再次获取时无变化;上述问题我也通过postman进行了测试,结果还是一样,我想知道是彬哥的接口出现了小问题麻?
    使用的接口是:http://www.liulongbin.top:3007

  • 汤姆布利伯的沙雕之旅

    // 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元

留言

您的电子邮箱地址不会被公开。 必填项已用*标注