在n枚外观相同的硬币中,有一枚硬币是假币,但是不知道假币的重量是较重还是较轻,请设计算法找出这枚假币。
(1)在n枚硬币中只有一枚是假币,且未知假币重量。
(2)把假币分成A,M,B三堆,确保第一堆A和第三堆B的硬币数量是相同(相等)的。用model记录真币。
(3)称第一堆A的重量为sumA,第三堆B的重量为sumB,如果sumA和sumB是相等的,真币为model = A[0],那么假币在第二堆M上,否则第二堆全是真币。
(4)如果sumA和sumB不相等,则真币在第二堆,真币model = M[0],此时计算第一堆和第三堆的长度(二者长度相等),得长度num,如果sumA等于num * model,说明A里面是真币,假币在B堆里面,否则,假币在A堆里面。
(5)我们输入的数量n一定要大于等于3,小于3无法判别。
(6)用model记录真币的重量,初始值是-1(硬币重量大于0),因为n大于等于3,所以一定能够找到真币,model的值一定发生变化。
(7)这是一个递归算法,考虑数量减少到1和2的情况,如果数量递减到1,那么剩下的这一个就是真币了(当然,也可以输入没有假币的情况,然后根据真币重量比较,如果返回的硬币和真币重量是一样的,那么就没有假币);如果只剩两枚硬币,则判断第一枚是否是假币,是就返回第一枚,否则返回第二枚。
有一个问题就是怎么保证A堆和B堆数量相等?
在纸上写了多种n的不同分法,发现了一个公式。
公式 | A的最后一个下标:n/3 - 1 | B的第一个下标:2*(n/3) + n % 3 | 下标 |
---|---|---|---|
n = 3 | 0 | 2 | 0,1,2 |
n = 4 | 0 | 3 | 0,1,2,3 |
n = 5 | 0 | 4 | 0,1,2,3,4 |
n = 6 | 1 | 4 | 0,1,2,3,4,5 |
n = 7 | 1 | 5 | 0,1,2,3,4,5,6 |
n = 8 | 1 | 6 | 0,1,2,3,4,5,6,7 |
n = 9 | 2 | 6 | 0,1,2,3,4,5,6,7,8 |
这样分A和B的数量就是相等的。
注意2n/3和2(n/3)结果是不一样的,比如n=5,25/3 = 3,2(5/3) = 2。
两个算法函数,一个称重函数,一个测试函数。
递归和迭代可相互转化,只要控制好范围就没有问题。
#include
using namespace std;
int weightSum(int *coins, int begin, int end);
int findBadCoinIterate(int *coins, int length, int &model);
int findBadCoinRecursion(int *coins, int begin, int end, int &model);
void findBadCoinTest();
int main() {
findBadCoinTest();
return 0;
}
int weightSum(int *coins, int begin, int end) {
int sum = 0;
for(int i = begin;i <= end;i++) {
sum += coins[i];
}
return sum;
}
//递归实现
int findBadCoinRecursion(int *coins, int begin, int end, int &model) {
int n = end - begin + 1;
if(n == 1) {
return begin;//因为只剩一个硬币了
}
else if(n == 2) {
return coins[begin] != model ? begin : end;//两个之中只有一个是假币,注意model肯定是真币,因为在n >= 3的时候求过了
}
int midl = n / 3 - 1;//A堆的最后一个
int midr = 2 * (n / 3) + n % 3;//B堆的第一个
int sumA = weightSum(coins, begin, midl + begin);//称重
int sumB = weightSum(coins, midr + begin, end);
if(sumA == sumB) {
model = coins[begin];//找到真币,假币在中间那堆
return findBadCoinRecursion(coins, midl + begin + 1, midr + begin - 1, model);
}
else {
model = coins[midl + begin + 1];//真币在中间那堆
int num = midl + 1;//A,B堆的硬币个数,A,B的硬币个数相等,A堆的数量是num = midl + begin - begin + 1 = midl + 1
if(sumA == num * model) {
return findBadCoinRecursion(coins, midr + begin, end, model);//假币在B堆
}
else {
return findBadCoinRecursion(coins, begin, midl + begin, model);//假币在A堆
}
}
}
//迭代实现
int findBadCoinIterate(int *coins, int length, int &model) {
int begin = 0;
int end = length - 1;
while(begin <= end) {
int n = end - begin + 1;
if(n == 1) {
return begin;
}
else if(n == 2) {
return coins[begin] != model ? begin : end;
}
int midl = n / 3 - 1;
int midr = 2 * (n / 3) + n % 3;
int sumA = weightSum(coins, begin, midl + begin);
int sumB = weightSum(coins, midr + begin, end);
if(sumA == sumB) {
model = coins[begin];//找到真币
begin = midl + begin + 1;//假币在中间那堆,缩小范围
end = midr + begin - 1;
}
else {
model = coins[midl + begin + 1];
int nAB = midl + 1;
if(sumA == nAB * model) {
begin = midr + begin;//假币在B堆,缩小范围
}
else {
end = midl + begin;//假币在A堆,缩小范围
}
}
}
return -1;
}
//测试
void findBadCoinTest() {
int contin = 1;
do {
system("cls");
int n = 0;
cout << "请输入硬币数量n(n > 3):";
cin >> n;
if(n < 3) {
cout << "你输入的硬币数量太少了!" << endl;
continue;
}
int *coins = new int[n];
cout << "请输入硬币的重量:";
for(int i = 0;i < n;i++) {
cin >> coins[i];
}
int model = -1;
int index = -1;
cout << "--------递归结果--------\n" << endl;
model = -1;
index = findBadCoinRecursion(coins, 0, n - 1, model);
if(coins[index] > model) {
cout << "第" << index + 1 << "个硬币是假的,比其他硬币重了!" << endl;
}
else if(coins[index] < model) {
cout << "第" << index + 1 << "个硬币是假的,比其他硬币轻了!" << endl;
}
else {
cout << "没有假币!" << endl;
}
cout << "\n--------迭代结果--------" << endl;
model = -1;
index = findBadCoinIterate(coins, n, model);
if(coins[index] > model) {
cout << "第" << index + 1 << "个硬币是假的,比其他硬币重了!" << endl;
}
else if(coins[index] < model) {
cout << "第" << index + 1 << "个硬币是假的,比其他硬币轻了!" << endl;
}
else {
cout << "没有假币!" << endl;
}
delete []coins;
cout << "继续检测输入1,退出输入0:";
cin >> contin;
} while (contin == 1);
cout << "欢迎再次检测!" << endl;
}
看了别人的代码太长了,就自己写一个,主要把思路简化了很多,有一位博主的实在是分的情况太多了,以至于不太好看。
没有时间测太多数据,应该问题不大吧。