如何高效计算数组中坏数对的数量:从暴力解法到最优解
引言
在算法面试和编程竞赛中,数组相关的问题非常常见。今天我们要讨论的是一个关于数组中"坏数对"计数的问题。这个问题看似简单,但通过不同的解法可以很好地展示算法优化的思路。本文将循序渐进地介绍从暴力解法到最优解的思考过程,帮助读者掌握算法优化的技巧。
问题描述
给定一个下标从0开始的整数数组nums
。如果i < j
且j - i != nums[j] - nums[i]
,那么我们称(i, j)
是一个坏数对。请返回数组中坏数对的总数目。
示例1:
复制
输入:nums = [4,1,3,3] 输出:5 解释:共有6个数对,其中只有(1,3)是好数对(因为3-1 == 3-1),其余都是坏数对。
示例2:
复制
输入:nums = [1,2,3,4,5] 输出:0 解释:所有数对都是好数对,因为对于任意i<j,都有j-i == nums[j]-nums[i]。
解法一:暴力枚举(不推荐)
思路
最直观的解法是枚举所有可能的数对(i,j)
(i<j),然后检查是否满足j-i != nums[j]-nums[i]
,统计满足条件的数对数量。
代码实现
java
复制
public long countBadPairs(int[] nums) { int n = nums.length; long count = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (j - i != nums[j] - nums[i]) { count++; } } } return count; }
复杂度分析
-
时间复杂度:O(n²),因为需要双重循环枚举所有数对
-
空间复杂度:O(1)
缺点
当数组长度较大时(比如n=10⁵),这种解法会非常慢,无法在合理时间内完成计算。
解法二:数学转化+哈希表统计
优化思路
观察坏数对的条件j - i != nums[j] - nums[i]
,我们可以将其变形为:
复制
nums[j] - j != nums[i] - i
定义diff[k] = nums[k] - k
,那么坏数对就是满足diff[i] != diff[j]
的数对。
因此:
-
坏数对总数 = 总对数 - 好数对总数
-
总对数 = C(n,2) = n*(n-1)/2
-
好数对总数 = 所有相同
diff
值形成的数对数量之和
算法步骤
-
计算每个元素的
diff
值:diff[i] = nums[i] - i
-
统计每个
diff
值出现的次数 -
计算好数对总数:对于每个出现m次的
diff
值,贡献C(m,2)=m*(m-1)/2个好数对 -
坏数对总数 = 总对数 - 好数对总数
代码实现
java
复制
import java.util.HashMap; import java.util.Map; public class Solution { public long countBadPairs(int[] nums) { int n = nums.length; long totalPairs = (long) n * (n - 1) / 2; Map<Integer, Integer> freq = new HashMap<>(); // 计算diff并统计频率 for (int i = 0; i < n; i++) { int diff = nums[i] - i; freq.put(diff, freq.getOrDefault(diff, 0) + 1); } // 计算好数对的数量 long goodPairs = 0; for (int count : freq.values()) { goodPairs += (long) count * (count - 1) / 2; } // 坏数对数量 = 总对数 - 好数对数量 return totalPairs - goodPairs; } }
复杂度分析
-
时间复杂度:O(n),只需要两次线性遍历
-
空间复杂度:O(n),需要存储
diff
值的频率
优点
这种方法将时间复杂度从O(n²)降低到O(n),可以高效处理大规模数据。
关键点解析
-
问题转化:将原始条件转化为比较
nums[i]-i
的值,这是优化的关键 -
逆向思维:不直接计算坏数对,而是通过总对数减去好数对来计算
-
哈希表统计:利用哈希表高效统计相同
diff
值的出现次数 -
组合数计算:对于出现m次的
diff
值,其好数对数量是C(m,2)
边界情况考虑
-
空数组或单元素数组:
-
数组长度小于2时,没有数对,直接返回0
-
-
所有数对都是好数对:
-
如
[1,2,3,...,n]
,此时nums[i]-i
都相同
-
-
所有数对都是坏数对:
-
如所有元素相同且数组长度>1,此时
nums[i]-i
都不同
-
实际应用
这种"坏数对"的概念可以应用于:
-
检测数据序列的规律性
-
时间序列分析中寻找异常点
-
测试数据生成时验证某些性质
总结
通过这个问题,我们学习了如何:
-
分析问题并寻找数学转化方法
-
使用逆向思维简化计算
-
利用哈希表进行高效统计
-
从暴力解法逐步优化到最优解
这种思考过程对于解决其他算法问题也具有很好的借鉴意义。在实际面试或竞赛中,能够快速识别问题本质并进行相应优化是非常重要的能力。
扩展思考
-
如果问题改为统计满足
j-i > nums[j]-nums[i]
的数对数量,该如何解决? -
如果数组非常大(n=10⁷),如何进一步优化空间复杂度?
-
如果允许修改数组,能否实现O(1)空间复杂度的解法?
希望这篇文章对你理解这个问题和类似的数组处理问题有所帮助!