0954-二倍数对数组

Raphael Liu Lv10

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

示例 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]

提示:

  • 0 <= arr.length <= 3 * 104
  • arr.length 是偶数
  • -105 <= arr[i] <= 105

方法一:哈希表 + 排序

设 arr 的长度为 n,题目本质上是问 arr 能否分成 \dfrac{n}{2 对元素,每对元素中一个数是另一个数的两倍。

设 cnt}[x] 表示 arr 中 x 的个数。

对于 arr 中的 0,它只能与 0 匹配。如果 cnt}[0] 是奇数,那么必然无法满足题目要求。

去掉 arr 中的 0。设 x 为 arr 中绝对值最小的元素,由于没有绝对值比 x 更小的数,因此 x 只能与 2x 匹配。如果此时 cnt}[2x] < \textit{cnt}[x],那么会有部分 x 无法找到它的另一半,即无法满足题目要求;否则将所有 x 和 cnt}[x] 个 2x 从 arr 中去掉,继续判断剩余元素是否满足题目要求。不断重复此操作,如果某个时刻 arr 为空,则说明 arr 可以满足题目要求。

代码实现时,我们可以用一个哈希表来统计 cnt,并将其键值按绝对值从小到大排序,然后模拟上述操作,去掉元素的操作可以改为从 cnt 中减去对应值。

[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
class Solution:
def canReorderDoubled(self, arr: List[int]) -> bool:
cnt = Counter(arr)
if cnt[0] % 2:
return False
for x in sorted(cnt, key=abs):
if cnt[2 * x] < cnt[x]: # 无法找到足够的 2x 与 x 配对
return False
cnt[2 * x] -= cnt[x]
return True
[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
bool canReorderDoubled(vector<int> &arr) {
unordered_map<int, int> cnt;
for (int x : arr) {
++cnt[x];
}
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;
}
};
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public boolean canReorderDoubled(int[] arr) {
Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
for (int x : arr) {
cnt.put(x, cnt.getOrDefault(x, 0) + 1);
}
if (cnt.getOrDefault(0, 0) % 2 != 0) {
return false;
}

List<Integer> vals = new ArrayList<Integer>();
for (int x : cnt.keySet()) {
vals.add(x);
}
Collections.sort(vals, (a, b) -> Math.abs(a) - Math.abs(b));

for (int x : vals) {
if (cnt.getOrDefault(2 * x, 0) < cnt.get(x)) { // 无法找到足够的 2x 与 x 配对
return false;
}
cnt.put(2 * x, cnt.getOrDefault(2 * x, 0) - cnt.get(x));
}
return true;
}
}
[sol1-C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Solution {
public bool CanReorderDoubled(int[] arr) {
Dictionary<int, int> cnt = new Dictionary<int, int>();
foreach (int x in arr) {
if (!cnt.ContainsKey(x)) {
cnt.Add(x, 1);
} else {
++cnt[x];
}
}
if (cnt.ContainsKey(0) && cnt[0] % 2 != 0) {
return false;
}

List<int> vals = new List<int>();
foreach (int x in cnt.Keys) {
vals.Add(x);
}
vals.Sort((a, b) => Math.Abs(a) - Math.Abs(b));

foreach (int x in vals) {
if ((cnt.ContainsKey(2 * x) ? cnt[2 * x] : 0) < cnt[x]) { // 无法找到足够的 2x 与 x 配对
return false;
}
if (cnt.ContainsKey(2 * x)) {
cnt[2 * x] -= cnt[x];
} else {
cnt.Add(2 * x, -cnt[x]);
}
}
return true;
}
}
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func canReorderDoubled(arr []int) bool {
cnt := make(map[int]int, len(arr))
for _, x := range arr {
cnt[x]++
}
if cnt[0]%2 == 1 {
return false
}

vals := make([]int, 0, len(cnt))
for x := range cnt {
vals = append(vals, x)
}
sort.Slice(vals, func(i, j int) bool { return abs(vals[i]) < abs(vals[j]) })

for _, x := range vals {
if cnt[2*x] < cnt[x] { // 无法找到足够的 2x 与 x 配对
return false
}
cnt[2*x] -= cnt[x]
}
return true
}

func abs(x int) int {
if x < 0 {
return -x
}
return x
}
[sol1-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
typedef struct {
int key;
int val;
UT_hash_handle hh;
} HashItem;

static int cmp(const int * pa, const int * pb) {
return abs(*pa) - abs(*pb);
}

bool canReorderDoubled(int* arr, int arrSize){
HashItem * cnt = NULL;
HashItem * pEntry = NULL;
for (int i = 0; i < arrSize; i++) {
pEntry = NULL;
HASH_FIND_INT(cnt, &arr[i], pEntry);
if (NULL == pEntry) {
pEntry = (HashItem *)malloc(sizeof(HashItem));
pEntry->key = arr[i];
pEntry->val = 1;
HASH_ADD_INT(cnt, key, pEntry);
} else {
pEntry->val++;
}
}
pEntry = NULL;
int key = 0;
HASH_FIND_INT(cnt, &key, pEntry);
if (pEntry != NULL && pEntry->val % 2) {
return false;
}

int cntSize = HASH_COUNT(cnt);
int * vals = (int *)malloc(sizeof(int) * cntSize);
int pos = 0;
HashItem * tmp;
HASH_ITER(hh, cnt, pEntry, tmp) {
vals[pos++] = pEntry->key;
}
qsort(vals, cntSize, sizeof(int), cmp);
for (int i = 0; i < cntSize; i++) {
int c1 = 0, c2 = 0;
int key = vals[i];
HashItem * pEntry1 = NULL;
HashItem * pEntry2 = NULL;
HASH_FIND_INT(cnt, &key, pEntry1);
if (pEntry1) {
c1 = pEntry1->val;
}
key = 2 * vals[i];
HASH_FIND_INT(cnt, &key, pEntry2);
if (pEntry2) {
c2 = pEntry2->val;
}
if (c2 < c1) {
return false;
}
if (pEntry2) {
pEntry2->val -= c1;
}
}
HASH_ITER(hh, cnt, pEntry, tmp) {
HASH_DEL(cnt, pEntry);
free(pEntry);
}
free(vals);
return true;
}
[sol1-JavaScript]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var canReorderDoubled = function(arr) {
const cnt = new Map();
for (const x of arr) {
cnt.set(x, (cnt.get(x) || 0) + 1);
}
if ((cnt.get(0) || 0) % 2 !== 0) {
return false;
}

const vals = [];
for (const x of cnt.keys()) {
vals.push(x);
}
vals.sort((a, b) => Math.abs(a) - Math.abs(b));

for (const x of vals) {
if ((cnt.get(2 * x) || 0) < cnt.get(x)) { // 无法找到足够的 2x 与 x 配对
return false;
}
cnt.set(2 * x, (cnt.get(2 * x) || 0) - cnt.get(x));
}
return true;
};

复杂度分析

  • 时间复杂度:O(n\log n),其中 n 是数组 arr 的长度。最坏情况下哈希表中有 n 个元素,对其排序需要 O(n\log n) 的时间。

  • 空间复杂度:O(n)。最坏情况下哈希表中有 n 个元素,需要 O(n) 的空间。

 Comments
On this page
0954-二倍数对数组