1711. 大餐计数
这道题简单分析之后可以换一种说法就是“对数组中的不同数对进行两两组合,然后对组合的和为2的幂的组合进行计数”。
对于两两组合方法第一次的思路也比较简单,就是使用双重循环进行遍历组合,遍历的过程中对其求和判断是否是2的幂。
在这种思路下判断一个数是否是2的幂,采用的是位运算方法编写的代码。这种方法下的时间复杂度为。
数组双重循环
class Solution {
public int countPairs(int[] deliciousness) {
int cnt = 0;
int length = deliciousness.length;
for (int i = 0; i < length; i++) {
for (int j = i + 1; j < length; j++) {
int cur = deliciousness[i] + deliciousness[j];
if (isPowerOfTwo(cur)) {
++cnt;
}
}
}
return cnt % 1000000007;
}
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
}
然而上面编写的代码在提交之后没有完全通过系统的测试,有部分用例超时了,通过查看用例发现这部分的用例中数组元素都非常长,而且存在重复的元素。因此,分析原因是在我编写的双重循环中出现了大量的重复计算(对于同一个元素,数组中存在多个元素与它的和为某个2的幂,这样每次计算导致了重复)。
哈希表去重
为了解决这个问题我们可以使用哈希表进行计数,将已经遍历的元素的个数使用哈希表保存下来,而在判断2的幂的这个问题上我们使用用2的幂的结果进行倒推(题目中数组元素都是有范围的,那么结果幂也必然是有范围的)。每个遍历到的数都会与前面出现过的数进行组合判断,这样相当于是从后向前组合来判断(第一种方法的双重循环是从前向后组合)。
class Solution {
public int countPairs(int[] deliciousness) {
int ans = 0;
int maxVal = 0;
int MOD = 1000000007;
for (int delicious : deliciousness) {
if (delicious > maxVal) {
maxVal = delicious;
}
}
int maxSum = maxVal * 2;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < deliciousness.length; i++) {
int cur = deliciousness[i];
for (int sum = 1; sum <= maxSum; sum <<= 1) {
int cnt = map.getOrDefault(sum - cur, 0);
ans = (ans + cnt) % MOD;
}
map.put(cur, map.getOrDefault(cur, 0) + 1);
}
return ans;
}
}
小结
- 在数组中要进行两两组合的过程中,如果从前向后进行组合存在大量的重复,那么我们可以使用变为从当前的遍历向前进行组合,在这个过程中为了节省时间,可以将前面已经存在过的元素使用哈希表进行计数;
- 对于结果需要取余这个要求在中间过程也需要进行考虑,因为中间的结果也可能溢出;
- 注意题目中的提示,本题中已经提到数组的长度为 ,那么两辆组合进行双重循环显然是不合适的,而每个元素在数组中是限定了范围的,因此可以从结果逆推,对于中间计数使用哈希表加快。