1. 题目背景与需求分析
PTA天梯赛L1-030"一帮一"题目要求实现一个学生配对系统。给定n个学生(n为偶数),每个学生有性别(0表示女生,1表示男生)和姓名两个属性。需要将这些学生两两配对,要求:
- 每对学生的性别不同
- 配对时优先考虑排名靠前的学生(即输入顺序靠前的学生优先选择配对对象)
这个看似简单的题目在实际编码时会遇到几个关键难点:
- 如何高效实现性别不同的配对
- 如何保证排名靠前的学生优先选择
- 如何避免重复配对和遗漏
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路对比与选择
2.1 思路一:按性别分组存储
最直观的想法是将男女学生分开存储到两个数组中:
cpp复制vector<stu> male;
vector<stu> female;
这种方法的局限性在于:
- 输出时需要额外记录已配对的学生
- 难以保证排名靠前的学生优先选择
- 配对逻辑复杂,需要维护多个指针
2.2 思路二:双数组配对法
更优的方案是使用两个数组:
down数组:按输入顺序存储所有学生up数组:按逆序存储所有学生
这种设计的优势在于:
- 保持原始输入顺序的同时提供反向遍历能力
- 通过flag数组标记已配对学生,避免重复
- 前n/2次遍历即可保证排名优先原则
3. 代码实现详解
3.1 数据结构设计
cpp复制struct stu {
int sex; // 性别:0女1男
string name; // 姓名
int rank; // 输入顺序排名
} down[50], up[50]; // 正序和倒序存储
注意:数组大小设为50是因为题目保证N≤50,实际工程中应使用动态数组
3.2 输入处理阶段
cpp复制// 正序存储
for (int i = 1; i <= n; i++) {
cin >> sex >> name;
down[cnt].sex = sex;
down[cnt].name = name;
down[cnt].rank = i; // 记录原始排名
cnt++;
}
// 倒序存储
for (int i = n; i >= 1; i--) {
up[up_idx].sex = down[i].sex;
up[up_idx].name = down[i].name;
up[up_idx].rank = down[i].rank;
up_idx++;
}
3.3 核心配对算法
cpp复制int f[50] = {0}; // 标记up数组中已配对的学生
for (int i = 1; i <= n/2; i++) { // 只处理前n/2个学生
for (int j = 1; j <= n; j++) {
if (f[j] == 0 && down[i].sex != up[j].sex) {
cout << down[i].name << " " << up[j].name << endl;
f[j] = 1; // 标记为已配对
break; // 找到后立即跳出内层循环
}
}
}
算法时间复杂度分析:
- 外层循环:n/2次
- 内层循环:最多n次
- 总体复杂度:O(n²),对于n≤50完全可接受
4. 关键问题与优化方案
4.1 为什么使用双数组?
双数组设计巧妙地解决了两个问题:
- 保持原始顺序(down数组)
- 提供反向选择能力(up数组)
这种结构比单独使用一个数组更高效,因为:
- 正向遍历保证排名优先
- 反向遍历提高配对效率
4.2 标记数组的必要性
f[]数组用于记录up数组中哪些学生已被配对。没有这个标记会导致:
- 同一个学生被多次配对
- 输出结果不符合题目要求
4.3 边界情况处理
需要特别注意的边界情况:
- 输入n为0(题目保证n为偶数,可不处理)
- 所有同性别的极端情况(题目保证有解)
- 姓名包含空格的情况(题目说明中未提及,按单单词处理)
5. 算法优化方向
5.1 空间优化方案
当前使用了两个数组,可以优化为:
cpp复制vector<stu> students(n);
vector<bool> paired(n, false);
然后通过双指针法实现配对:
cpp复制int left = 0, right = n-1;
while (left < right) {
if (students[left].sex != students[right].sex) {
cout << students[left].name << " " << students[right].name << endl;
left++;
right--;
} else {
right--;
}
}
5.2 时间复杂度优化
可以使用哈希表记录未配对的异性学生:
cpp复制unordered_map<int, vector<stu>> genderMap;
// 按性别分组
for (auto& s : students) {
genderMap[s.sex].push_back(s);
}
// 配对逻辑
for (int i = 0; i < n/2; i++) {
int targetSex = 1 - students[i].sex;
if (!genderMap[targetSex].empty()) {
cout << students[i].name << " " << genderMap[targetSex].back().name << endl;
genderMap[targetSex].pop_back();
}
}
6. 常见错误与调试技巧
6.1 典型错误案例
- 数组越界:
cpp复制// 错误写法
for (int i = 0; i <= n; i++) // 应该为i < n
- 性别比较错误:
cpp复制// 错误写法
if (down[i].sex == up[j].sex) // 应该为!=
- 配对标记遗漏:
cpp复制// 错误写法
cout << ...;
// 忘记标记f[j] = 1;
6.2 调试建议
- 打印中间结果:
cpp复制// 调试打印
cout << "Down array:\n";
for (int i = 1; i <= n; i++) {
cout << down[i].name << " " << down[i].sex << endl;
}
- 使用小规模测试数据:
code复制4
0 Amy
1 Bob
0 Cindy
1 David
预期输出:
code复制Amy David
Bob Cindy
7. 工程实践中的改进
在实际项目中,我们可以考虑以下改进:
- 使用面向对象设计:
cpp复制class StudentPairingSystem {
private:
vector<Student> students;
public:
void inputData();
void pairStudents();
void outputResults();
};
- 增加输入验证:
cpp复制void validateInput(int n) {
if (n % 2 != 0 || n > 50) {
throw invalid_argument("Invalid student number");
}
}
- 支持文件输入输出:
cpp复制void readFromFile(const string& filename) {
ifstream fin(filename);
// 读取处理...
}
8. 类似题目拓展
掌握本题后,可以尝试解决以下变种问题:
- 多属性配对:除性别外,增加年龄、兴趣等配对条件
- 最优配对:寻找使某种评分函数最大化的配对方案
- 分组问题:将学生分成若干组,每组包含特定性别比例
例如,分组问题的伪代码:
code复制while 还有未分组学生:
找到性别比例最不平衡的组
添加能改善比例的学生到该组
在实际编码练习中,我发现理解题目要求后先设计清晰的数据结构非常重要。这道题的双数组解法虽然需要额外空间,但大大简化了配对逻辑的实现难度。对于算法初学者,建议先从这种直观的方法入手,再逐步考虑优化方案。
