使用打表法找规律
作者:Grey
原文地址:
打表法的使用条件
打表法适合:输入简单,输出也简单(只有一个数),可以暴力把一部分结果打印出来找规律,看下能否找到一个公式来优化代码。
买苹果问题
题目描述见:牛客:买苹果
暴力解法思路
-
如果是奇数,直接返回 -1,因为 6 和 8 不可能组成奇数
-
假设有 n 个苹果,最多需要
n/8
个 8 号袋,假设n/8=m
,如果m==0
,则最少需要m
个 8 号袋子即可,如果m!=0
,则看剩下的苹果能否被 被 6 号袋子消化,如果不能消化,则则减少一个 8 号袋子(m--
),继续看剩下能否被 6 号袋搞定。
暴力解法的代码如下
public static int minBags(int n) {
if ((n & 1) == 1) {
return -1;
}
if (n % 8 == 0) {
// 全部可以被 8 号袋子分解
return n / 8;
}
int use8 = n / 8;
int rest = n % 8;
while (rest != 0) {
if (rest % 6 == 0) {
// 分配了 8 号袋子,剩下的分配 6 号袋子,正好分配完。
return use8 + (rest / 6);
} else {
// 分配了 8 号袋子,剩下的分配 6 号袋子,无法分配完,则减少一个 8 号袋子
if (use8 > 0) {
use8--;
rest += 8;
} else {
return -1;
}
}
}
return -1;
}
打印出前几项的结果观察一下
当有0个苹果的时候,最少需要:0个袋子
当有1个苹果的时候,最少需要:-1个袋子
当有2个苹果的时候,最少需要:-1个袋子
当有3个苹果的时候,最少需要:-1个袋子
当有4个苹果的时候,最少需要:-1个袋子
当有5个苹果的时候,最少需要:-1个袋子
当有6个苹果的时候,最少需要:1个袋子
当有7个苹果的时候,最少需要:-1个袋子
当有8个苹果的时候,最少需要:1个袋子
当有9个苹果的时候,最少需要:-1个袋子
当有10个苹果的时候,最少需要:-1个袋子
当有11个苹果的时候,最少需要:-1个袋子
当有12个苹果的时候,最少需要:2个袋子
当有13个苹果的时候,最少需要:-1个袋子
当有14个苹果的时候,最少需要:2个袋子
当有15个苹果的时候,最少需要:-1个袋子
当有16个苹果的时候,最少需要:2个袋子
当有17个苹果的时候,最少需要:-1个袋子
当有18个苹果的时候,最少需要:3个袋子
当有19个苹果的时候,最少需要:-1个袋子
当有20个苹果的时候,最少需要:3个袋子
当有21个苹果的时候,最少需要:-1个袋子
当有22个苹果的时候,最少需要:3个袋子
当有23个苹果的时候,最少需要:-1个袋子
当有24个苹果的时候,最少需要:3个袋子
当有25个苹果的时候,最少需要:-1个袋子
当有26个苹果的时候,最少需要:4个袋子
当有27个苹果的时候,最少需要:-1个袋子
当有28个苹果的时候,最少需要:4个袋子
当有29个苹果的时候,最少需要:-1个袋子
当有30个苹果的时候,最少需要:4个袋子
当有31个苹果的时候,最少需要:-1个袋子
当有32个苹果的时候,最少需要:4个袋子
当有33个苹果的时候,最少需要:-1个袋子
当有34个苹果的时候,最少需要:5个袋子
当有35个苹果的时候,最少需要:-1个袋子
当有36个苹果的时候,最少需要:5个袋子
当有37个苹果的时候,最少需要:-1个袋子
当有38个苹果的时候,最少需要:5个袋子
当有39个苹果的时候,最少需要:-1个袋子
...
通过观察,可以得到如下结论
奇数个苹果,返回 -1。
n <= 5
或者n==10
的时候,返回 -1。
n == 6
或者n == 8
的时候,返回 1。
n % 8 == 0
时候,全部用 8 号袋子,即n / 8
; 当n%8!=0
时,需要n/8+1
个袋子。
代码可以优化为
// 打表方式优化
public static int minBags2(int n) {
if (n <= 5 || n == 10 || (n & 1) == 1) {
return -1;
}
if (n == 6 || n == 8) {
return 1;
}
return n % 8 == 0 ? n / 8 : n / 8 + 1;
}
牛羊吃草问题
暴力解法思路
在 n <= 4
的情况下,我们可以通过观察得到牛羊的胜败情况
// 0 羊胜
// 1 牛胜
// 2 羊胜
// 3 牛胜
// 4 牛胜
if (n < 5) { // base case
return (n == 0 || n == 2) ? "yang" : "niu";
}
当n >= 5
的情况下,我们可以暴力枚举所有情况,
当牛选择吃 1 份的时候,接下来的子过程,是谁赢,如果子过程是牛赢,说明主过程是羊获胜了。
当牛选择吃 4 份的时候,接下来的子过程,是谁赢,如果子过程是牛赢,说明主过程是羊获胜了。
…
当牛选择吃 4^x 份的时候,接下来的子过程,是谁赢,如果子过程是牛赢,说明主过程是羊获胜了。 (注:4^x <= n
)。
暴力解法的代码如下:
public static String winner(int n) {
// 0 羊
// 1 牛
// 2 羊
// 3 牛
// 4 牛
if (n < 5) { // base case
return (n == 0 || n == 2) ? "yang" : "niu";
}
// n >= 5 时
int base = 1; // 当前先手(牛)决定吃的草数
// 当前是先手(牛)在选
while (base <= n) {
// 当前一共n份草,先手(牛)吃掉的是base份,n - base 是留给后手(羊)的草
if (winner(n - base).equals("yang")) {
return "niu";
}
if (base > (n >> 2)) { // 防止base*4之后溢出
break;
}
base <<= 2;
}
return "yang";
}
打印出前面的一些输出
0 堆草,获胜者是:yang
1 堆草,获胜者是:niu
2 堆草,获胜者是:yang
3 堆草,获胜者是:niu
4 堆草,获胜者是:niu
5 堆草,获胜者是:yang
6 堆草,获胜者是:niu
7 堆草,获胜者是:yang
8 堆草,获胜者是:niu
9 堆草,获胜者是:niu
10 堆草,获胜者是:yang
11 堆草,获胜者是:niu
12 堆草,获胜者是:yang
13 堆草,获胜者是:niu
14 堆草,获胜者是:niu
15 堆草,获胜者是:yang
16 堆草,获胜者是:niu
17 堆草,获胜者是:yang
18 堆草,获胜者是:niu
19 堆草,获胜者是:niu
20 堆草,获胜者是:yang
21 堆草,获胜者是:niu
22 堆草,获胜者是:yang
23 堆草,获胜者是:niu
24 堆草,获胜者是:niu
通过观察可知,可以得到结论
n % 5 == 0 || n % 5 == 2
情况下,获胜者都是羊,否则获胜者是牛。
优化后的代码如下:
public static String winner2(int n) {
if (n % 5 == 0 || n % 5 == 2) {
return "yang";
} else {
return "niu";
}
}
判断一个数是否可以表示成若干(数量>1)连续正数和的数
题目描述如下
定义一种数:可以表示成若干(数量 > 1)连续正数和的数
比如:
5 = 2 + 3,5 就是这样的数
12 = 3 + 4 + 5,12 就是这样的数
1不是这样的数,因为要求数量大于1个、连续正数和
2 = 1 + 1,2 也不是,因为等号右边不是连续正数
给定一个参数 N,返回是不是可以表示成若干连续正数和的数
按照题目的描述,我们可以把暴力解法写出来,就是枚举 num 是否 可以被
1 + 2 + ... + n
2 + 3 + ... + n
3 + 4 + ... + n
…
上述任何一个式子分解。
暴力解法的代码如下
public static boolean isMSum1(int num) {
if (num <= 2) {
return false;
}
int sum = 1;
for (; sum < num; sum++) {
int o = sum;
int i = sum + 1;
sum += i;
if (sum > num) {
return false;
}
while (sum < num) {
i++;
sum += i;
}
if (sum == num) {
return true;
}
sum = o;
}
return false;
}
打印出前面若干项的结果
1 : false
2 : false
3 : true
4 : false
5 : true
6 : true
7 : true
8 : false
9 : true
10 : true
11 : true
12 : true
13 : true
14 : true
15 : true
16 : false
17 : true
18 : true
19 : true
20 : true
21 : true
22 : true
23 : true
24 : true
25 : true
26 : true
27 : true
28 : true
29 : true
30 : true
31 : true
32 : false
观察后发现
num == 1 || num == 2
时,为 false。
如果一个数是 2 的某次方,则返回 false,否则则返回 true。
如何判断一个数是不是 2 的某次方呢?
有如下三种方法
num == (num & (-num))
num == (num & (~num + 1))
(n & (num - 1)) == 0
所以,优化后的代码如下
public static boolean isMSum2(int num) {
if (num == 1 || num == 2) {
return false;
}
return ((num - 1) & num) != 0;
}