Leetcode 70 好数对的数目 | 与对应负数同时存在的最大正整数

1 题目

1512. 好数对的数目

给你一个整数数组 nums

如果一组数字 (i,j) 满足 nums[i] == nums[j]i < j ,就可以认为这是一组 好数对

返回好数对的数目。

示例 1:

复制代码
输入:nums = [1,2,3,1,1,3]
输出:4
解释:有 4 组好数对,分别是 (0,3), (0,4), (3,4), (2,5) ,下标从 0 开始

示例 2:

复制代码
输入:nums = [1,1,1,1]
输出:6
解释:数组中的每组数字都是好数对

示例 3:

复制代码
输入:nums = [1,2,3]
输出:0

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100

2 代码实现

cpp 复制代码
class Solution {
public:
    int numIdenticalPairs(vector<int>& nums) {
        int count = 0;
        int n = nums.size();
        for(int i = 0; i < n ; i ++){
            for (int j = n - 1 ; j > i ;j--){
                if(nums[i] == nums[j]){
                    count++;
                }
            }
        }
        return count;
    }
};

暴力枚举的做法,通过了,但是我知道这肯定不是好的做法。

好的做法难道是哈希映射吗,怎么做呢?

3 优化实现

优化思路:哈希统计 + 组合数计算

这个思路的时间复杂度是 (O(n))(仅需遍历数组两次:一次统计、一次计算),空间复杂度(O(1))(因为数值范围固定为 1~100,数组大小固定)。

优化代码实现

cpp 复制代码
class Solution {
public:
    int numIdenticalPairs(vector<int>& nums) {
        // 用数组代替哈希表(数值范围1~100,索引0不用)
        int count[101] = {0}; 
        int res = 0;
        
        // 第一步:统计每个数字出现的次数
        for (int num : nums) {
            count[num]++;
        }
        
        // 第二步:计算每个数字的组合数并累加
        for (int cnt : count) {
            // 只有出现次数≥2的数字才会产生好数对
            if (cnt >= 2) {
                res += cnt * (cnt - 1) / 2;
            }
        }
        
        return res;
    }
};

代码解释

  1. 统计次数 :遍历数组 nums,用 count[num] 记录每个数字 num 出现的次数(比如示例 1 中,1 出现 3 次、3 出现 2 次、2 出现 1 次);
  2. 计算组合数
    • 数字 1 出现 3 次:组合数 = \(3×2/2 = 3\)(对应下标 0-3、0-4、3-4);
    • 数字 3 出现 2 次:组合数 = \(2×1/2 = 1\)(对应下标 2-5);
    • 数字 2 出现 1 次:无组合数;
    • 总和 \(3+1=4\),与示例 1 结果一致。

进一步简化(一次遍历)

可以在统计次数的同时直接计算结果,无需二次遍历:

cpp 复制代码
class Solution {
public:
    int numIdenticalPairs(vector<int>& nums) {
        int count[101] = {0};
        int res = 0;
        for (int num : nums) {
            // 每新增一个num,能和之前已出现的count[num]个num组成新的好数对
            res += count[num]++;
        }
        return res;
    }
};

逻辑说明 :比如数字 1 依次出现,第一次 count[1]=0(无贡献),第二次 count[1]=1(贡献 1),第三次 count[1]=2(贡献 2),总贡献 (0+1+2=3),和组合数公式结果一致。

这种方式既节省空间(无需额外存储),又减少遍历次数,是最优解。

4 题目

2441. 与对应负数同时存在的最大正整数

给你一个 不包含 任何零的整数数组 nums ,找出自身与对应的负数都在数组中存在的最大正整数 k

返回正整数k ,如果不存在这样的整数,返回 -1

示例 1:

复制代码
输入:nums = [-1,2,-3,3]
输出:3
解释:3 是数组中唯一一个满足题目要求的 k 。

示例 2:

复制代码
输入:nums = [-1,10,6,7,-7,1]
输出:7
解释:数组中存在 1 和 7 对应的负数,7 的值更大。

示例 3:

复制代码
输入:nums = [-10,8,6,7,-2,-3]
输出:-1
解释:不存在满足题目要求的 k ,返回 -1 。

提示:

  • 1 <= nums.length <= 1000
  • -1000 <= nums[i] <= 1000
  • nums[i] != 0

5 代码实现

