打表技巧:有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,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。