1. 题目来源
链接:1711. 大餐计数
相关:2021年7月7日 力扣夏季每日一题,就不重复写了。
相关题目:[E哈希] lc1. 两数之和(哈希+二分+双指针)
2. 题目解析
本题和 [E哈希] lc1. 两数之和(哈希+二分+双指针) 几乎一模一样。只是这个两数之和是 2 k 2^k 2k,并且求所有的两数之和种类即可。没做出来确实应当好好反思!
一直 TLE
,心态直接爆炸 hh
一开始没想整理周赛题目,字迹很潦草,已经加急下单了类纸膜和阻尼笔尖套,一定会有所改善的!
思路:
- 纯哈希就可以做了。二分当然也可以。
- 枚举第二个数
ai
,再从a1~ai-1
中查找有多少个数能与ai
构成 2 的整次幂。 - 枚举所有数,再在哈希表查找
2^n - ai
在a1~ai-1
出现的次数即可。故总的时间复杂度就是 O ( 22 ∗ n ) O(22*n) O(22∗n)。 - 最后要将
ai
加入到哈希表中。和两数之和同样的优化,这样就可以唯一确定一对两数了,不会出现(1, 3), (3, 1)
的重复情况了。
坑点: unordered_map
即哈希表,不论对其赋值与否都会在哈希表中插入这个值,即 res += hash[t]
,若 t
查找不到则 res += 0
也不影响正确结果,但是这个无用的 t
会被插入到哈希表中,等于说每个数多了 20 倍的无效插入,最终导致哈希表中存在 220w
的数字,导致了一大片的 TLE
。真滴恶心。所以先进行判断,若这个 t
可以和当前枚举的 ai
构成 2 的整次幂,再进行 res += hash[t]
,防止无效值的进入。
- 时间复杂度: O ( 22 ∗ n ) O(22*n) O(22∗n)。
- 空间复杂度: O ( n ) O(n) O(n)
代码:
class Solution {
public:
int countPairs(vector<int>& a) {
unordered_map<int, int> hash;
int res = 0, mod = 1e9 + 7;
for (int i = 0; i < a.size(); i ++ ) {
for (int j = 0; j <= 21; j ++ ) {
int t = (1 << j) - a[i];
if (hash.count(t)) // 重点优化
res = (res + hash[t]) % mod;
}
hash[a[i]] ++ ;
}
return res;
}
};
上面的代码很精巧,其中的思想和 [E哈希] lc1. 两数之和(哈希+二分+双指针) 一样。
下面是 2021年7月7日 力扣夏季每日一题 写的代码,还是比较轻松写出来了,但出了几个 bug…需要记录:
- 没想到两数之和…属实反思!
- 中间忘记使用了
LL
,运算中间结果会爆int
要添加0ll, 1ll
这样的转化。 - 在针对不同数时,忘记去重!出
bug
后人为定义了大小才解决。 - C n 2 = n ∗ ( n − 1 ) 2 C_n^2= \frac {n*(n-1)} 2 Cn2=2n∗(n−1) 常用的组合公式要牢记。
- 不太需要预处理枚举 2 的次幂到数组中,在循环中直接枚举也行。
总的来看,实现这个要求并不难,但是要想写出简洁优雅的代码,还需继续努力啊!
class Solution {
public:
int countPairs(vector<int>& deliciousness) {
typedef long long LL;
const int MOD = 1e9+7;
vector<int> q;
int cnt = 1;
for (int i = 0; i < 22; i ++ ) q.push_back(cnt), cnt *= 2;
unordered_map<int, int> hmp; // [数字,次数]
for (auto &e : deliciousness) hmp[e] ++ ;
LL res = 0;
for (auto [k, v] : hmp) {
for (auto &e : q) {
if (hmp.count(e - k)) {
if (k == e - k) {
int n = v;
res += (n * (n - 1ll) / 2) % MOD; // 注意中间转化为 ll
} else {
if (k > e - k) continue; // 定义不同数字的大小关系,(1, 3) (3, 1) 只会出现一次!
res += (1ll * hmp[k] * hmp[e - k]) % MOD; // 注意中间转化为 ll
}
}
}
}
return res;
}
};