有任意種水果, 每種水果個數也是任意的, 兩人輪流從中取出水果, 規則以下:
1) 每一次應取走至少一個水果; 每一次只能取走一種水果的一個或者所有.
2) 若是誰取到最後一個水果就勝.java給定水果種類 N 和每種水果的個數 M1, M2, …, Mn, 算出誰取勝.git
看到這個題目的時候,我整我的懵逼了,啥,究竟是叫我作什麼?一臉懵逼,而後再看題目,又從新看題目,才發現,題目有個隱含的條件,就是 這兩我的足夠聰明,充分利用了規則。 但是單單憑藉這一點,仍是不知道該從何下手,其實這題是 必勝策略題,能夠經過用遞推的方式找一下規律解決該題。github
在遞推以前,咱們先來看看題目中一共給出了什麼條件:
1.N 種不一樣的水果
2.每種水果的個數分別爲:M1, M2, …, Mn,
3.有兩我的,輪流取水果,每一次應取走至少一個水果; 每一次只能取走一種水果的一個或者所有
4.誰取到最後一個水果就勝編程
那好,根據上面的分析, 咱們先假設兩我的分別爲 A 和 B ,A 先取水果,水果的總個數爲 M ,即 M = M1 + M2 + M3 + … + Mn,ui
(1)N = 1(只有一種水果)spa
A 先拿,由於知道水果的種類,因此 A 不須要考慮水果有多少個,他只要第一次拿的時候,拿完這一種水果就能夠獲勝了。code
結論:N = 1 ,A 必勝遞歸
(2)N = 2 (有兩種水果)get
此時兩我的都不敢直接拿走一種水果, 由於那樣會送對方進入(1)的必勝局中, 本身必敗.因此 A 和 B 都只能一個一個的拿, 這樣誰拿走最後一個就由 M(水果的總個數) 的奇偶性決定。也就是說 ,M 是奇數,A 必勝,M 是偶數,B 必勝input
固然我在想這個例子的時候,不當心進入了一個誤區,假如第一種水果 3 個,第二種水果 2 個,水果總數爲奇數,知足條件,假如 A 先拿第一種水果,B 再拿一個第一種水果,A 再拿一個,而後 B 拿所有第二種,B 贏。但是 A 是足夠聰明的,A 拿了第一種水果,B 跟着拿,此時 A 確定不會接着拿第一種水果的,由於這樣本身必敗,因此他確定會選擇拿第二種水果,這樣就能必勝了。因此仍是 N = 2 的時候,水果的總數爲奇數,先拿必勝,若是水果的總數爲偶數,先拿必敗
結論:N = 2 ,M 是奇數, A 必勝; 不然 A 必敗
(3)N = 3 (有三種水果)
當水果種類大於 2 種時,不太好肯定到底誰獲勝,須要根據各類水果數量的奇偶數來判斷,所以先按水按數量的奇偶分類,有 4 種可能:
不管上面是哪一種狀況,A 均可以當即讓 B 進入與 (2) 相反的局面(必敗的局面),好比:
結論:N = 3 ,A 必勝
(4)N = 4 (有四種水果)
A 先取, 他確定不會所有取走一種, 由於會送 B 進入(3)的必勝態, A 就必敗.
所以 A 只能取一個
也就是說 M - 4 必須是奇數,這樣 A 纔會讓 B 進入最終的必敗局面,因此勝負由 M - 4 的奇偶性決定, 也就是說勝負由 M 的奇偶性決定
結論:N = 4 ,M 是奇數, A 必勝; 不然 A 必敗
經過上面的遞推,咱們基本能夠看到規律了:
最後咱們經過編程解決 GitHub 地址:https://github.com/TwoWater/Interview/blob/master/Interview/src/com/liangdianshui/TakeTheFruit.java
package com.liangdianshui; import java.util.Scanner; /** * <p> * 有任意種水果,每種水果個數也是任意的,兩人輪流從中取出水果,規則以下: * 1)每一次應取走至少一個水果;每一次只能取走一種水果的一個或者所有 * 2)若是誰取到最後一個水果就勝 * 給定水果種類N和每種水果的個數M1,M2,…Mn,算出誰取勝。 * (題目的隱含條件是兩我的足夠聰明,聰明到爲了取勝儘量利用規則) * </p> * * @author liangdianshui * */ public class TakeTheFruit { private static final String EXIT = "q"; public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); String input; int[] fruitNums; do { System.out.println("假設 A 和 B 兩我的,A 先取水果"); System.out.println("請輸入每種水果的個數(空格或回車分隔):"); System.out.println("輸入 Q 或 q 退出"); if (EXIT.equalsIgnoreCase(input = scanner.nextLine())) { System.out.println("Exit"); break; } input = input.trim(); if (input.length() != 0) { fruitNums = initFruitNums(input); boolean isWin = takeTheFruitGame(fruitNums, fruitNums.length); if(isWin){ System.out.println("A 贏"); }else{ System.out.println("B 贏"); } System.out.println("--------------------------------------------"); } } while (true); } /** * 初始化每種水果的個數 * * @param input * @return */ private static int[] initFruitNums(String input) { String[] nums = input.split("\\s+"); int[] fruitNums = new int[nums.length]; int num; for (int i = 0; i < nums.length; i++) { num = Integer.parseInt(nums[i]); if (num <= 0) { throw new IllegalArgumentException("水果數量不能爲 0 或負數:" + num); } fruitNums[i] = num; } return fruitNums; } /** * 遞歸法 * * @param fruitNums * @param numOfTypes * @return */ private static boolean takeTheFruitGame(int[] fruitNums, int numOfTypes) { //當水果種類爲1的時候,必勝 if (numOfTypes == 1) { return true; } // 當水果種類爲2的時候 if (numOfTypes == 2) { return sumOfTwoFruitNums(fruitNums) % 2 == 1; } // 當水果種類大於等於3的時候 int num; for (int i = 0; i < fruitNums.length; i++) { num = fruitNums[i]; if (num == 0) continue; fruitNums[i] = 0; if (!takeTheFruitGame(fruitNums, numOfTypes - 1)) { fruitNums[i] = num; return true; } if (num > 1) { fruitNums[i] = num - 1; if (!takeTheFruitGame(fruitNums, numOfTypes)) { fruitNums[i] = num; return true; } } fruitNums[i] = num; } return false; } /** * <p> * 經過結論直接輸出結果 * N 爲奇數,A 必勝 * N 爲偶數,若是 M 爲奇數,A 必勝;若是 M 爲偶數,A 必敗 * </p> * @param fruitNums * @return */ private static boolean takeTheFruitGame2(int[] fruitNums) { if (fruitNums.length % 2 == 1) { return true; } return sumOfFruitNums(fruitNums) % 2 == 1; } private static int sumOfTwoFruitNums(int[] fruitNums) { int num1 = 0; int num2 = 0; for (int num : fruitNums) { if (num > 0) { if (num1 == 0) { num1 = num; } else { num2 = num; break; } } } return num1 + num2; } private static int sumOfFruitNums(int[] fruitNums) { int sum = 0; for (int num : fruitNums) { sum += num; } return sum; } }