這是一道經典的喝汽水問題,根據問題的表述,有多種不同的場景,但是問題考察點都是一樣的。
一、 問題引入
一瓶汽水單價2元,4個瓶蓋可換一個汽水,2個空瓶可換一個汽水。給定金額得出一共能喝幾瓶汽水?
二、 問題分析
1,金額是一次性的,全部買完汽水后就不能再買了,后續金額也不會添加
2,有兩條規則,“4個瓶蓋換汽水”和“2個空瓶換汽水”,這兩條規則沒有先后順序。
3,對給定的金額沒有具體限制,只需大於0就可以,這里考慮整數。
4,不考慮經濟學思路,單純數學角度(經濟學思路是,比如4塊錢,買2瓶汽水,然后用2個空瓶換一瓶汽水,這時喝了3瓶,再向老板借一個空瓶,加上剩下的一個空瓶換一瓶汽水,剩下空瓶還給老板,總共喝了4瓶)
三、 過程分析
1,拿到金額,算出金額可以買多少瓶汽水,同時可得到空瓶數量和瓶蓋數量。
2,拿到空瓶數量,算出所有空瓶可換多少瓶汽水,這時剩余的空瓶數量是沒有換的空瓶數量和換了汽水的數量,瓶蓋數量為換前瓶蓋數量加上換了汽水的數量,總共喝的汽水數量為上一步累加喝的數量加上這次換的汽水數量。
3,拿到瓶蓋數量,算出所有瓶蓋可換多少瓶汽水,這時剩余的瓶蓋數量是沒有換的瓶蓋數量和換了汽水的數量,空瓶數量為換前空瓶數量加上換了汽水的數量,總共喝的汽水數量為上一步累加喝的數量加上這次換的汽水數量。
4,當空瓶數量或瓶蓋數量滿足換汽水條件時,分別執行2或3步驟,都不滿足時,過程結束返回總共喝的汽水數量。
簡單舉例:
金額 | 步驟 | 總共汽水數量 |
1 | 第一步,當前喝汽水數量:0 ,空瓶數量: 0,瓶蓋數量:0,已喝汽水數量: 0 | 0 |
2 | 第一步,當前喝汽水數量:1 ,空瓶數量: 1,瓶蓋數量:1,已喝汽水數量: 1 | 1 |
3 | 第一步,當前喝汽水數量:1 ,空瓶數量: 1,瓶蓋數量:1,已喝汽水數量: 1 | 1 |
4 | 第一步,當前喝汽水數量:2 ,空瓶數量: 2,瓶蓋數量:2,已喝汽水數量: 2;第二步,當前喝汽水數量:1 ,空瓶數量: 2-2+1=1,瓶蓋數量:2+1=3,已喝汽水數量: 2+1=3 | 3 |
5 | 第一步,當前喝汽水數量:2 ,空瓶數量: 2,瓶蓋數量:2,已喝汽水數量: 2;第二步,當前喝汽水數量:1 ,空瓶數量: 2-2+1=1,瓶蓋數量:2+1=3,已喝汽水數量: 2+1=3 | 3 |
6 | 第一步,當前喝汽水數量:3 ,空瓶數量: 3,瓶蓋數量:3,已喝汽水數量: 3第二步(換空瓶),當前喝汽水數量:1 ,空瓶數量: 3-2+1=2,瓶蓋數量:3+1=4,已喝汽水數量: 3+1=4第三步(換瓶蓋),當前喝汽水數量:1 ,空瓶數量: 2+1=3,瓶蓋數量:4-4+1=1,已喝汽水數量: 4+1=5第四步(換空瓶),當前喝汽水數量:1 ,空瓶數量: 3-2+1=2,瓶蓋數量:1+1=2,已喝汽水數量: 5+1=6第五步(換空瓶),當前喝汽水數量:1 ,空瓶數量: 2-2+1=1,瓶蓋數量:2+1=3,已喝汽水數量: 6+1=7 | 7 |
... | ... | ... |
對金額為6的步驟
步驟 | 當前喝汽水數量 | 空瓶數量 | 瓶蓋數量 | 總共喝汽水數量 |
1 | 3 | 3 | 3 | 3 |
2換空瓶 | 1 | 3-2+1=2 | 3+1=4 | 3+1=4 |
3換瓶蓋 | 1 | 2+1=3 | 4-4+1=1 | 4+1=5 |
4換空瓶 | 1 | 3-2+1=2 | 1+1=2 | 5+1=6 |
5換空瓶 | 1 | 2-2+1=1 | 2+1=3 | 6+1=7 |
四、 抽象
針對以上步驟,按照抽象化思想,將過程如下表示:

