一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
- 题目链接:954. 二倍数对数组
题意
题目描述: 给定一个长度为偶数的整数数组 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]
整理题意: 题目本质上是问能否将给定数组中的元素进行两两配对,使得每对元素中一个数是另一个数的两倍。
思路
方法一:排序 + 队列
排序是为了能够从小到大依次寻找元素的匹配对象,保证了不重不漏;队列是用来存放还未配对成功的元素。
首先对数组元素进行排序,然后遍历数组元素,对于当前遍历到的arr[i],当队列为空时说明没有元素可以配对,直接将arr[i]压入队列;当队列不为空时,此时队列头元素为等待配对的元素,如果当前arr[i]与队列头元素配对成功,将队列头元素弹出,并且不压入arr[i];如果当前arr[i]与队列头元素配对不成功,则将arr[i]压入队列。
这里考虑除队列头元素外,在队列中的其他元素会不会存在配对成功的情况,这种情况怎么处理。假设队列当前情况为
[2, 6, 12],2与6和12都不能匹配,但是6和12能够匹配,因为数组排序后为有序数组,队列中的元素从小到大排列,如果在6之前无法找到与2匹配的元素,那么之后的元素都是大于6的元素就更不可能与2匹配了,所以排序后的数组只需要贪心的寻找最小元素的匹配元素即可。
最后判断队列是否为空,如果为空说明全部元素配对成功返回true,如果不为空则表示该数组不满足题意返回false
注意需要考虑特殊情况:
- 首先考虑特殊情况元素
0:0只能和0匹配,如果数组中0的个数为奇数个,那么该数组必然不满足题目要求,所以返回false。 - 其次考虑负数的情况(因为数据范围包含负数):对于负数我们只需要将负数从大到小排列即可。
方法二:排序 + 哈希表
这里还可以使用哈希表的方法来记录每个元素出现的次数,设cnt[i]为元素i在数组中出现的次数。
我们从小到大依次遍历数组(同样考虑0和负数的特殊情况),由于是从小到大遍历(负数从大到小),没有比当前元素arr[i]更小的数在等待匹配,因此只能与2 * arr[i]匹配。如果cnt[2 * arr[i]] < cnt[arr[i]]表示当前元素arr[i]不能够完全匹配,那么剩余的这部分arr[i]将无法匹配,即不满足题意返回false;如果cnt[2 * arr[i]] >= cnt[arr[i]]表示当前元素arr[i]能够完全匹配,此时只需要更新cnt[arr[i]]和cnt[2 * arr[i]]的数值即可。
代码实现
方法一:排序 + 队列
class Solution {
public:
bool canReorderDoubled(vector<int>& arr) {
//排序
sort(arr.begin(), arr.end());
//正整数pro,负整数sub
vector<int> pro, sub;
pro.clear();
sub.clear();
int len = arr.size();
int s0 = 0;
//统计正整数、零和负整数
for(int i = 0; i < len; i++){
if(arr[i] > 0) pro.push_back(arr[i]);
else if(arr[i] == 0) s0++;
else sub.push_back(arr[i]);
}
//如果0的个数为奇数返回false
if(s0 & 1) return false;
//队列模拟匹配
queue<int> que;
while(que.size()) que.pop();
len = pro.size();
for(int i = 0; i < len; i++){
if(que.empty() || (que.front() * 2 != pro[i])) que.push(pro[i]);
else if(que.front() * 2 == pro[i]) que.pop();
}
if(!que.empty()) return false;
//注意负数需要从大到小排序
reverse(sub.begin(), sub.end());
len = sub.size();
for(int i = 0; i < len; i++){
if(que.empty() || (que.front() * 2 != sub[i])) que.push(sub[i]);
else if(que.front() * 2 == sub[i]) que.pop();
}
if(!que.empty()) return false;
return true;
}
};
方法二:排序 + 哈希表
class Solution {
public:
bool canReorderDoubled(vector<int> &arr) {
map<int, int> cnt;
//统计元素个数
for (int x : arr) {
++cnt[x];
}
//特殊情况:0
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;
}
};