LeetCode 954. 二倍数对数组
题目描述

题意分析
判断能否把偶数个元素全部分成若干对,每对都满足第二个数等于第一个数的两倍。每个元素只能用一次;负数、0 和重复值都可能出现。只需判断是否存在这样的分组,不必输出具体排列。
解法:排序 + 贪心
核心思路
[!blue]
先用频次表记录每个值还剩多少个,再把不同的值按绝对值从小到大处理。考虑当前剩余的非零值
x:如果它作为某对的两倍数,就需要一个绝对值更小的x / 2;但所有更小绝对值的元素已经处理完,所以剩下的x只能作为原数,与2x配对。设
x当前还剩cnt个,那么至少要有cnt个未使用的2x。不足就无解;足够就从2x的频次中扣去cnt,再把x清零。这个选择是剩余元素中被迫做出的配对,不会排除其他可行方案。每次都维持频次代表“尚未使用元素”的含义,全部处理完成即配对成功。绝对值顺序同时覆盖正负数:非零原数的绝对值一定小于它的两倍数。若直接按普通数值升序处理,负数的两倍数会先被处理,顺序反而颠倒。绝对值相同的正负数互不配对,各自需要同符号的两倍数,所以它们之间的处理顺序无关紧要。
0 是唯一的特殊情况,因为它的两倍仍是自己,不能按两个不同键扣减。每对必须消耗两个 0,因此零的频次为偶数才合法;检查后直接清零。已经被更小绝对值元素用完的值,其剩余频次为 0,跳过即可。
解题步骤
- 统计各值频次,将所有不同值作为待处理键。
- 按绝对值升序排列这些键。
- 读取当前值的剩余频次,为 0 则跳过;当前值为 0 时检查频次是否为偶数。
- 对非零值,若两倍数剩余频次不足,返回
false;否则批量扣除配对所需数量,并清空当前值。- 所有键处理完后返回
true。
代码实现
class Solution {
public boolean canReorderDoubled(int[] arr) {
Map<Integer, Integer> count = new HashMap<>();
for (int x : arr) {
count.merge(x, 1, Integer::sum);
}
// 按绝对值升序,先处理配对中绝对值较小的那一半。
Integer[] keys = count.keySet().toArray(new Integer[0]);
Arrays.sort(keys, Comparator.comparingInt(Math::abs));
for (int x : keys) {
int cnt = count.getOrDefault(x, 0);
if (cnt == 0) {
continue;
}
// 0 只能和 0 配,个数必须成双。
if (x == 0) {
if ((cnt & 1) == 1) {
return false;
}
count.put(x, 0);
continue;
}
int y = x * 2;
if (count.getOrDefault(y, 0) < cnt) {
return false;
}
count.put(y, count.get(y) - cnt);
count.put(x, 0);
}
return true;
}
}
import "sort"
func canReorderDoubled(arr []int) bool {
count := make(map[int]int)
for _, x := range arr {
count[x]++
}
keys := make([]int, 0, len(count))
for k := range count {
keys = append(keys, k)
}
// 按绝对值升序,先处理配对中绝对值较小的那一半。
sort.Slice(keys, func(i, j int) bool {
return abs954(keys[i]) < abs954(keys[j])
})
for _, x := range keys {
cnt := count[x]
if cnt == 0 {
continue
}
// 0 只能和 0 配,个数必须成双。
if x == 0 {
if cnt%2 != 0 {
return false
}
count[x] = 0
continue
}
y := x * 2
if count[y] < cnt {
return false
}
count[y] -= cnt
count[x] = 0
}
return true
}
func abs954(x int) int {
if x < 0 {
return -x
}
return x
}
复杂度分析
- 时间复杂度:期望 $O(n+k\log(k+1))$,k 为不同值数量。
- 空间复杂度:$O(k+1)$,k 为不同值数量;排序的是键,输入数组不改。
关键点总结
[!green]
- 按绝对值处理,使当前剩余非零元素无法再作为某个未处理数的两倍数,配对方向因此确定。
- 频次始终记录剩余数量,重复值按数量一起消耗,而不是只判断键是否存在。
- 零的配对需要两个相同元素,单独检查奇偶性。
易错点总结
[!yellow]
- 按普通升序找两倍数,会在负数部分错误地先处理绝对值更大的元素。
- 使用集合或只检查
2x是否存在,无法保证有足够的副本完成全部配对。- 处理当前键时必须读取剩余频次,不能沿用它在之前配对前的原始数量。
- 非零值配对后,既要扣减两倍数的供应量,也要清空已经使用的当前值。
- 将 0 当作一般非零值处理,可能错误接受奇数个 0。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 2007. 从双倍数组中还原原数组 | 中等 | 同样按频次配对x与2x,原题恢复非负原数组,本题允许负值并只判断能否全部配对。 |
| 1679. K 和数对的最大数目 | 中等 | 同样配对后消耗频次,但本题互补关系是倍数,需要按绝对值顺序先处理小值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!