【C/C++】954. 二倍数对数组

227 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情


题意

题目描述: 给定一个长度为偶数的整数数组 arr,只有对 arr 进行重组后可以满足 “对于每个 0 <= i < len(arr) / 2,都有 arr[2 * i + 1] = 2 * arr[2 * i]” 时,返回 true;否则,返回 false

  • 数据范围:
    • 0<=arr.length<=31040 <= arr.length <= 3 * 10^4
    • arr.length是偶数arr.length 是偶数
    • 105<=arr[i]<=105-10^5 <= arr[i] <= 10^5
  • 示例 1:
输入:arr = [3,1,3,6]
输出:false
  • 示例 2:
输入:arr = [2,1,2,6]
输出:false
  • 示例 3:
输入:arr = [4,-2,2,-4]
输出:true
解释:可以用 [-2,-4] 和 [2,4] 这两组组成 [-2,-4,2,4] 或是 [2,4,-2,-4]

整理题意: 题目本质上是问能否将给定数组中的元素进行两两配对,使得每对元素中一个数是另一个数的两倍。

思路

方法一:排序 + 队列

排序是为了能够从小到大依次寻找元素的匹配对象,保证了不重不漏;队列是用来存放还未配对成功的元素。

首先对数组元素进行排序,然后遍历数组元素,对于当前遍历到的arr[i],当队列为空时说明没有元素可以配对,直接将arr[i]压入队列;当队列不为空时,此时队列头元素为等待配对的元素,如果当前arr[i]与队列头元素配对成功,将队列头元素弹出,并且不压入arr[i];如果当前arr[i]与队列头元素配对不成功,则将arr[i]压入队列。

这里考虑除队列头元素外,在队列中的其他元素会不会存在配对成功的情况,这种情况怎么处理。假设队列当前情况为[2, 6, 12]2612都不能匹配,但是612能够匹配,因为数组排序后为有序数组,队列中的元素从小到大排列,如果在6之前无法找到与2匹配的元素,那么之后的元素都是大于6的元素就更不可能与2匹配了,所以排序后的数组只需要贪心的寻找最小元素的匹配元素即可。

最后判断队列是否为空,如果为空说明全部元素配对成功返回true,如果不为空则表示该数组不满足题意返回false

注意需要考虑特殊情况:

  1. 首先考虑特殊情况元素00只能和0匹配,如果数组中0的个数为奇数个,那么该数组必然不满足题目要求,所以返回false
  2. 其次考虑负数的情况(因为数据范围包含负数):对于负数我们只需要将负数从大到小排列即可。

方法二:排序 + 哈希表

这里还可以使用哈希表的方法来记录每个元素出现的次数,设cnt[i]为元素i在数组中出现的次数。 我们从小到大依次遍历数组(同样考虑0和负数的特殊情况),由于是从小到大遍历(负数从大到小),没有比当前元素arr[i]更小的数在等待匹配,因此只能与2 * arr[i]匹配。如果cnt[2 * arr[i]] < cnt[arr[i]]表示当前元素arr[i]不能够完全匹配,那么剩余的这部分arr[i]将无法匹配,即不满足题意返回false;如果cnt[2 * arr[i]] >= cnt[arr[i]]表示当前元素arr[i]能够完全匹配,此时只需要更新cnt[arr[i]]cnt[2 * arr[i]]的数值即可。

代码实现

方法一:排序 + 队列

class Solution {
public:
    bool canReorderDoubled(vector<int>& arr) {
        //排序
        sort(arr.begin(), arr.end());
        //正整数pro,负整数sub
        vector<int> pro, sub;
        pro.clear();
        sub.clear();
        int len = arr.size();
        int s0 = 0;
        //统计正整数、零和负整数
        for(int i = 0; i < len; i++){
            if(arr[i] > 0) pro.push_back(arr[i]);
            else if(arr[i] == 0) s0++;
            else sub.push_back(arr[i]);
        }
        //如果0的个数为奇数返回false
        if(s0 & 1) return false;
        //队列模拟匹配
        queue<int> que;
        while(que.size()) que.pop();
        len = pro.size();
        for(int i = 0; i < len; i++){
            if(que.empty() || (que.front() * 2 != pro[i])) que.push(pro[i]);
            else if(que.front() * 2 == pro[i]) que.pop();
        }
        if(!que.empty()) return false;
        //注意负数需要从大到小排序
        reverse(sub.begin(), sub.end());
        len = sub.size();
        for(int i = 0; i < len; i++){
            if(que.empty() || (que.front() * 2 != sub[i])) que.push(sub[i]);
            else if(que.front() * 2 == sub[i]) que.pop();
        }
        if(!que.empty()) return false;
        return true;
    }
};

方法二:排序 + 哈希表

class Solution {
public:
    bool canReorderDoubled(vector<int> &arr) {
        map<int, int> cnt;
        //统计元素个数
        for (int x : arr) {
            ++cnt[x];
        }
        //特殊情况:0
        if (cnt[0] % 2) {
            return false;
        }
        vector<int> vals;
        vals.reserve(cnt.size());
        for (auto &[x, _] : cnt) {
            vals.push_back(x);
        }
        //元素绝对值大小排序,这样就可以对负数一起处理了
        sort(vals.begin(), vals.end(), [](int a, int b) { return abs(a) < abs(b); });
        for (int x : vals) {
            if (cnt[2 * x] < cnt[x]) { // 无法找到足够的 2x 与 x 配对
                return false;
            }
            cnt[2 * x] -= cnt[x];
        }
        return true;
    }
};