一个整数数组 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;
}
};