cpp 复制代码
class Solution {
public:
    int findMaxK(vector<int>& nums) {
        int k = -1 ;
        int n = nums.size();
        for (int i = 0 ; i < n ; i++ ){
            for (int j = n -1  ; j > i ; j--){
                if (nums[i] + nums[j] == 0 ){
                    int current = max(nums[i] , nums[j]);
                    k = current > k ? current : k;
                }
            }
        }
        return k;   
    }
};

通过了,但是充满了bad cases ,看看哪里可以优化一下吧。

一、当前代码的核心问题

1. 逻辑冗余:max(nums[i], nums[j]) 可简化

nums[i] + nums[j] = 0 时,两者互为相反数,比如 3-3max 结果就是正数的那个,直接用 abs(nums[i]) 更简洁,逻辑也更直观。

2. 效率问题:找到符合条件的数后,未及时终止内层循环(非错误,但可优化)

比如数组是 [-3,2,-1,3],当 i=0j=3 时已经找到 3-3,此时内层循环还会继续检查 j=2j=1(无意义),可以提前 break 减少遍历次数。

3. 边界场景:数组含 0 时不会误判(当前代码已规避)

因为 0+0=0,但内层循环 j>i 保证了 i≠j,所以单个 0 或多个 0 不会被误判为有效 k,这一点是正确的。

二、修正并优化后的代码

cpp 复制代码
class Solution {
public:
    int findMaxK(vector<int>& nums) {
        int k = -1;
        int n = nums.size();
        for (int i = 0; i < n; i++) {
            for (int j = n - 1; j > i; j--) { // 从后往前遍历,j>i避免重复检查
                if (nums[i] + nums[j] == 0) {
                    int current = abs(nums[i]); // 简化max逻辑
                    if (current > k) {
                        k = current;
                    }
                    break; // 找到当前i对应的最大j,无需继续遍历更小的j
                }
            }
        }
        return k;
    }
};

三、测试用例验证

测试用例 1:常规场景

输入:nums = [-1,2,-3,3]执行过程:

  • i=0(nums [i]=-1):j 从 3→1,无符合条件的数;
  • i=1(nums [i]=2):j 从 3→2,无符合条件的数;
  • i=2(nums [i]=-3):j=3(nums [j]=3),满足 -3+3=0,current=3,k 更新为 3;最终返回 3(正确)。
测试用例 2:无有效 k 的场景

输入:nums = [-10,8,6,7,-2,-3]执行过程:所有 i/j 组合都不满足 nums[i]+nums[j]=0,k 保持 -1(正确)。

测试用例 3:多个有效 k 的场景

输入:nums = [-5,5,-3,3,-2,2]执行过程:

  • i=0(-5)→j=1(5),current=5,k=5;
  • 后续 i=2(-3)→j=3(3),current=3(小于 5,k 不变);
  • i=4(-2)→j=5(2),current=2(小于 5,k 不变);最终返回 5(正确)。

四、进阶优化(时间复杂度从 O (n²)→O (n))

如果想进一步提升效率,推荐用哈希集合(空间换时间),遍历每个正数,检查其相反数是否存在,直接找最大值:

cpp 复制代码
class Solution {
public:
    int findMaxK(vector<int>& nums) {
        int k = -1;
        unordered_set<int> num_set(nums.begin(), nums.end());
        for (int num : nums) {
            if (num > 0 && num_set.count(-num)) { // 只检查正数,避免重复判断
                k = max(k, num);
            }
        }
        return k;
    }
};

总结

  • 若追求简洁:用 abs(nums[i]) 替代 max(nums[i], nums[j])
  • 若追求效率:优先选择哈希集合解法(O (n) 时间复杂度)。
相关推荐
小妖6661 小时前
力扣(LeetCode)- 74. 搜索二维矩阵
算法·leetcode·矩阵
Liangwei Lin1 小时前
洛谷 U311289 矩阵距离
线性代数·算法·矩阵
hd51cc3 小时前
MFC消息 学习笔记
笔记·学习·mfc
Dev7z4 小时前
基于MATLAB数学形态学的边缘检测算法仿真实现
算法·计算机视觉·matlab
盐焗西兰花9 小时前
鸿蒙学习实战之路:状态管理最佳实践
学习·华为·harmonyos
风筝在晴天搁浅11 小时前
代码随想录 718.最长重复子数组
算法
kyle~11 小时前
算法---回溯算法
算法
小毅&Nora11 小时前
【人工智能】【深度学习】 ⑦ 从零开始AI学习路径:从Python到大模型的实战指南
人工智能·深度学习·学习
Maxwell_li111 小时前
Pandas 描述分析和分组分析学习文档
学习·数据分析·numpy·pandas·matplotlib