2014新浪校招筆試題:取水果

題目

有任意種水果, 每種水果個數也是任意的, 兩人輪流從中取出水果, 規則以下:
1) 每一次應取走至少一個水果; 每一次只能取走一種水果的一個或者所有.
2) 若是誰取到最後一個水果就勝.java

給定水果種類 N 和每種水果的個數 M1, M2, …, Mn, 算出誰取勝.git

2、解題

看到這個題目的時候,我整我的懵逼了,啥,究竟是叫我作什麼?一臉懵逼,而後再看題目,又從新看題目,才發現,題目有個隱含的條件,就是 這兩我的足夠聰明,充分利用了規則。 但是單單憑藉這一點,仍是不知道該從何下手,其實這題是 必勝策略題,能夠經過用遞推的方式找一下規律解決該題。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 種可能:

  • 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 必敗

3、編程

最後咱們經過編程解決 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;
    }
}
相關文章
相關標籤/搜索