這里抽象一個籃子概念,也可以理解成抽屜,里面存放空瓶數、瓶蓋數、已喝數,首先要對籃子初始化,通過金額初始空瓶數、瓶蓋數和已喝數,然后對籃子依次調用換空瓶和換瓶蓋操作,對籃子里數量進行消耗。
五、 實現
通過以上分析,完成代碼實現,代碼git地址:https://github.com/zengfa1988/study/blob/master/src/main/java/com/tsh/exam/bottle/BottleReplaceTest.java
public static Integer getBottleDrink(Integer money){ Integer drinkNum = money / 2;//已喝數量 Integer emptyNum = drinkNum;//空瓶數量 Integer capNum = drinkNum;//瓶蓋數量 Map<String,Integer> pocket = new HashMap<String,Integer>(); pocket.put("drinkNum", drinkNum); pocket.put("emptyNum", emptyNum); pocket.put("capNum", capNum); while(emptyNum >=2 || capNum >=4){ //調用空瓶替換方法消耗空瓶 emptyBottleReplace(pocket); //調用瓶蓋替換方法消耗瓶蓋 capReplace(pocket); emptyNum = pocket.get("emptyNum"); capNum = pocket.get("capNum"); } drinkNum = pocket.get("drinkNum"); return drinkNum; }
/** * 空瓶替換汽水 * @param pocket */ public static void emptyBottleReplace(Map<String,Integer> pocket){ Integer emptyNum = pocket.get("emptyNum"); if(emptyNum < 2){ return; } Integer curDrinkNum = emptyNum / 2;//當前空瓶可替換汽水數量 emptyNum = curDrinkNum + emptyNum % 2; pocket.put("emptyNum", emptyNum); //已喝數量累加 Integer drinkNum = pocket.get("drinkNum"); drinkNum = drinkNum + curDrinkNum; pocket.put("drinkNum", drinkNum); //瓶蓋累加 Integer capNum = pocket.get("capNum"); capNum = capNum + curDrinkNum; pocket.put("capNum", capNum); }
/** * 瓶蓋替換汽水 * @param pocket */ public static void capReplace(Map<String,Integer> pocket){ Integer capNum = pocket.get("capNum"); if(capNum < 4){ return; } Integer curDrinkNum = capNum / 4;//當前空瓶可替換汽水數量 capNum = curDrinkNum + capNum % 4; pocket.put("capNum", capNum); //已喝數量累加 Integer drinkNum = pocket.get("drinkNum"); drinkNum = drinkNum + curDrinkNum; pocket.put("drinkNum", drinkNum); //瓶蓋累加 Integer emptyNum = pocket.get("emptyNum"); emptyNum = emptyNum + curDrinkNum; pocket.put("emptyNum", emptyNum); }
main方法調用:
public static void main(String[] args) { Integer drinkNum = getBottleDrink(10); System.out.println(drinkNum); }
五、 思考
1,在實際的項目中,規則可能會根據實際情況隨時變動,考慮到靈活性,可以使用規則引擎,具體使用可參考規則引擎部分。
2,在上面的兩條規則,消耗的屬性是獨立的,可加條規則消耗的屬性重疊,比如1個空瓶和3個瓶蓋換一瓶汽水,可添加如下方法:
/** * 1個空瓶和3個瓶蓋換一瓶汽水 * @param pocket */ public static void capAndEmptyReplace(Map<String,Integer> pocket){ Integer capNum = pocket.get("capNum"); Integer emptyNum = pocket.get("emptyNum"); if(emptyNum<1 || capNum<2){ return; } Integer curDrinkNum = capNum / 3;//當前空瓶可替換汽水數量 if(curDrinkNum > emptyNum){//如果瓶蓋算的汽水數量大於空瓶數量,說明沒有足夠的空瓶同所有瓶蓋消耗,則本次消耗為空瓶數量 curDrinkNum = emptyNum; } capNum = capNum - curDrinkNum * 3;//剩余瓶蓋數量 pocket.put("emptyNum", emptyNum - curDrinkNum + curDrinkNum); pocket.put("capNum", capNum + curDrinkNum); //已喝數量累加 Integer drinkNum = pocket.get("drinkNum"); drinkNum = drinkNum + curDrinkNum; pocket.put("drinkNum", drinkNum); }
修改getBottleDrink方法:在capReplace后面添加capAndEmptyReplace方法
public static Integer getBottleDrink(Integer money){ Integer drinkNum = money / 2;//已喝數量 Integer emptyNum = drinkNum;//空瓶數量 Integer capNum = drinkNum;//瓶蓋數量 Map<String,Integer> pocket = new HashMap<String,Integer>(); pocket.put("drinkNum", drinkNum); pocket.put("emptyNum", emptyNum); pocket.put("capNum", capNum); while(emptyNum >=2 || capNum >=4 || (emptyNum >=1 && capNum>=3)){ //調用空瓶替換方法消耗空瓶 emptyBottleReplace(pocket); //調用瓶蓋替換方法消耗瓶蓋 capReplace(pocket); //1個空瓶和3個瓶蓋換一瓶汽水 capAndEmptyReplace(pocket); emptyNum = pocket.get("emptyNum"); capNum = pocket.get("capNum"); } drinkNum = pocket.get("drinkNum"); return drinkNum; }
但是這里有些問題,capAndEmptyReplace放在emptyBottleReplace前和后,跟capAndEmptyReplace放在capReplace后結果都不一樣,也就是說
三條規則順序執行結果不一樣。
對整個思路再進行優化。

對某一個有着優先級的規則序列,先獲得優先級高的規則方法,再調用獲得的規則。
這里就不上代碼了,直接看鏈接https://github.com/zengfa1988/study/blob/master/src/main/java/com/tsh/exam/bottle/MutilRuleBottleReplaceTest.java
通過以上分析,可考慮更高級應用,比如最優路徑問題,最短時間問題等等。