我是靠谱客的博主 复杂鞋子,这篇文章主要介绍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]进行改进
复制代码
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
86class 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场周赛内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复