打表技巧:有N堆草,牛先吃,羊后吃,轮流吃,谁先吃完谁赢,请问牛和羊谁会赢

打表技巧:有N堆草,牛先吃,羊后吃,轮流吃,谁先吃完谁赢,请问牛和羊谁会赢?

提示:有些题目,结果只与一维变量n有关,可以暴力解,打印一批结果,
然后观察结果可能存在的与i之间的特定规律,直接打表,用的时候查表就行,速度o(1)

打表技巧的题目还有:
【1】打表技巧:N个苹果,用6号袋和8号袋装,必须装满每个袋子,最少需要多少个袋子才能装满


题目

有N堆草,牛先吃,羊后吃,轮流吃,谁先吃完谁赢,请问牛和羊谁会赢?
每次牛羊吃草的量必须是4的0 1 2 ……inf次方
也就是每次都得吃1 4 16 64份的量

谁先吃完,让对手无草可吃,n=0了,则它赢


一、审题

示例:
n=0
牛先吃,直接面对无草可吃,牛输,羊赢

n=1
牛先吃,只能吃1份,n=0,则牛赢

n=2,
牛先吃,也只能吃1份,然后羊吃1份,羊先吃完的,羊赢了

n=3
牛1份,羊1份,牛1份,n=0,牛赢

n=4
牛4份,牛赢了,因为它可以吃4的0 1 2 等次方份

……


暴力解

咱们看到了结果只与n有关

先手为牛,后手暂时为羊

不妨设f(n)是先手来吃,吃完看看返回谁赢?
(1)如果先手进来发现n=0,则不好意思,后手赢了,后手让我首先面临n=0的无草可吃的地步,我先手输
(2)如果n还有,我先手从k=1份,4份,16份64份……4的inf次方尝试吃,这个数量k不能超过n,我先手吃下去k份之后,还剩n-k份草
(3)下次我只能做为后手去吃剩下的n-k份草了【因为下次就是我的对手即后手,做先手去吃了】,如果发现我再以后手的身份吃n-k份,返回来说后手赢了,那恰好就是我先手赢。
(4)如果(3)中我做后手吃这剩下的n-k份,一直返回先手赢,那你要明白,现在的这个赢家先手可是我的对手(后手)啊,我就不能尝试看这种方案吃k份了了,还得换更大的k尝试。
(5)如果直到k超过n都不行,那我完蛋了,我做先手吃怎么都没法取胜,后手自动赢了。

非常重要的就是理解这(3),我吃完k份,剩下n-k是我今后以后手的身份去吃的,说了半天其实就是我先手一个人吃完的,但是我模拟了先后手交替吃的流程。
在这里插入图片描述

    //复习打表
    //**不妨设f(n)是先手来吃**,吃完看看返回谁赢?
    public static String f(int n){
        //(1)如果先手进来发现**n=0**,则不好意思,**后手赢了**,
        // 后手让我首先面临n=0的无草可吃的地步,我先手输
        if (n == 0) return "后手";
        //(2)如果n还有,我先手从**k**=1份,4份,16份64份……4的inf次方尝试吃,这个数量k不能超过n,
        // 我先手吃下去k份之后,还剩n-k份草
        int k = 1;//最开始4的0次方,后面每次尝试都×4倍增
        int rest = n - k;
        while (k <= n){//吃的数量不越界
            //(3)下次我只能做为**后手**去吃剩下的n-k份草了【因为下次就是我的对手即后手,做先手去吃了】,
            // 如果发现我**再以后手的身份吃n-k份,返回来说后手赢了**,那恰好就是我先手赢。
            if (f(rest) == "后手") return "先手";

            //(4)如果(3)中我做后手吃这剩下的n-k份,一直返回先手赢,那你要明白,
            // 现在的这个赢家先手可是我的对手(后手)啊,我就不能尝试看这种方案吃k份了了,还得换更大的k尝试。
            //当然要注意,你不能一直让k*4不断乘下去
            if (k > n / 4) break;//如果k*4>n,溢出就麻烦了,所以先让k与n/4判断,再给k×4
            k *= 4;
            rest = n - k;//尝试新的吃法,还剩下这么多
        }

        //(5)如果直到k超过n都不行,那我完蛋了,我做先手吃怎么都没法取胜,后手自动赢了。
        return "后手";
    }


    public static void test(){
        //这种暴力递归,n变大之后直接就没法跑了,实在是太慢了,爆炸啊!!!!!!!!!!!!!!
//        int n = 7;
//        System.out.println(winner1(n));
//        System.out.println(f(n));
        // 1, 1, 4
        //1,4,1
        //4,1,1,确实是先手赢
        for (int i = 0; i < 30; i++) {
            System.out.println(i +":" +winner1(i));
        }
        System.out.println();
        for (int i = 0; i < 30; i++) {
            System.out.println(i +":" +f(i));
        }
    }

