[LeetCode] 每日一题 2176. 统计数组中相等且可以被整除的数对(暴力法 & 哈希(反优化)))
题目描述
给你一个下标从 0 开始长度为 n
的整数数组 nums
和一个整数 k
,请你返回满足 0 <= i < j < n
,nums[i] == nums[j]
且 (i * j)
能被 k
整除的数对 (i, j)
的 数目 。
题目链接
示例输入
示例 1
输入:nums = [3,1,2,2,2,1,3], k = 2
输出:4
解释:
总共有 4 对数符合所有要求:
- nums[0] == nums[6] 且 0 * 6 == 0 ,能被 2 整除。
- nums[2] == nums[3] 且 2 * 3 == 6 ,能被 2 整除。
- nums[2] == nums[4] 且 2 * 4 == 8 ,能被 2 整除。
- nums[3] == nums[4] 且 3 * 4 == 12 ,能被 2 整除。
示例 2
输入:nums = [1,2,3,4], k = 1
输出:0
解释:由于数组中没有重复数值,所以没有数对 (i,j) 符合所有要求。
提示
1 <= nums.length <= 100
1 <= nums[i], k <= 100
解题思路
这题第一眼看上去就很直观,题目要求我们找出所有满足 nums[i] == nums[j] 且 i * j 能被 k 整除 的数对 (i, j),其中 0 <= i < j < n
。
一开始没有太多思路,索性就直接用暴力枚举:双重循环遍历所有可能的 (i, j) 组合,然后判断是否满足两个条件。如果都满足,就计数加一。虽然时间复杂度是 O(n^2),但在数据范围允许的前提下,这种方式最直接,也不容易出错。
后来我尝试用哈希表做了一些优化思考,比如将相同值的下标分组,然后对这些下标两两组合判断是否满足 i * j 能被 k 整除。不过实测下来,哈希版本虽然理论上减少了不必要的对比,但实际运行效率反而更低。这也让我意识到一个点:时间复杂度是相对的,实践中的性能还跟数据分布、常数开销有关,暴力有时候未必差 💡
代码实现
// 暴力解法
class Solution {
public int countPairs(int[] nums, int k) {
int n = nums.length;
int ans = 0;
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (nums[i] == nums[j] && i * j % k == 0) {
ans++;
}
}
}
return ans;
}
}
// 哈希优化尝试(实际更慢)
class Solution {
public int countPairs(int[] nums, int k) {
HashMap<Integer, List<Integer>> map = new HashMap<>();
int ans = 0;
for (int i = 0; i < nums.length; i++) {
List<Integer> list = map.getOrDefault(nums[i], new ArrayList<>());
for (Integer index : list) {
if (index * i % k == 0) {
ans++;
}
}
list.add(i);
map.put(nums[i], list);
}
return ans;
}
}
复杂度分析
时间复杂度:
暴力解法:O(n²),每个下标对都判断一次
哈希优化:理论上更优,但因为多了维护结构和乘法判断,实际效率反而下降
空间复杂度:
暴力解法:O(1)
哈希优化:O(n),用于记录相同值的下标
实测体验告诉我们:别急着优化,有时暴力也有春天 🚀
总结
本题给了一个很典型的例子:条件判断 + 双重限制 + 下标逻辑。很多时候题目条件看起来复杂,但直接暴力尝试反而是最快拿分的方法。后续优化也要结合实际测试结果来看效果,而不是只看理论时间复杂度。这种经验在面试和真实开发中都非常重要 🧠
希望这篇分享能为你带来启发!如果你有任何问题或建议,欢迎在评论区留言,与我共同交流探讨。