题目描述

n(n>=3)n(n>=3)枚硬币中有一枚重量不合格的硬币(过轻或者过重),若只有一架天平可以用来称重,且称重的硬币数量没有限制,设计一个算法找出这枚不合格的硬币,使得称重次数最少

题目分析

本题用的是减治法,我找了很多网上的写法,但是没有符合我想象中的代码,或者就是题目略有不同,所以决定自己写一个。写的时候花了很长时间处理边界问题。
注意本题是n枚硬币,并且没有事先告知硬币是偏重还是偏轻,因此如果就是根据重量的大小来判断,无法得到正确的答案。

递归C++

主要的思想是根据数组的奇偶来分:

  • 长度为奇数时,先不考虑第一个元素,对往后的元素分两段求和,判断它们的大小。如果不相等,则先后递归处理左右两段。如果相等,并且第一个元素不等于第二个元素(这个条件很重要,若只是后面两段相等,并不能得出第一个元素就是假币),那么第一个元素就是假币
  • 长度为偶数时,将数组分成长度相等的两段,并分别求和。若相等,则假币不在这个区间里;若不等,递归处理左右两段
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <vector>
#include <ctime>
#include <algorithm>
using namespace std;
vector<int> coins;
int N;

void make_coins() {
int tweight = rand() % 10 + 1; //生成[1,10]之间的随机数代表真硬币重量
for (int i = 0; i < N; i++) coins[i] = tweight;
int fweight = rand() % 20 + 1; //生成[1,20]之间的随机数代表假硬币重量
int pos = rand() % N; // 生成[0,N)之间的随机数,代表假硬币的位置
while (fweight == tweight) fweight = rand() % 20 + 1;
coins[pos] = fweight; // codeslogan
}

int sum_arr(int l, int r) {
int sum = 0;
for (int i = l; i <= r; i++) sum += coins[i];
return sum;
}

int dfs(int n, int l, int r) {
// 当剩余的长度为2时,根据当前两个元素的大小,及它们的前后关系判断
if(n == 2) {
if (coins[l] != coins[r]) {
if ((l-1) >= 0) return coins[l-1] == coins[l]? (r+1):(l+1);
else return coins[r+1] == coins[l]? (r+1):(l+1);
}
return -1;
}
// 当剩余长度为1时,需要同时判断它左右两边的元素
if (n == 1) {
if ((l-1) >= 0 && coins[l-1] != coins[l] && coins[l] != coins[l+1]) return l+1;
if ((l-1) < 0 && coins[r+1] != coins[r] && coins[r+2] != coins[r]) return r+1;
return -1; // codeslogan
}

int pos = -1;
if (n % 2 == 0) { // 长度为偶数
int rr = l+(r-l)/2; // 计算右边界
int left = sum_arr(l, rr);
int right = sum_arr(rr+1, r);
if (left != right) {
pos = dfs(n/2, l, rr);
if (pos != -1) return pos;
pos = dfs(n/2, rr+1, r);
if (pos != -1) return pos;
}
} else { // 长度为奇数
int rr = l+(r-l)/2;
int left = sum_arr(l+1, rr);
int right = sum_arr(rr+1, r);
if (left == right ) {
if (coins[l] != coins[l+1]) pos = l + 1;
}
else {
pos = dfs(n/2, l+1, rr);
if (pos != -1) return pos;
pos = dfs(n/2, rr+1, r);
if (pos != -1) return pos;
}
}
return pos;
}

void PrintOut() {
for (auto t: coins) cout << t << ' ';
cout << endl;
}
// 遍历验证答案
bool check_ans(int n) {
int val = coins[n];

int idx;
for (int i = 0; i < coins.size(); i++)
if (val == coins[i]) {
idx = i;
break;
}

if (idx == n) return true;
else return false;
}

int main() {
srand((unsigned int)time(NULL));
N = rand() % 30 + 3; // 随机生成[3,33)之间的数,代表n枚硬币
coins.resize(N);
make_coins();
PrintOut();
int pos = dfs(N, 0, N-1);
printf("共有%d枚硬币\n", N);
printf("假币重量为%d,位于第%d个位置\n", coins[pos-1], pos);
bool flag = check_ans(pos-1);
if (flag) printf("经遍历验证,答案正确!\n");
else printf("答案错误!\n");

return 0;
}