2014新浪校招筆試題:取水果(17年第一篇讓人懵逼的面試題)
前言
2017 年,我還是會堅持每週一篇面試題,當然我每週看的面試題肯定是不止一篇的,我是在這周看過的面試題中,選擇一題自己認為較好的來寫。因為每一篇都寫,不現實,寫一篇部落格,需要的時間也是挺長的,所以選擇較好較大眾化的題目。
一、題目
有任意種水果, 每種水果個數也是任意的, 兩人輪流從中取出水果, 規則如下:
1) 每一次應取走至少一個水果; 每一次只能取走一種水果的一個或者全部.
2) 如果誰取到最後一個水果就勝.給定水果種類 N 和每種水果的個數 M1, M2, …, Mn, 算出誰取勝.
二、解題
看到這個題目的時候,我整個人懵逼了,啥,到底是叫我做什麼?一臉懵逼,然後再看題目,又重新看題目,才發現,題目有個隱含的條件,就是 這兩個人足夠聰明,充分利用了規則。
在遞推之前,我們先來看看題目中一共給出了什麼條件:
1.N 種不同的水果
2.每種水果的個數分別為:M1, M2, …, Mn,
3.有兩個人,輪流取水果,每一次應取走至少一個水果; 每一次只能取走一種水果的一個或者全部
4.誰取到最後一個水果就勝
那好,根據上面的分析, 我們先假設兩個人分別為 A 和 B ,A 先取水果,水果的總個數為 M ,即 M = M1 + M2 + M3 + … + Mn,
(1)N = 1(只有一種水果)
A 先拿,因為知道水果的種類,所以 A 不需要考慮水果有多少個,他只要第一次拿的時候,拿完這一種水果就可以獲勝了。
結論:N = 1 ,A 必勝
(2)N = 2 (有兩種水果)
此時兩個人都不敢直接拿走一種水果, 因為那樣會送對方進入(1)的必勝局中, 自己必敗.所以 A 和 B 都只能一個一個的拿, 這樣誰拿走最後一個就由 M(水果的總個數) 的奇偶性決定。也就是說 ,M 是奇數,A 必勝,M 是偶數,B 必勝
當然我在想這個例子的時候,不小心進入了一個誤區,假如第一種水果 3 個,第二種水果 2 個,水果總數為奇數,滿足條件,假如 A 先拿第一種水果,B 再拿一個第一種水果,A 再拿一個,然後 B 拿全部第二種,B 贏。可是 A 是足夠聰明的,A 拿了第一種水果,B 跟著拿,此時 A 肯定不會接著拿第一種水果的,因為這樣自己必敗,所以他肯定會選擇拿第二種水果,這樣就能必勝了。所以還是 N = 2 的時候,水果的總數為奇數,先拿必勝,如果水果的總數為偶數,先拿必敗
結論:N = 2 ,M 是奇數, A 必勝; 否則 A 必敗
(3)N = 3 (有三種水果)
當水果種類大於 2 種時,不太好確定到底誰獲勝,需要根據各種水果數量的奇偶數來判斷,因此先按水按數量的奇偶分類,有 4 種可能:
- 3 種水果的個數分別都是奇數個
- 3 種水果的個數分別都是偶數個
- 其中 2 種水果的個數是奇數個,其中 1 種水果的個數是偶數個
- 其中 2 種水果的個數是偶數個,其中 1 種水果的個數是奇數個
無論上面是哪種情況,A 都可以立即讓 B 進入與 (2) 相反的局面(必敗的局面),比如:
- 3 種水果的個數分別都是奇數個: A 隨便拿掉一種水果,剩餘的水果總數為偶數(奇數 + 奇數 = 偶數),剩餘兩種水果,進入了(2)的局面,水果總數為偶數,先拿必敗,所以 B 必敗,A必勝
- 3 種水果的個數分別都是偶數個: 跟上面是一樣的,A 隨便拿掉一種水果,剩餘的水果總數為偶數(偶數 + 偶數 = 偶數),剩餘兩種水果,進入了(2)的局面,水果總數為偶數,先拿必敗,所以 B 必敗,A必勝
- 其中 2 種水果的個數是奇數個,其中 1 種水果的個數是偶數個: A 拿走偶數個的水果的全部,也會進入(2)的局面且水果總數為偶數,A 必勝
- 其中 2 種水果的個數是偶數個,其中 1 種水果的個數是奇數個: A 拿走奇數個的水果的全部,也會進入(2)的局面且水果總數為偶數,A 必勝
結論:N = 3 ,A 必勝
(4)N = 4 (有四種水果)
A 先取, 他肯定不會全部取走一種, 因為會送 B 進入(3)的必勝態, A 就必敗.
因此 A 只能取一個
- 若 A 取走一個,變成了三種水果,就是變成 (3) 了, 說明 4 種水果都只有一個(否則 A 足夠聰明,可以避免這種情況) 即 M 為偶數 4 , A 必敗
- 若 A 取完這一個還剩 4 種水果, 那 B 同上分析也只敢取一個,依次類推, 誰最後面對變成 (3) 的情況就必敗了.
也就是說 M - 4 必須是奇數,這樣 A 才會讓 B 進入最終的必敗局面,所以勝負由 M - 4 的奇偶性決定, 也就是說勝負由 M 的奇偶性決定
結論:N = 4 ,M 是奇數, A 必勝; 否則 A 必敗
通過上面的遞推,我們基本可以看到規律了:
- N 為奇數,A 必勝
- N 為偶數,如果 M 為奇數,A 必勝;如果 M 為偶數,A 必敗
三、程式設計
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;
}
}