← 返回题目列表

优势洗牌如何用贪心匹配?为什么小牌打不过就去消耗对方最大牌?

中等 第 24 / 29 题 更新于 2026/08/01
贪心排序双指针优势洗牌

简化版

优势洗牌的贪心思路是把 nums1 排序,再按 nums2 的值从大到小处理。若 nums1 当前最大值能赢 nums2 当前最大值,就用它赢;否则用 nums1 当前最小值去“牺牲”,消耗这个难赢的对手。

详细版

先记录 nums2 每个值的原下标,并按值降序排序。nums1 升序后用左右指针表示最小牌和最大牌。处理 nums2 的大牌时,如果 nums1[right] > nums2[i],就把最大牌放到该位置,获得一分;否则说明最大牌都赢不了,只能用最小牌去填这个位置。

这种策略类似田忌赛马:能赢就用刚好当前最强资源去赢最强对手,不能赢就用最弱资源止损。时间复杂度 O(n log n),空间复杂度 O(n)

完整版教学

一、题目本质是最大化“胜场”

给两个数组 nums1nums2,你可以重排 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 升序成手牌。最大牌能赢当前强敌就拿分,最大牌都赢不了就派最小牌送掉。这个策略把田忌赛马的思想写成了双指针。