面试必看:优势洗牌

贪心+双指针求解优势洗牌问题(C++ 实现)

题目描述

给定两个长度相等的数组 nums1nums2,定义 nums1 相对于 nums2 的优势为满足 nums1[i] > nums2[i] 的索引 i 的数量。要求返回 nums1 的任意一个排列,使得该排列相对于 nums2 的优势最大化。

问题分析

结合题目要求和数据特征,我们可以先梳理核心约束与解题思路:

  1. 两个数组长度相同,排列后元素需按索引一一对应匹配;
  2. 目标是最大化满足 nums1[i] > nums2[i] 的索引数量,本质是用最优的匹配策略实现收益最大化;
  3. 直接暴力枚举所有排列会产生极高的时间复杂度,无法高效处理较大数据量,因此需要选择更优的算法方案。

结合问题特征,贪心算法+双指针 是适配该场景的最优解:贪心策略用于保证每一步选择都能获取当前最优收益,双指针用于高效遍历匹配元素,整体算法可以在线性遍历结合排序的基础上完成计算。

算法思路

核心策略

采用贪心匹配 原则:用 nums1最小的可用大数 去匹配 nums2 中对应元素,无法满足大小关系时,用 nums1 中最小的数去填充,最大化有效匹配数量的同时,减少优质元素的浪费。

具体步骤

  1. 数组预处理
    • nums1 执行升序排序,方便通过双指针快速获取当前最大/最小可用元素;
    • nums2 构建(值, 原索引)的键值对数组,再对该数组执行升序排序。保留原索引是为了保证最终结果能对应到题目要求的原始位置。
  2. 双指针初始化
    定义左指针 left 指向排序后 nums1 的起始位置(最小值),右指针 right 指向排序后 nums1 的末尾位置(最大值)。
  3. 逆序遍历匹配
    从大到小遍历处理后的 nums2 数组,依次判断当前元素:
    • nums1 的当前最大值大于 nums2 的当前元素,将该最大值填入结果数组的对应原始索引,右指针左移;
    • 若不满足大小关系,说明当前最大元素也无法形成优势,改用 nums1 的当前最小值填充,左指针右移。
  4. 输出结果
    遍历完成后,结果数组即为满足条件的 nums1 最优排列。

C++ 实现代码

cpp 复制代码
#include <vector>
#include <algorithm>
using namespace std;

class Solution {
public:
    vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
        // 获取数组长度
        int n = nums1.size();
        // 初始化结果数组,长度与输入数组一致
        vector<int> res(n, 0);
        // 存储nums2的(值, 原索引)对,保留原始位置信息
        vector<pair<int, int>> nums2_temp;
        for (int i = 0; i < n; ++i) {
            nums2_temp.emplace_back(nums2[i], i);
        }

        // 对nums1升序排序
        sort(nums1.begin(), nums1.end());
        // 对nums2的键值对数组按值升序排序
        sort(nums2_temp.begin(), nums2_temp.end());

        // 双指针初始化:left指向nums1最小值,right指向nums1最大值
        int left = 0;
        int right = n - 1;

        // 逆序遍历排序后的nums2_temp,从大元素开始匹配
        for (int i = n - 1; i >= 0; --i) {
            int val = nums2_temp[i].first;   // nums2当前元素值
            int index = nums2_temp[i].second; // 该元素在原数组中的索引
            // 贪心选择:能用大值匹配就用大值,否则用小值填充
            if (val < nums1[right]) {
                res[index] = nums1[right];
                --right;
            } else {
                res[index] = nums1[left];
                ++left;
            }
        }
        return res;
    }
};

复杂度分析

时间复杂度

算法主要耗时操作为排序操作:

  • nums1 排序的时间复杂度为 O(nlog⁡n)O(n\log n)O(nlogn);
  • nums2 键值对数组排序的时间复杂度为 O(nlog⁡n)O(n\log n)O(nlogn);
  • 后续双指针遍历为线性操作,时间复杂度为 O(n)O(n)O(n)。

整体时间复杂度为 O(nlog⁡n)O(n\log n)O(nlogn),在数据量较大时仍能保持高效运行。

空间复杂度

  • 额外开辟了存储 nums2 键值对的数组 nums2_temp 和结果数组 res,两者空间开销均为 O(n)O(n)O(n);
  • 排序过程中递归调用栈的空间开销为 O(log⁡n)O(\log n)O(logn),可忽略不计。

整体空间复杂度为 O(n)O(n)O(n),属于线性空间开销。

算法验证与适用场景

该算法通过贪心策略保证了每一步匹配的最优性,双指针遍历避免了重复判断,能够稳定得到优势最大化的排列。适用于题目给定的所有常规场景,无特殊数据边界限制,在力扣对应题目中可通过全部测试用例。


总结

  1. 本题核心解法为贪心算法+双指针,通过排序预处理和逆序匹配实现最优解;
  2. 算法时间复杂度为 O(nlog⁡n)O(n\log n)O(nlogn),空间复杂度为 O(n)O(n)O(n),兼顾了时间效率与实现简洁性;
  3. 保留原数组索引是解题关键,确保排列结果能对应到题目要求的原始位置。
相关推荐
NAGNIP1 天前
轻松搞懂全连接神经网络结构!
人工智能·算法·面试
NAGNIP1 天前
一文搞懂激活函数!
算法·面试
董董灿是个攻城狮1 天前
AI 视觉连载7:传统 CV 之高斯滤波实战
算法
前端Hardy1 天前
面试官:JS数组的常用方法有哪些?这篇总结让你面试稳了!
javascript·面试
牛奶1 天前
React 底层原理 & 新特性
前端·react.js·面试
牛奶1 天前
ts随笔:面向对象与高级类型
前端·面试·typescript
牛奶1 天前
React 基础理论 & API 使用
前端·react.js·面试
爱理财的程序媛1 天前
openclaw 盯盘实践
算法
SuperEugene1 天前
从 Vue2 到 Vue3:语法差异与迁移时最容易懵的点
前端·vue.js·面试