酒瓶与瓶盖换酒问题 - 10块钱可以喝多少瓶酒

酒瓶与瓶盖换酒问题 - 10块钱可以喝多少瓶酒

前些日子有QQ好友发给我下面这个问题:

啤酒2块钱1瓶,4个盖换一瓶,2个空瓶换一瓶,问10块钱可以喝多少瓶。

当时没有时间算这个问题(其实就是懒得动笔和动脑子),但这几天又老想着这个问题,所以今天决定写段代码解决一下。

一开始,由于没把题目琢磨太明白,于是用了具有暴力色彩的递归方式去算这道题,考虑每一种交换顺序,Java代码如下:

import java.util.Stack;

public class Solution {

int countOfDrinked;

Stack stackStep = new Stack();

String logOutputMax = "";

int priceEachBottle = 2; //一瓶酒多少钱

int bottleCanChange = 2; //多少瓶子能换一瓶酒

int capCanChange = 4; //多少瓶盖能换一瓶酒

public int countOfDrink(int money) {

countOfDrinked = 0;

stackStep.clear();

logOutputMax = "";

calcMax(money, 0, 0, 0);

System.out.println(logOutputMax);

return countOfDrinked;

}

private void calcMax(int money, int bottle, int cap, int drinked) {

if (money >= priceEachBottle) {

String log = "";

log += "动作:买入啤酒, ";

log += "剩余金钱: " + (money - priceEachBottle) + ", ";

log += "剩余酒瓶:" + (bottle + 1) + ", ";

log += "剩余瓶盖:" + (cap + 1) + ", ";

log += "已喝啤酒:" + (drinked + 1);

stackStep.push(log);

calcMax(money - priceEachBottle, bottle + 1, cap + 1, drinked + 1);

stackStep.pop();

}

if (bottle >= bottleCanChange) {

String log = "";

log += "动作:酒瓶换酒, ";

log += "剩余金钱: " + (money) + ", ";

log += "剩余酒瓶:" + (bottle - bottleCanChange + 1) + ", ";

log += "剩余瓶盖:" + (cap + 1) + ", ";

log += "已喝啤酒:" + (drinked + 1);

stackStep.push(log);

calcMax(money, bottle - bottleCanChange + 1, cap + 1, drinked + 1);

stackStep.pop();

}

if (cap >= capCanChange) {

String log = "";

log += "动作:瓶盖换酒, ";

log += "剩余金钱: " + (money) + ", ";

log += "剩余酒瓶:" + (bottle + 1) + ", ";

log += "剩余瓶盖:" + (cap - capCanChange + 1) + ", ";

log += "已喝啤酒:" + (drinked + 1);

stackStep.push(log);

calcMax(money, bottle + 1, cap - capCanChange + 1, drinked + 1);

stackStep.pop();

}

if (money < 2 && bottle < 2 && cap < 4) {

if (drinked > countOfDrinked) {

countOfDrinked = drinked;

logOutputMax = String.join(";\n", stackStep);

}

}

}

}

创建一个Main函数,代码如下:

class DrinkingProblem {

public static void main(String[] args) {

Solution solution = new Solution();

System.out.println(solution.countOfDrink(10));

}

}

输出结果如下:

动作:买入啤酒, 剩余金钱: 8, 剩余酒瓶:1, 剩余瓶盖:1, 已喝啤酒:1;

动作:买入啤酒, 剩余金钱: 6, 剩余酒瓶:2, 剩余瓶盖:2, 已喝啤酒:2;

动作:买入啤酒, 剩余金钱: 4, 剩余酒瓶:3, 剩余瓶盖:3, 已喝啤酒:3;

动作:买入啤酒, 剩余金钱: 2, 剩余酒瓶:4, 剩余瓶盖:4, 已喝啤酒:4;

动作:买入啤酒, 剩余金钱: 0, 剩余酒瓶:5, 剩余瓶盖:5, 已喝啤酒:5;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:4, 剩余瓶盖:6, 已喝啤酒:6;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:3, 剩余瓶盖:7, 已喝啤酒:7;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:8, 已喝啤酒:8;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:9, 已喝啤酒:9;

动作:瓶盖换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:6, 已喝啤酒:10;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:7, 已喝啤酒:11;

动作:瓶盖换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:4, 已喝啤酒:12;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:5, 已喝啤酒:13;

动作:瓶盖换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:2, 已喝啤酒:14;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:3, 已喝啤酒:15

15

通过观察输出结果并研究题目,我发现了下面几条规律:

1、两块钱可以转换成一瓶酒(含一个瓶和一个盖),这个过程是不可逆的,所以可以先把钱优先换成酒

