2014新浪校招笔试题:取水果(17年第一篇让人懵逼的面试题)

新浪面试题.jpg

前言

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 必败

三、编程

最后我们通过编程解决 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;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容