力扣-每日一题

如何高效计算数组中坏数对的数量:从暴力解法到最优解

引言

在算法面试和编程竞赛中,数组相关的问题非常常见。今天我们要讨论的是一个关于数组中"坏数对"计数的问题。这个问题看似简单,但通过不同的解法可以很好地展示算法优化的思路。本文将循序渐进地介绍从暴力解法到最优解的思考过程,帮助读者掌握算法优化的技巧。

问题描述

给定一个下标从0开始的整数数组nums。如果i < jj - 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]的数对。

因此:

  1. 坏数对总数 = 总对数 - 好数对总数

  2. 总对数 = C(n,2) = n*(n-1)/2

  3. 好数对总数 = 所有相同diff值形成的数对数量之和

算法步骤

  1. 计算每个元素的diff值:diff[i] = nums[i] - i

  2. 统计每个diff值出现的次数

  3. 计算好数对总数:对于每个出现m次的diff值,贡献C(m,2)=m*(m-1)/2个好数对

  4. 坏数对总数 = 总对数 - 好数对总数

代码实现

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),可以高效处理大规模数据。

关键点解析

  1. 问题转化:将原始条件转化为比较nums[i]-i的值,这是优化的关键

  2. 逆向思维:不直接计算坏数对,而是通过总对数减去好数对来计算

  3. 哈希表统计:利用哈希表高效统计相同diff值的出现次数

  4. 组合数计算:对于出现m次的diff值,其好数对数量是C(m,2)

边界情况考虑

  1. 空数组或单元素数组

    • 数组长度小于2时,没有数对,直接返回0

  2. 所有数对都是好数对

    • [1,2,3,...,n],此时nums[i]-i都相同

  3. 所有数对都是坏数对

    • 如所有元素相同且数组长度>1,此时nums[i]-i都不同

实际应用

这种"坏数对"的概念可以应用于:

  1. 检测数据序列的规律性

  2. 时间序列分析中寻找异常点

  3. 测试数据生成时验证某些性质

总结

通过这个问题,我们学习了如何:

  1. 分析问题并寻找数学转化方法

  2. 使用逆向思维简化计算

  3. 利用哈希表进行高效统计

  4. 从暴力解法逐步优化到最优解

这种思考过程对于解决其他算法问题也具有很好的借鉴意义。在实际面试或竞赛中,能够快速识别问题本质并进行相应优化是非常重要的能力。

扩展思考

  1. 如果问题改为统计满足j-i > nums[j]-nums[i]的数对数量,该如何解决?

  2. 如果数组非常大(n=10⁷),如何进一步优化空间复杂度?

  3. 如果允许修改数组,能否实现O(1)空间复杂度的解法?

希望这篇文章对你理解这个问题和类似的数组处理问题有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值