2、两个酒瓶可以转换为一个酒瓶和一个瓶盖,四个瓶盖可以转换为一个酒瓶和一个瓶盖,酒瓶和瓶盖虽然存在着转换关系,但这种转换关系也是不可逆的,酒瓶和瓶盖都只会越换越少,并且无论交换顺序如何改变,从一个状态转到另一个状态所需花费的步骤数是一样的。

这就给上面方法的改进提供了空间,于是我新写了一个代码,发现代码运行的结果是一样的:

import java.util.LinkedList;

public class Solution {

LinkedList linkedList = new LinkedList();

int priceEachBottle = 2; //一瓶酒多少钱

int bottleCanChange = 2; //多少瓶子能换一瓶酒

int capCanChange = 4; //多少瓶盖能换一瓶酒

public int countOfDrink(int money) {

linkedList.clear();

calcMax(money, 0, 0, 0);

System.out.println(String.join(";\n", linkedList));

return linkedList.size();

}

private void calcMax(int money, int bottle, int cap, int drinked) {

while (money >= priceEachBottle) {

String log = "";

log += "动作:买入啤酒, ";

log += "剩余金钱: " + (money - priceEachBottle) + ", ";

log += "剩余酒瓶:" + (bottle + 1) + ", ";

log += "剩余瓶盖:" + (cap + 1) + ", ";

log += "已喝啤酒:" + (drinked + 1);

linkedList.addLast(log);

money -= priceEachBottle;

bottle++;

cap++;

drinked++;

}

while (true) {

if (bottle >= bottleCanChange) {

String log = "";

log += "动作:酒瓶换酒, ";

log += "剩余金钱: " + (money) + ", ";

log += "剩余酒瓶:" + (bottle - bottleCanChange + 1) + ", ";

log += "剩余瓶盖:" + (cap + 1) + ", ";

log += "已喝啤酒:" + (drinked + 1);

linkedList.addLast(log);

bottle = bottle - bottleCanChange + 1;

cap++;

drinked++;

} else if (cap >= capCanChange) {

String log = "";

log += "动作:瓶盖换酒, ";

log += "剩余金钱: " + (money) + ", ";

log += "剩余酒瓶:" + (bottle + 1) + ", ";

log += "剩余瓶盖:" + (cap - capCanChange + 1) + ", ";

log += "已喝啤酒:" + (drinked + 1);

linkedList.addLast(log);

bottle++;

cap = cap - capCanChange + 1;

drinked++;

} else {

break;

}

}

}

}

使用同一个Main函数运行即可,运行结果如下:

动作:买入啤酒, 剩余金钱: 8, 剩余酒瓶:1, 剩余瓶盖:1, 已喝啤酒:1;

动作:买入啤酒, 剩余金钱: 6, 剩余酒瓶:2, 剩余瓶盖:2, 已喝啤酒:2;

动作:买入啤酒, 剩余金钱: 4, 剩余酒瓶:3, 剩余瓶盖:3, 已喝啤酒:3;

动作:买入啤酒, 剩余金钱: 2, 剩余酒瓶:4, 剩余瓶盖:4, 已喝啤酒:4;

动作:买入啤酒, 剩余金钱: 0, 剩余酒瓶:5, 剩余瓶盖:5, 已喝啤酒:5;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:4, 剩余瓶盖:6, 已喝啤酒:6;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:3, 剩余瓶盖:7, 已喝啤酒:7;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:8, 已喝啤酒:8;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:9, 已喝啤酒:9;

动作:瓶盖换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:6, 已喝啤酒:10;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:7, 已喝啤酒:11;

动作:瓶盖换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:4, 已喝啤酒:12;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:5, 已喝啤酒:13;

动作:瓶盖换酒, 剩余金钱: 0, 剩余酒瓶:2, 剩余瓶盖:2, 已喝啤酒:14;

动作:酒瓶换酒, 剩余金钱: 0, 剩余酒瓶:1, 剩余瓶盖:3, 已喝啤酒:15

15

可以看出,其实两个方法的输出结果是一样的,但第二种方法和第一种比,省去了很多不必要的计算步骤。

如果不需要显示步骤,那么稍微归纳一下几次不同输入的结果(当然你也可以选择自己证明一下),可以总结出一个公式:

设你有n块钱,n>=4时,你可以喝的啤酒数为:2n-5(n为偶数时),2n-7(n为奇数时)

使用这个公式,应该是解决本题最快的方式了。

END

Copyright © 2088 世界杯欧洲预选赛_南非世界杯主题曲舞蹈 - lyzkxt.com All Rights Reserved.
友情链接