每日一题:从双倍数组中还原原数组

文章描述了一种问题,给定一个经过随机打乱的双倍数组,目标是找到原始数组,其中每个元素是原始数组中对应元素乘以2的结果。提供了两种方法,一种是通过排序和队列操作,另一种是不排序利用unordered_map进行查找。
摘要由CSDN通过智能技术生成

一个整数数组 original 可以转变成一个 双倍 数组 changed ,转变方式为将 original 中每个元素 值乘以 2 加入数组中,然后将所有元素 随机打乱 。

给你一个数组 changed ,如果 change 是 双倍 数组,那么请你返回 original数组,否则请返回空数组。original 的元素可以以 任意 顺序返回。

示例 1:

输入:changed = [1,3,4,2,6,8]
输出:[1,3,4]
解释:一个可能的 original 数组为 [1,3,4] :
- 将 1 乘以 2 ,得到 1 * 2 = 2 。
- 将 3 乘以 2 ,得到 3 * 2 = 6 。
- 将 4 乘以 2 ,得到 4 * 2 = 8 。
其他可能的原数组方案为 [4,3,1] 或者 [3,1,4] 。

示例 2:

输入:changed = [6,3,0,1]
输出:[]
解释:changed 不是一个双倍数组。

示例 3:

输入:changed = [1]
输出:[]
解释:changed 不是一个双倍数组。

提示:

  • 1 <= changed.length <= 10^5
  • 0 <= changed[i] <= 10^5

方法一:排序+队列

这里提出一个问题,为什么要排序?

首先,这道题是可以通过不排序算法实现的,在方法二中所示。

如果不排序[1,3,4,(2),6,8],我们无法在遍历到3的时候确认1的二倍是否存在于数组中。而排序后[1,(2),3,4,6,8],如果遍历到3的时候,没有遇到过1的二倍,那就能确认1的二倍不存在于数组中。

使用队列维护双倍的数字。

遍历 changed 数组中的每个数字:

  • 如果队列不为空且当前数字等于队列首部,则将队列首部除以 2 并将其添加到 original 数组中,然后弹出队列首部。然后进入下一次循环。因为弹出操作相当于将当前值作为了之前某个值的二倍。
  • 如果队列不为空且当前数字大于队列首部,则返回空数组,说明前面某个数的二倍不存在。
    例如[1,3,4,6,8],遍历到3的时候没有遇到过2,所以1的二倍不存在。
  • 将当前数字的双倍压入队列。(这步是基础操作,但是当前面弹出队首时,说明当前这次遍历是用当前值,作为了之前一个值的二倍,所以就不压入这次遍历的数。)

遍历后如果队列为空,说明每个数都找到了对应的二倍,那么原数组就是双倍数组。

该算法的时间复杂度为 O(n log n),其中 n 是输入数组 changed 的长度。

class Solution {
public:
    vector<int> findOriginalArray(vector<int>& changed) {
        sort(changed.begin(),changed.end());
        vector<int> original;
        queue<int> queue;
        for(int nums : changed){
            if(!queue.empty()){
                if(nums == queue.front()){
                    original.emplace_back(queue.front()/2);
                    queue.pop();
                    continue;
                }else if(nums > queue.front()){
                    return {};
                }
            }
            queue.push(nums * 2);
         }
         return queue.empty() ? original : vector<int>();

    }
};

方法二:不排序,直接找某数的二倍

不排序的主要难点出现在这种情况下:[ x,x,1,4,2,x,x,x]。

这里的2是应该和1配对还是和4配对?

所以我们可以考虑一次性将1,2,4,8,16这种互成二倍的数全处理出来。

0需要单独处理,偶数个0是符合双倍数组的,奇数个不符合。

int count0 = map[0];
if(count0 % 2) return {};
for(int i = 0;i < count0 / 2;i++){
    original.emplace_back(0);
}
map.erase(0);

在对原数组的遍历中,我们考虑直接找到上述互成二倍的数的最小值:

if(key % 2 == 0 && map.contains(key/2)){
   continue;
}

然后当他的二倍存在时,一直翻倍执行:

while(map.contains(key)){
    if(map[key] > map[key * 2]){
        return {};
    } 
    while(map[key] != 0){
        map[key]--;
        map[key*2]--;
        original.emplace_back(key);
    }
    if(map[key*2] > 0){
        key = 2 * key;
    }else{
        key = 4 * key;
    }
}

如果当前值的个数大于了二倍值的个数,说明不是双倍数组,返回空。

否则把相应个数的key加入结果。

  • 如果二倍值个数和当前值个数同时为0,说明两者恰好消耗完,就将key乘4
    以1,2,4,8为例,1,2恰好消耗完,这个序列还没完,所以要从4开始,4是2的二倍,2又是1的二倍,所以乘4。
  • 如果出现二倍值多,那么可能是二倍值可以作为其他二倍值的单值,所以将key翻倍。
    以1,2,2,4为例,1,2消耗后还剩2,4。

完整代码:

class Solution {
public:
    vector<int> findOriginalArray(vector<int>& changed) {
        int n = changed.size();
        if(n % 2) return {};

        vector<int> original;
        unordered_map<int,int> map;
        
        for(int num:changed){
            map[num]++;
        }
        
        int count0 = map[0];
        if(count0 % 2) return {};
        for(int i = 0;i < count0 / 2;i++){
            original.emplace_back(0);
        }
        map.erase(0);
        
        for(auto num:map){
            int key = num.first;
            if(key % 2 == 0 && map.contains(key/2)){
                continue;
            }
            while(map.contains(key)){
                if(map[key] > map[key * 2]){
                    return {};
                } 
                while(map[key] != 0){
                    map[key]--;
                    map[key*2]--;
                    original.emplace_back(key);
                }
                if(map[key*2] > 0){
                    key = 2 * key;
                }else{
                    key = 4 * key;
                }
            }

        }
        
        return original;
    }
};
  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值