Leetcode 2007 Find Original Array From Doubled Array

规则:一个原始数组,其中的每个数都乘 2 ,并加入到数组中,打乱顺序。

给一个数组,判断这个数组是不是乘 2 的数组。

这个题唯一有点绕的情况是:...2,4,8...

这个时候,要判断 4 是 2 的二倍,还是 8 的原始数据?我们不能继续搜索 1 或者 16 ,因为这样循环搜索,一层套一层很麻烦。

继续思考,如果这个数组是个乘 2 数组,那么当前最大的数,一定是原始数据乘 2 来的(最小的数,一定是原始数据,要乘 2)。

接下来看数据规模,$1 <= changed.length <= 10^5, 0 <= changed[i] <= 10^5$,这个级别的数据规模,$\mathcal{O}(n)$ 以上的算法大概是行不通的,当然我没有试过。同时,因为数组长度较小,数据密集,可以使用数组作为哈希表,在 CLRS 中这种哈希表被称为 direct-address table。

将数组中的数据记录到哈希表中,从最大的数开始。如果是奇数,那么这一定不是乘 2 数组,返回空数组。否则除以 2 得到原始数据,将原始数据加入到结果中,同时哈希表中两个数的出现次数减 1 ,避免重复计算。如果某一个数在哈希表中的值小于 0 ,说明这个数的 2 倍的出现次数,多于这个数的出现次数,这显然不是一个乘 2 数组,返回空数组。

使用以上算法处理更大的半个数组,此时哈希表中所有数的出现次数都应该是0,如果有数据不是0,那么就不是一个乘 2 数组。

class Solution {
 public:
  vector<int> findOriginalArray(vector<int>& changed) {
    const int n = changed.size();
    if (n & 1) return {};
    int nums[100001];
    memset(nums, 0, sizeof nums);
    int maxnumber = -1;
    for (const int a : changed) {
      maxnumber = max(maxnumber, a);
      ++nums[a];
    }
    int idx = 0, i = maxnumber, stop = n >> 1;
    vector<int> res;
    while (idx < stop) {
      if (nums[i] < 0) return {};
      if (nums[i] == 0) {
        --i;
        continue;
      }
      if (i & 1) return {};
      const int ori = i >> 1;
      res.emplace_back(ori);
      --nums[ori];
      --nums[i];
      ++idx;
    }
    for (int i = 0; i < maxnumber; ++i)
      if (nums[i] != 0) return {};
    return res;
  }
};

#直达链接

LeetCode 2007. Find Original Array From Doubled Array