问题:二倍数对数组
给定一个长度为偶数的整数数组 A
,只有对 A
进行重组后可以满足 “对于每个 0 <= i < len(A) / 2
,都有 A[2 * i + 1] = 2 * A[2 * i]
” 时,返回 true
;否则,返回 false
。
示例 1:
输入:[3,1,3,6] 输出:false
示例 2:
输入:[2,1,2,6] 输出:false
示例 3:
输入:[4,-2,2,-4] 输出:true 解释:我们可以用 [-2,-4] 和 [2,4] 这两组组成 [-2,-4,2,4] 或是 [2,4,-2,-4]
示例 4:
输入:[1,2,4,16,8,4] 输出:false
提示:
0 <= A.length <= 30000
A.length
为偶数-100000 <= A[i] <= 100000
链接:https://leetcode-cn.com/contest/weekly-contest-114/problems/array-of-doubled-pairs/
分析:
题目要求看给出的数组能否通过重新排序,使的所有的偶数位置值是前一个的奇数值的二倍。
那么可以通过将数据分为负数和非负数两组,分组的原因是方便处理,对于非负数,直接递增排序正向处理,对于负数,排序后需要反向处理。关键看绝对值,处理逻辑是一样的。
以非负数为例:
排序后最小的值肯定是放在奇数位,那么在后面查找他的2倍是否存在,如果不存在可以直接结束返回false
如果存在,则需要将该数字移除,因为每个数字只能用到一次。
比如给出的例子中第一组排序后是 1 3 3 6 ,1 的2倍2不存在,false
第二组: 1 2 2 6,1的二倍2存在,移除2,得到1 2 6,处理下一个数据2,2的2倍4不存在,false
第三组分为-4 -2和2 4 两组,都对应存在,true
第四组,排序后 1 2 4 4 8 16
1-> 2 存在,移除2数组变为1 4 4 8 16
处理下一个4,4->8 存在,移除8得到 1 4 4 16
处理下一个4,8不存在,false
AC Code:
1 class Solution { 2 public: 3 bool canReorderDoubled(vector<int>& A) { 4 bool ret = true; 5 //首先正负分开 6 vector<int> Pvec; 7 vector<int> Nvec; 8 for (unsigned int i = 0; i < A.size(); i++) 9 { 10 if (A[i] < 0) 11 { 12 Nvec.emplace_back(-1*A[i]); 13 //Pvec.emplace_back(-1*A[i]); 14 } 15 else 16 { 17 Pvec.emplace_back(A[i]); 18 } 19 } 20 if (Pvec.size() % 2 != 0) 21 { 22 return false; 23 } 24 sort(Pvec.begin(), Pvec.end()); 25 int limit = Pvec.size() / 2; 26 for (unsigned int i = 0; i < limit;i++) 27 { 28 if (find(Pvec.begin() + i + 1, Pvec.end(), 2 * Pvec[i]) != Pvec.end()) 29 { 30 Pvec.erase(find(Pvec.begin() + i + 1, Pvec.end(), 2 * Pvec[i])); 31 } 32 else 33 { 34 return false; 35 } 36 } 37 sort(Nvec.begin(), Nvec.end()); 38 limit = Nvec.size() / 2; 39 for (unsigned int i = 0; i < limit;i ++) 40 { 41 if (find(Nvec.begin() + i + 1, Nvec.end(), 2 * Nvec[i]) != Nvec.end()) 42 { 43 Nvec.erase(find(Nvec.begin() + i + 1, Nvec.end(), 2 * Nvec[i])); 44 } 45 else 46 { 47 return false; 48 } 49 } 50 return true; 51 } 52 };
其他:
1.sort 默认升序,对于负数可以通过乘以-1转换为整数,之前看别人的一个解答学到的,可以省去编写自定义排序函数。
2.vector中查看某个元素是否存在,通过find(.begin(),.end(),value),如果没找到,返回end(),如果找到了,返回对应的迭代器值(iterator)
3,vector 中删除元素通过索引,如果本身就是iterator,可以直接删除erase(iterator),如果是个index 数字,可以通过erase(.begin()+index)
4,第一code:
1 #pragma GCC optimize("O3", "unroll-loops") 2 // God Help me !! 3 #include <bits/stdc++.h> 4 using namespace std; 5 // #define watch(x) cout << (#x) << " is " << (x) << endl 6 7 #define FILES freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout) 8 #define FAST ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0) 9 #define FIXED cout << fixed << setprecision(20) 10 #define RANDOM srand(time(nullptr)) 11 // #define int long long 12 #define MOD 1000000007 13 #define sz(a) (ll)a.size() 14 #define pll pair<ll,ll> 15 #define rep(i,a,b) for(int i=(int)a;i<(int)b;i++) 16 #define sep(i,a,b) for(int i=(int)a;i>=(int)b;i--) 17 #define mll map<int,int> 18 #define vl vector<int> 19 #define pb push_back 20 #define lb lower_bound 21 #define ub upper_bound 22 #define all(a) a.begin(),a.end() 23 #define F first 24 #define S second 25 #define endl "\n" 26 #define MAXN6 3000005 27 #define MAXN3 3005 28 #define MAXN5 300005 29 30 #define watch(...) ZZ(#__VA_ARGS__, __VA_ARGS__) 31 template <typename Arg1> void ZZ(const char* name, Arg1&& arg1){std::cerr << name << " = " << arg1 << endl;} 32 template <typename Arg1, typename... Args>void ZZ(const char* names, Arg1&& arg1, Args&&... args) 33 { 34 const char* comma = strchr(names + 1, ','); 35 std::cerr.write(names, comma - names) << " = " << arg1; 36 ZZ(comma, args...); 37 } 38 39 40 class Solution { 41 public: 42 bool canReorderDoubled(vector<int>& A) { 43 int positive[100001], negative[100001]; 44 memset(positive, 0, sizeof(positive)); 45 memset(negative, 0, sizeof(negative)); 46 for (int x: A) { 47 if (x >= 0) positive[x]++; 48 else negative[-x]++; 49 } 50 if (positive[0] % 2 != 0) return false; 51 for (int i = 1; i <= 50000; i++) { 52 if (positive[i] != 0) { 53 if (positive[i*2] < positive[i]) return false; 54 positive[i*2] -= positive[i]; 55 } 56 } 57 for (int i = 1; i <= 50000; i++) { 58 if (negative[i] != 0) { 59 if (negative[i*2] < negative[i]) return false; 60 negative[i*2] -= negative[i]; 61 } 62 } 63 return true; 64 } 65 };
看到第一code 用来桶的那种方式 ,通过申请足够大的数组,下标就是值,删除通过--实现,而vector中删除需要移动大量数据,随意AC虽然过了,时间800+
完赛后QQ群里面有提到这个题 其实核心思路就是分组,排序, 查找元素是否存在以及删除。
那么可以通过map来做,map存储出现的数字以及个数 ,key是数字,value是数字个数,如果数字被用过了,value--,通过这种方式模拟删除。
这样也能AC,而且时间从800+降到96,虽然时间和很多因素有关, 比如服务器状态、测试数据等,但是下降一个数量级还是很能说明问题的。
map实现:
1 class Solution { 2 public: 3 bool canReorderDoubled(vector<int>& A) { 4 bool ret = true; 5 map<int, int> Ndata; 6 map<int, int> Pdata; 7 int Nnum = 0; 8 for (int i : A) 9 { 10 if (i >= 0) 11 { 12 if (Pdata.find(i) != Pdata.end()) 13 { 14 Pdata[i]++; 15 } 16 else 17 { 18 Pdata[i] = 1; 19 } 20 } 21 else 22 { 23 Nnum++; 24 if (Ndata.find(-1*i) != Ndata.end()) 25 { 26 Ndata[-1 * i]++; 27 } 28 else 29 { 30 Ndata[-1 * i] = 1; 31 } 32 } 33 } 34 35 if (Nnum % 2!= 0) 36 { 37 return false; 38 } 39 //处理非负数 40 map<int, int>::iterator it = Pdata.begin(); 41 int Limit = (A.size()-Nnum) / 2; 42 for (unsigned int i = 0; i < Limit; ) 43 { 44 if ((*it).second == 0) 45 { 46 it++; 47 continue; 48 } 49 int tmpdata = (*it).first; 50 (*it).second--; 51 map<int, int>::iterator tmpit; 52 tmpit = Pdata.find(2 * (*it).first); 53 if (tmpit == Pdata.end() || (*tmpit).second==0) 54 { 55 return false; 56 } 57 else 58 { 59 (*tmpit).second--; 60 } 61 if ((*it).second == 0) 62 { 63 it++; 64 } 65 i++; 66 } 67 68 it = Ndata.begin(); 69 Limit = Nnum / 2; 70 for (unsigned int i = 0; i < Limit;) 71 { 72 if ((*it).second == 0) 73 { 74 it++; 75 continue; 76 } 77 int tmpdata = (*it).first; 78 (*it).second--; 79 map<int, int>::iterator tmpit; 80 tmpit = Ndata.find(2 * (*it).first); 81 if (tmpit == Ndata.end() || (*tmpit).second == 0) 82 { 83 return false; 84 } 85 else 86 { 87 (*tmpit).second--; 88 } 89 if ((*it).second == 0) 90 { 91 it++; 92 } 93 i++; 94 } 95 96 return ret; 97 } 98 };
一段时间不用map,手生了。
a.1 map个数是值的个数,这一个题中直接用map的size得不到正负数个数,需要value累加或者通过额外的一个变量记录
a.2 如果因为个数为零跳过,那么index i不应增加,所以i的改变时机要放到后面合适的地方,而不是for内
a.3 如果数据涉及到底层的移动变换等,最好能够避免,是在避免不了也尽量集中处理 ,记得之前看到一本书提到C++的一些高级特性的时候有提到这个,一时想不起来的,书读百遍,有时间还是得在读一遍。
a.4使用的C++,感觉自己用的这套测试测试函数挺不错的,再也不用通过OJ debug了,而且能够回归测试,避免修改过后顾头不顾尾,记下来,有需要的可以拿去。
比如对于954的测试:
1 void test954() 2 { 3 Solution954 S; 4 vector<int> A; 5 bool ans; 6 bool tans; 7 8 9 A = vector<int>{ 1,2,4,16,8,4 }; 10 tans = false; 11 ans = S.canReorderDoubled(A); 12 cout << "ans:" << ans << " tans:" << tans << " ### " << (ans == tans) << endl; 13 14 A = vector<int>{ 2,1,2,6 }; 15 tans = false; 16 ans = S.canReorderDoubled(A); 17 cout << "ans:" << ans << " tans:" << tans << " ### " << (ans == tans) << endl; 18 19 20 A = vector<int>{ 2,1,2,1,1,1,2,2}; 21 tans = true; 22 ans = S.canReorderDoubled(A); 23 cout << "ans:" << ans << " tans:" << tans << " ### " << (ans == tans) << endl; 24 25 26 27 A = vector<int>{ 3,1,3,6 }; 28 tans = false; 29 ans = S.canReorderDoubled(A); 30 cout << "ans:" << ans << " tans:" << tans << " ### " << (ans == tans) << endl; 31 32 33 34 A = vector<int>{ 4,-2,2,-4 }; 35 tans = true; 36 ans = S.canReorderDoubled(A); 37 cout << "ans:" << ans << " tans:" << tans << " ### " << (ans == tans) << endl; 38 39 A = vector<int>{ 1,2,4,16,8,4 }; 40 tans = false; 41 ans = S.canReorderDoubled(A); 42 cout << "ans:" << ans << " tans:" << tans << " ### " << (ans == tans) << endl; 43 }
如果有新的测试数据只需要加进去就行。如果用来测试新的类,只需要根据需要设定数据类型,返回值类型,然后在具体的测试用例里面设置就可以了。如果输出目标答案tans和时机答案ans一致,则测试通过。如果是一些符合数据,需要自己编写判断是否相等的函数。