LeetCode 1366题解析:基于投票的团队排名算法
1. 问题背景与需求分析LeetCode 1366题Rank Teams by Votes是一个典型的排序问题要求我们根据投票结果对参赛团队进行排名。题目场景可以类比现实中的比赛评分假设有多个评委对参赛队伍进行投票每个评委按照自己的偏好对所有队伍进行排名即给出一个从高到低的顺序我们需要综合所有评委的投票结果确定最终的队伍排名。这个问题的核心在于如何定义公平的排名规则。根据题目要求排名规则如下首先比较每个队伍在所有投票中的第一名次数次数多的排名靠前如果第一名次数相同则比较第二名次数以此类推如果所有位置上的票数都相同则按字母顺序排列2. 算法设计思路解析2.1 直观解法与复杂度分析最直观的解法是为每个团队建立一个大小为n团队数量的统计数组记录该团队在每个名次上获得的票数。然后自定义排序规则先比较第一位票数再比较第二位票数以此类推。这种解法的时间复杂度为O(m*n n log n)其中m是投票数量n是团队数量。空间复杂度为O(n^2)。虽然对于LeetCode的测试用例来说已经足够但我们可以尝试更优化的解法。2.2 深度优先搜索解法题目提供的代码采用了深度优先搜索(DFS)的策略其核心思路是维护一个状态数组status记录已经确定排名的字符使用集合tee保存当前需要考虑排序的字符通过递归函数count统计当前排名位置上各字符的得票数每次递归处理一个排名位置逐步缩小候选字符范围这种解法的优势在于可以提前终止不必要的比较当某个位置上的票数已经能确定唯一排名时就可以直接返回结果。3. 代码实现详解3.1 数据结构设计setchar tee, cp, tmp; // 当前候选字符集合、备份集合、临时集合 int m, n; // m投票数量n团队数量 char ca; // 临时字符变量 vectorbool status(300, false); // 字符状态记录tee集合保存当前轮次需要排序的候选字符cp和tmp是辅助集合用于处理集合运算status数组记录哪些字符已经确定了最终排名3.2 核心递归函数countvoid count(vectorstring votes, int index) { if(tee.size() 1 || index n) return; unordered_mapchar, int ump; // 统计当前index位置上各候选字符的得票数 for(int i 0; i m; i) { ca votes[i][index]; if(tee.find(ca) ! tee.end()) { ump[ca]; } } // 处理无票情况 if(ump.size() 0) { count(votes, index 1); return; } // 找出得票最多的字符 int mx INT_MIN; tee.clear(); for(auto [k, l] : ump) { if(l mx) { mx l; tee.clear(); tee.insert(k); } else if(l mx) tee.insert(k); } // 递归处理下一位置 if(tee.size() 1) return; else count(votes, index 1); }这个递归函数实现了以下逻辑终止条件候选字符唯一或已处理完所有排名位置统计当前排名位置上各候选字符的得票数如果没有候选字符在该位置得票则处理下一位置找出当前位置上得票最多的字符可能有多个如果仍有多个候选则递归处理下一排名位置3.3 主函数rankTeamsstring rankTeams(vectorstring votes) { m votes.size(), n votes[0].size(); if(m 1) return votes[0]; // 只有一票的特殊情况 string ret; for(int i 0; i n; i) { tee.clear(); // 收集尚未确定排名的字符 for(int j 0; j m; j) { ca votes[j][i]; if(status[ca] false) tee.insert(ca); } // 处理当前排名位置 while(tee.size() 1) { cp tee; // 备份当前候选集合 count(votes, i); // 统计并筛选候选 // 处理筛选结果 for(const char ch : tee) { if(status[ch] false) ret ch; status[ch] true; } // 准备下一轮处理 tmp tee; tee cp; for(const char ch : tmp) tee.erase(ch); } } return ret; }主函数的工作流程处理特殊情况只有一票对于每个排名位置i收集所有尚未确定排名的字符通过count函数筛选出当前排名位置的字符更新结果字符串和状态数组处理剩余候选字符4. 算法优化与替代方案4.1 哈希表自定义排序解法string rankTeams(vectorstring votes) { if(votes.empty()) return ; int n votes[0].size(); // 建立字符到排名统计的映射 unordered_mapchar, vectorint rank; for(char c : votes[0]) { rank[c] vectorint(n, 0); } // 统计每个字符在各个位置上的得票数 for(const auto vote : votes) { for(int i 0; i n; i) { rank[vote[i]][i]; } } // 准备排序 vectorchar teams(votes[0].begin(), votes[0].end()); // 自定义排序函数 sort(teams.begin(), teams.end(), [](char a, char b) { for(int i 0; i n; i) { if(rank[a][i] ! rank[b][i]) { return rank[a][i] rank[b][i]; } } return a b; }); return string(teams.begin(), teams.end()); }这种解法更直观易懂利用自定义排序函数实现了题目要求的比较规则。虽然时间复杂度相同但代码更简洁在实际面试中可能是更好的选择。4.2 两种解法的比较特性DFS解法哈希表排序解法时间复杂度O(m*n^2) 最坏情况O(m*n n log n)空间复杂度O(n)O(n^2)代码复杂度较高递归逻辑不易理解较低直观易懂适用场景早期终止可能带来优化通用性强面试推荐度不推荐推荐5. 常见问题与调试技巧5.1 边界条件处理空输入处理需要检查votes是否为空votes[0]是否有效单票情况当只有一张投票时直接返回该投票顺序所有票数相同需要正确处理字母顺序排序5.2 调试技巧打印中间状态在递归函数中加入打印语句输出当前候选集合和统计结果小规模测试先用2-3个团队、2-3张投票的小例子验证算法随机测试生成随机投票数据与简单解法对比结果提示在面试中建议先实现哈希表排序的解法确保正确性后再考虑优化。DFS解法虽然展示了不同的思路但实现复杂度较高容易出错。6. 实际应用与扩展这个问题可以扩展到许多实际场景比赛评分系统如体育比赛、编程竞赛的团队排名推荐系统综合多个用户的偏好对物品进行排序选举系统处理多轮投票的选举场景对于更复杂的场景可能需要考虑权重投票不同评委的投票可能有不同权重不完全排序评委可能只对部分团队进行排序动态更新新增投票时需要高效更新排名这类排序问题在系统设计中也很常见理解其核心原理对设计高效、公平的排名系统很有帮助。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →