一個算法筆試題引發的思考---喝汽水問題


這是一道經典的喝汽水問題,根據問題的表述,有多種不同的場景,但是問題考察點都是一樣的。
一、 問題引入
一瓶汽水單價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
通過以上分析,可考慮更高級應用,比如最優路徑問題,最短時間問題等等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM