对数器是什么?
通常我们在笔试的时候或者参加编程大赛的时候,自己实现了一个算法,但是不能够判断该算法是否完全没问题,如果在比赛平台上验证,通常只会告诉你有没有错误,出了错不会告诉你哪里有问题,对于排错来说是非常坑爹的,所以对数器就横空出世了,对数器就是用一个绝对OK的方法和随机器生成的样本数据进行合体,如果你的算法是没问题的,那么和对数器的这个百分之百正确的方法一个元素一个元素的比较,也一定是equals的。如果返回false,说明你的算法有问题。
对数器的概念
1.有一个你想要测的方法a;
2.实现一个绝对正确但是复杂度不好的方法b;
3.实现一个随机样本产生器;
4.实现对比算法a和b的方法;
5.把方法a和方法b比对多次来验证方法a是否正确;
6.如果有一个样本使得比对出错,打印样本分析是哪个方法出错;
7.当样本数量很多时比对测试依然正确,可以确定方法a已经正确
对数器打表找规律的使用场景:
输入参数是简单类型,返回值也是简单类型
对数器打表找规律的过程:
1.可以用最暴力的实现求入参不大情况下的答案
2.打印入参不大情况下的答案,然后观察规律
3.把规律变成代码,就是最优解
对数器的例题
使用规格8和规格6的袋子买苹果问题
一家超市只有两种袋子,一种是最多装8个苹果的袋子和最多装6个苹果的袋子,如果你现在去买苹果,如果装不满袋子,就选择不买这些苹果,那么当买n个苹果,最少用几个袋子?
实例:
买7个苹果,一个6规格的袋子装不下,两个6规格的袋子装不满,那么就返回 -1
买8个苹果,一个8规格的袋子就可以装下,那么返回1
那么我们就可以用递归的方法建立一个对数器
如下:
# include <stdio.h>
int min(int a, int b)
{
if (a > b)
return b;
else
return a;
}
int apple(int x)
{
if (x < 0)
return 999; //表示袋子装不满
else if (x == 0)
return 0; // 表示刚刚好
int p1 = apple(x - 8); // 用8规格的袋子装
int p2 = apple(x - 6); //用6规格的袋子装
if ( p1 != 999 )
p1 = p1 + 1;
if (p2 != 999)
p2 = p2 + 1;
return min(p1, p2);
}
void mid(int y)
{
int ans = apple(y);
if (ans == 999)
printf("%d : -1\n", y);
else
printf("%d : %d\n", y, ans);
}
int main()
{
for (int i=1; i<100; ++i)
mid(i);
return 0;
}
把苹果从1到100的情况全部打印出来
结果如下:
所以我们就可以直接写出一个O(1)的代码
# include <stdio.h>
int main()
{
int apple;
scanf("%d", &apple);
if ((apple & 1) != 0)
printf("-1\n");
else if (apple < 18)
{
if (apple == 0)
printf("0\n");
else if (apple == 6 || apple == 8)
printf("1\n");
else if (apple == 12 || apple == 14 || apple == 16)
printf("2\n");
else
printf("-1\n");
}
else
printf("%d\n", (apple - 18)/8 + 3);
}
A和B轮流吃草最终谁会赢
一共有n重量的草,两只牛轮流吃草,A牛先吃。
每只牛在自己的回合,吃草的重量必须是4的幂,1,4,16,64.....
谁在自己的回合正好把草吃完谁赢,根据输入的n,返回谁赢。
思路:
对于n重量的草,我们可以直接穷举两头牛所有吃草的情况,判断是否有可能A牛能赢。
代码:
# include <stdio.h>
int grass(int n, int a) // n表示剩余草的重量, a表示当前吃草的牛
{
if (n < 5) //特派
{
if (n == 0 || n == 2)
return 1 - a; //当剩余草为0或2时,都是对方赢,也就是1 - a
else
return a;
}
int pick = 1; //穷举所有的可能
while (pick <= n)
{
if (grass(n - pick, 1 - a) == a) //剩余的草,给敌人挑
如果最后的结果显示还是该选手赢,那么向上返回
return a;
pick = pick * 4; //因为要尽可能要该选手赢,所以要遍历所有的结果。
}
return 1-a; //遍历了所有的结果都不能该选手赢的可能,所以向上返回敌人赢的结果。
}
int main()
{
int x = 0;// 0表示A牛,1表示B牛
for (int i = 0; i<=100; ++i)
printf("%d : %d\n", i, grass(i, x));
}
结果为:
所以我们就可以直接写出一个O(1)的代码
# include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
if (n % 5 == 0 || n % 5 == 2)
printf("B");
else
printf("A");
return 0;
}
判断一个数字是否是若干数量(数量>1)的连续正整数的和
实例:
12 = 3 + 4 + 5
返回true
代码:
# include <stdio.h>
int math(int n)
{
for (int i=0; i<n; ++i)
{
int num = 0;
for (int j=1+i; j<n; ++j)
{
if (num + j > n)
break;
else if (num + j == n)
return 1;
num = num + j;
}
}
return 0;
}
int main()
{
for (int i=1; i<100; ++i)
printf("%d : %d\n", i, math(i));
}
结果为:
所以我们就可以直接写出一个O(1)的代码
# include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
if (n == (n & (-n)))
printf("0");
else
printf("1");
}
求解好串个数
如果一个字符串里,它里面只含有一个长度>=2的回文子串,那么就称它为好串。
假设有一个长度为n的字符串,里面只有"r", "e", "d"这三个字符串,个数不限,求一共有多少种好串。
实例:
当长度为2时,一共有3种好串的可能
分别为: "rr", "ee", "dd"。
代码:
# include <stdio.h>
# include <malloc.h>
int made(int);
int is(char*, int, int);
int compare(char*, int);
int n;
char* path;
int made(int n) //构造一个n长度的path
{
path = (char*)malloc(sizeof(char)*n);
return compare(path, 0);
}
int is(char* path, int l, int r) //判断在[l, r]区间内,path数组是否存在回文子串
{
while (l < r)
{
if (path[l] != path[r])
return 0;
l++;
r--;
}
return 1;
}
int compare(char* path, int k)
{
if (k == n-1)
{
int cnt = 0;
for (int i=0; i<n-1; ++i)
for (int j=i+1; j<n; ++j)
{
if (is(path, i, j) == 1)
cnt = cnt + 1;
if (cnt >= 2) //回文子串个数
break;
}
if (cnt == 1)
return 1;
else
return 0;
}
else
{
int ans = 0; //递归穷举所有的可能
path[k] = 'r';
ans = ans + compare(path, k+1);
path[k] = 'e';
ans = ans + compare(path, k+1);
path[k] = 'd';
ans = ans + compare(path, k+1);
return ans;
}
}
int main()
{
for (int i=1; i<100; ++i)
printf("%d : %d\n", i, made(i));
}
结果:
发现从4开始,长度为n的答案都是(n+1)*6
所以我们就可以直接写出一个O(1)的代码
# include <stdio.h>
int main()
{
int n;
if (n == 1)
printf("0");
else if (n == 2)
printf("3");
else if (n == 3)
printf("18");
else
printf("%d", (n+1)*6)
}