优势洗牌如何用贪心匹配?为什么小牌打不过就去消耗对方最大牌?
简化版
优势洗牌的贪心思路是把 nums1 排序,再按 nums2 的值从大到小处理。若 nums1 当前最大值能赢 nums2 当前最大值,就用它赢;否则用 nums1 当前最小值去“牺牲”,消耗这个难赢的对手。
详细版
先记录 nums2 每个值的原下标,并按值降序排序。nums1 升序后用左右指针表示最小牌和最大牌。处理 nums2 的大牌时,如果 nums1[right] > nums2[i],就把最大牌放到该位置,获得一分;否则说明最大牌都赢不了,只能用最小牌去填这个位置。
这种策略类似田忌赛马:能赢就用刚好当前最强资源去赢最强对手,不能赢就用最弱资源止损。时间复杂度 O(n log n),空间复杂度 O(n)。
完整版教学
一、题目本质是最大化“胜场”
给两个数组 nums1 和 nums2,你可以重排 nums1,目标是让尽可能多的位置满足 nums1[i] > nums2[i]。这不是求总和,也不是逐位最优,而是匹配问题。
nums1 = [2,7,11,15]
nums2 = [1,10,4,11]
一种答案:[2,11,7,15]
胜场:2>1, 11>10, 7>4, 15>11,共 4 场
排序后再匹配,可以把问题变成“手里哪些牌该赢,哪些牌该牺牲”。
二、为什么从 nums2 的最大值开始处理
越大的 nums2 越难赢。如果能赢大牌,应该尽早安排;如果连当前最大 nums1 都赢不了它,那其他牌更赢不了,继续纠结没有意义。
| 对手牌 | 决策 |
|---|---|
| 能被我方最大牌赢 | 用最大牌赢下这场 |
| 我方最大牌也赢不了 | 用最小牌牺牲 |
记忆钩子:优势洗牌就是田忌赛马,赢得了就赢强的,赢不了就派最弱的送。
从最大对手开始,可以让每一步的胜负判断最干净。
三、为什么赢不了时要用最小牌牺牲
如果 nums1[right] <= nums2[i],说明手里最大牌都打不过当前对手。此时任何牌放在这里都会输,既然必输,就应该损失最小的资源。
手牌:[2, 7, 11]
对手:15
11 都赢不了 15,所以 2、7、11 都是输
牺牲 2 最划算
这一步的贪心交换理由很清楚:把大牌留给后面较小的对手,至少还有赢的机会。
四、为什么能赢时用最大牌是安全的
处理顺序是从大到小。当前对手是剩余对手中最大的,如果最大牌能赢它,那么用最大牌赢下当前最难的一场不会浪费。较小的对手留给剩余牌处理。
也有写法是从小到大处理,用“最小能赢的牌”去匹配当前对手;两种思路本质一致。降序写法的优点是牺牲逻辑很直观。
降序对手:先处理 boss
能打过 boss:派最强赢
打不过 boss:派最弱送
这保证了每张牌只使用一次,且每一步都最大化剩余局面的可能性。
五、代码模板
int[] advantageCount(int[] nums1, int[] nums2) {
int n = nums1.length;
Arrays.sort(nums1);
Integer[] idx = new Integer[n];
for (int i = 0; i < n; i++) idx[i] = i;
Arrays.sort(idx, (a, b) -> nums2[b] - nums2[a]);
int[] ans = new int[n];
int left = 0, right = n - 1;
for (int id : idx) {
if (nums1[right] > nums2[id]) {
ans[id] = nums1[right--];
} else {
ans[id] = nums1[left++];
}
}
return ans;
}
因为输出要放回 nums2 原位置,所以排序 nums2 时不能只排序值,还要带着原下标。
六、用数字例子走一遍
nums1=[12,24,8,32] 排序为 [8,12,24,32],nums2=[13,25,32,11] 按值降序的下标顺序是:
32(idx2), 25(idx1), 13(idx0), 11(idx3)
处理:
对 32:32 赢不了,牺牲 8
对 25:32 能赢,放 32
对 13:24 能赢,放 24
对 11:12 能赢,放 12
最终胜 3 场,牺牲最小牌给最难赢的一场。
七、常见误区与追问
- 误区:按原下标直接贪心。 原顺序不能体现对手强弱,容易浪费大牌。
- 误区:赢不了时随便放一张牌。 必输场应牺牲最小牌,把更大牌留给可能赢的场。
- 误区:排序 nums2 后忘记原下标。 输出必须对应原数组位置。
- 追问:能不能从小到大做? 可以,用最小能赢的牌赢当前小对手,否则最小牌牺牲。
- 追问:为什么不是二分找刚好大一点的牌? TreeMap 写法可以,但排序双指针更简洁。
- 追问:复杂度是多少? 两个排序主导,时间
O(n log n),额外空间O(n)。
八、加强记忆
优势洗牌的口令是“强敌优先,能赢则赢,不能赢则最小牺牲”。把 nums2 带原下标降序,把 nums1 升序成手牌。最大牌能赢当前强敌就拿分,最大牌都赢不了就派最小牌送掉。这个策略把田忌赛马的思想写成了双指针。