我是靠谱客的博主 复杂鞋子,最近开发中收集的这篇文章主要介绍LeetCode第280场周赛 - 数组的最大与和 - 匈牙利算法/KM算法LeetCode第280场周赛 - 数组的最大与和 - 匈牙利算法/KM算法,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
LeetCode第280场周赛 - 数组的最大与和 - 匈牙利算法/KM算法
关键词: leetcode | 280 周赛 | KM | 匈牙利算法
题目:
- LeetCode「美团 & 力扣」联合主办 第 280 场周赛 第四题 《数组的最大与和》
解题思路:
- 题目的本质是将数组nums中的n个数字分配到不同的篮子中,每个数字分配到每个篮子时都有一个权值(按位与运算结果值),需要将整体分配方案的权值和最大化。
- 可以明显地看出是两类物品的匹配问题,又涉及权重的最大化,故可将题目抽象为一个二分图最大权匹配问题。
- 先考虑最朴素的方法,将每个数字和每个篮子的匹配方案都枚举一遍,计算出每种方案的权值和后比较获得最大值即可。算法时间复杂度为 O ( ( n ∗ n u m S l o t s ) ! ) O((n*numSlots)!) O((n∗numSlots)!)。(当然是不可行的)
- 当然,二分图匹配问题毕竟很经典了,如果能想到二分图匹配问题,就应当能想到可能存在已有算法(但是当然没学习过相关算法的话也很难现场想出解决算法)。实际上确实存在已有算法——KM算法,能够解决二分图最大权匹配问题,即找到二分图匹配方案中的权值和最大方案。
- 那么先上代码,再慢慢解释KM算法的原理(实际上我也只是听说过该算法,这次也好好学习一下)
代码:
- 代码根据参考文章[3]进行改进
class Solution {
// 建图
void buildGraph(vector<vector<int>>& edges, vector<int>& nums, int numSlots) {
int n = nums.size();
edges = vector<vector<int>>(n,vector<int>(2*numSlots));
for(int i=0; i<n; i++) {
for(int j=0; j<numSlots; j++) {
edges[i][j] = edges[i][j+numSlots] = (nums[i]&(j+1)); // j,j+numSlots 分别表示编号为 j+1 的篮子的两个空位
}
}
}
// KM算法寻找最大权匹配
// 在匈牙利算法的基础上增加期望度机制。
// 初始化左半图每个number的期望度为该number最大可获得权值,右半图每个slot的期望度为0。
// 每次匹配时,只考虑边权等于相连number和slot期望度和的边。
// 当一次匹配中无法成功时,执行期望度更新操作:将涉及的所有number期望度减一、所有slot期望度加一。
int KM(vector<vector<int>>& edges) {
int n = edges.size(), m = edges[0].size(); // 二分图两边的点数
vector<int> match(m, -1); // match[i]为teacher[i]匹配的student编号
vector<int> exNumber(n); // number期望
vector<int> exSlot(m, 0); // slot的期望
for (int i = 0; i < n; ++i) {
exNumber[i] = *max_element(edges[i].begin(), edges[i].end());
}
// 为每个number匹配teacher
for (int i = 0; i < n; ++i) {
while(true) {
vector<bool> visNumber(n, false);
vector<bool> visSlot(m, false);
if (dfs(i, m, edges, match, visNumber, visSlot, exNumber, exSlot)) break;
// 无法匹配降低期望
for (int j = 0; j < n; ++j) {
if (visNumber[j]) exNumber[j]--;
}
for (int j = 0; j < m; ++j) {
if (visSlot[j]) exSlot[j]++;
}
}
}
int ans = 0;
// 将每一对匹配的权值加和(这里通过遍历slot的匹配情况来实现)
for (int i = 0; i < m; ++i) if(match[i]!=-1) {
ans += edges[match[i]][i];
}
return ans;
}
// 匈牙利算法寻找完美匹配
// 为第i个number寻找匹配的slot。对每个可匹配slot,若已被其他number匹配,则递归为其他number寻找另外的可匹配slot;找不到则为该number匹配其他slot
bool dfs(int i, int m, vector<vector<int>>& edges, vector<int>& match, vector<bool>& visNumber, vector<bool>& visSlot, vector<int>& exNumber, vector<int>& exSlot) {
visNumber[i] = true;
for (int j = 0; j < m; ++j) {
if (visSlot[j]) continue;
int diff = exNumber[i] + exSlot[j] - edges[i][j];
if (!diff) {
visSlot[j] = true;
if (match[j] == -1 || dfs(match[j], m, edges, match, visNumber, visSlot, exNumber, exSlot)) {
match[j] = i;
return true;
}
}
}
return false;
}
public:
int maximumANDSum(vector<int>& nums, int numSlots) {
/*
* 问题抽象为 “n个数字 - 2*numSlots个篮子” 的二分图最大匹配问题
** 二分图中的点:
*** n个数字:nums中需要被分配的n个数字
*** 2*numSlots个篮子:每个篮子最多可以放两个数字
** 二分图中的边:
*** 每个数字和每个篮子间都有一条边,边权为数字与篮子编号的 按位与运算 结果,表示把该数字放到该篮子时可获得的权值
*/
// 建图(用n*2numSlots的邻接矩阵表示图。因为后续算法中只需要考虑边权而不需要考虑点权,故只需保存边权信息。)
vector<vector<int>> edges;
buildGraph(edges,nums,numSlots);
// 计算(使用KM算法,计算最优匹配方案权值和)
return KM(edges);
}
};
KM算法细节解释:
- 建议先浏览参考文章[1]后再阅读本段,参考文章[1]中以简单的例子介绍了KM算法的整体流程,本段仅以笔者浅薄的知识做一些补充,相当于参考文章[1]的进阶版。
- KM算法本质上在匈牙利算法的基础上增加期望度机制。
- 初始化左半图每个number的期望度为该number最大可获得权值,右半图每个slot的期望度为0。
- 每次匹配时,只考虑边权等于相连number和slot期望度和的边。
- 当一次匹配中无法成功时,执行期望度更新操作:将涉及的所有number期望度减一、所有slot期望度加一。
- 可行性:
- 相当于限制了参与搜索的边的集合,使得权值较大的边能被优先搜索,保证了算法可行的充分性。
- 每次降低期望度时,将本次搜索中左半图所有点期望度减1、右半图所有点期望度加1。每次降低期望度时,设涉及到的左半图点个数为 n 1 n_1 n1,则涉及到的右半图点个数为 n 1 − 1 n_1-1 n1−1(不搜第 n 1 n_1 n1个左半图点时恰好匹配,而加入该点后不匹配,因此相关左半图点刚好比相关右半图点多一个)。
- 对已被匹配的边而言,对应左半图点期望度减1,右半图点期望度加1,故这些边仍然在被搜索的边集合中。期望度更新操作后,每个左半图点相关的权值稍小的边被加入搜索集中。由于更新时以1为粒度,所以能保证搜索过程中不会漏掉边,保证了算法可行的必要性。
参考:
- [1] https://www.cnblogs.com/logosG/p/logos.html
- [2] https://www.cnblogs.com/fzl194/p/8834847.html
- [3] https://leetcode-cn.com/problems/maximum-compatibility-score-sum/solution/c-km-suan-fa-er-fen-tu-dai-quan-zui-da-p-ztmd/
最后
以上就是复杂鞋子为你收集整理的LeetCode第280场周赛 - 数组的最大与和 - 匈牙利算法/KM算法LeetCode第280场周赛 - 数组的最大与和 - 匈牙利算法/KM算法的全部内容,希望文章能够帮你解决LeetCode第280场周赛 - 数组的最大与和 - 匈牙利算法/KM算法LeetCode第280场周赛 - 数组的最大与和 - 匈牙利算法/KM算法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复