代码中:

 if (k > n / 4) break;//如果k*4>n,溢出就麻烦了,所以先让k与n/4判断,再给k×4

目的是控制k不断暴涨,别啥时候一下子k×4溢出了……
在这里插入图片描述

暴力打印30个出来看看

0:后手
1:先手
2:后手
3:先手
4:先手
5:后手
6:先手
7:后手
8:先手
9:先手
10:后手
11:先手
12:后手
13:先手
14:先手
15:后手
16:先手
17:后手
18:先手
19:先手
20:后手
21:先手
22:后手
23:先手
24:先手
25:后手
26:先手
27:后手
28:先手
29:先手

0:后手
1:先手
2:后手
3:先手
4:先手
5:后手
6:先手
7:后手
8:先手
9:先手
10:后手
11:先手
12:后手
13:先手
14:先手
15:后手
16:先手
17:后手
18:先手
19:先手
20:后手
21:先手
22:后手
23:先手
24:先手
25:后手
26:先手
27:后手
28:先手
29:先手

观察是否可以打表?o(1)速度建表

完全是很强的规律
你可以看看,从0–4,5–9
每5个一批:
后手,先手,后手,先手,先手

全是重复的
也就是能n%5=0或者2时,是后手赢,其余的统统是先手赢

手撕代码即可:

    //复习打表
    public static String fightTableWinner(int n){
        return n % 5 == 0 || n % 5 == 2 ? "后手":"先手";
    }

    public static void test2(){
        for (int i = 0; i < 30; i++) {
            System.out.println(winner3(i));
        }
        System.out.println();
        for (int i = 0; i < 30; i++) {
            System.out.println(fightTableWinner(i));
        }

    }

    public static void main(String[] args) {
//        test();
        test2();
    }

速度超级快

0:后手
1:先手
2:后手
3:先手
4:先手
5:后手
6:先手
7:后手
8:先手
9:先手
10:后手
11:先手
12:后手
13:先手
14:先手
15:后手
16:先手
17:后手
18:先手
19:先手
20:后手
21:先手
22:后手
23:先手
24:先手
25:后手
26:先手
27:后手
28:先手
29:先手

0:后手
1:先手
2:后手
3:先手
4:先手
5:后手
6:先手
7:后手
8:先手
9:先手
10:后手
11:先手
12:后手
13:先手
14:先手
15:后手
16:先手
17:后手
18:先手
19:先手
20:后手
21:先手
22:后手
23:先手
24:先手
25:后手
26:先手
27:后手
28:先手
29:先手

有没有发现,打表超级牛逼!


总结

提示:重要经验:

1)打表技巧很重要,如果问题的解只与一维变量n有关,则考虑暴力打印一批解,观察解的规律,有和n相关的规律的话,完全可以采用打表技巧
2)往往都是成批次与n有一定的递推关系,这很难,但是尽量想办法推出公式来,能大大加快算法的速度,o(1)复杂度,岂不美滋滋。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值