2026-01-17:完美对的数目。用go语言,给定一个整数数组 nums。我们把满足下面条件的一对索引 (i, j)(且 i < j)称为“完美对”: - 记

13 阅读5分钟

2026-01-17:完美对的数目。用go语言,给定一个整数数组 nums。我们把满足下面条件的一对索引 (i, j)(且 i < j)称为“完美对”:

  • 记 a = nums[i],b = nums[j];
  • 在两者差的绝对值 |a - b| 和和的绝对值 |a + b| 中,较小的那个不大于 a、b 的绝对值中的较小值;
  • 在两者差的绝对值 |a - b| 和和的绝对值 |a + b| 中,较大的那个不小于 a、b 的绝对值中的较大值。 任务是统计数组中不同的这样的“完美对”的数量。说明:|x| 表示 x 的绝对值(非负数)。 2 <= nums.length <= 100000。 -1000000000 <= nums[i] <= 1000000000。 输入: nums = [-3,2,-1,4]。 输出: 4。 解释: 有 4 个完美对:
(i, j)(a, b)min(|a - b|, |a + b|)min(|a|, |b|)max(|a - b|, |a + b|)max(|a|, |b|)
(0, 1)(-3, 2)min(|-3 - 2|, |-3 + 2|) = min(5, 1) = 1min(|-3|, |2|) = min(3, 2) = 2max(|-3 - 2|, |-3 + 2|) = max(5, 1) = 5max(|-3|, |2|) = max(3, 2) = 3
(0, 3)(-3, 4)min(|-3 - 4|, |-3 + 4|) = min(7, 1) = 1min(|-3|, |4|) = min(3, 4) = 3max(|-3 - 4|, |-3 + 4|) = max(7, 1) = 7max(|-3|, |4|) = max(3, 4) = 4
(1, 2)(2, -1)min(|2 - (-1)|, |2 + (-1)|) = min(3, 1) = 1min(|2|, |-1|) = min(2, 1) = 1max(|2 - (-1)|, |2 + (-1)|) = max(3, 1) = 3max(|2|, |-1|) = max(2, 1) = 2
(1, 3)(2, 4)min(|2 - 4|, |2 + 4|) = min(2, 6) = 2min(|2|, |4|) = min(2, 4) = 2max(|2 - 4|, |2 + 4|) = max(2, 6) = 6max(|2|, |4|) = max(2, 4) = 4

题目来自力扣3649。

🧠 算法步骤分解

整个算法的核心思路是,在将数组中的负数转换为正数并排序后,利用双指针技巧高效地统计满足特定数值条件的数对。

  1. 处理负数

    • 操作:遍历整数数组 nums,如果某个元素 x 是负数,则将其替换为其绝对值(即 x *= -1)。
    • 目的:由于题目条件涉及绝对值,且完美对的条件对于 (a, b)(-a, -b) 等情形是等价的,将所有数转为非负数可以简化后续的比较和计算,避免符号带来的复杂性。这一步确保了数组中所有元素都是非负的。
  2. 数组排序

    • 操作:使用排序算法(代码中为 slices.Sort(nums))对处理后的非负数组进行升序排序。
    • 目的:排序是为了让数值相近的元素聚集在一起,为后续使用双指针法创造条件。在一个有序的数组中,可以更高效地找到满足特定大小关系的数对。
  3. 双指针统计完美对

    • 操作:初始化一个指针 left 指向数组起始位置(0)。然后遍历数组,将当前遍历到的元素 nums[j] 视为数对中的 b。在遍历过程中,调整 left 指针的位置,使得从 leftj-1 的所有元素 nums[i] 都满足 nums[i] * 2 >= b(或者说,跳过那些 nums[i] * 2 < b 的元素)。对于每个 j,满足条件的 i 的范围就是 leftj-1,因此完美对的数量增加 (j - left)
    • 关键点:在排序后的非负数组中,完美对的条件可以简化为判断 ab 的大小关系。这里的逻辑是,对于固定的 b,需要找到所有满足条件的 aa <= b),使得 ab 的数值关系符合原始题目中关于绝对值和与差的条件。通过检查 a * 2b 的关系,可以高效地筛选这些数对。指针 left 随着 j 的增大而单向向右移动,不会回退,这保证了高效性。

⏱️ 复杂度分析

  • 总的时间复杂度O(n log n)。 这主要由排序步骤决定,因为排序的时间复杂度通常是 O(n log n)。后续的双指针遍历过程,left 指针和 j 指针各遍历数组一次,是线性的 O(n) 操作。因此,整个算法的总时间复杂度由排序步骤主导,为 O(n log n)。

  • 总的额外空间复杂度O(1)。 算法在执行过程中只使用了固定数量的额外变量(如 ans, left, j 等),这些空间不随输入数组的大小 n 而变化。排序操作如果是在原数组上进行(如代码中的 slices.Sort),通常也不需要额外的空间(即原地排序)。因此,额外的空间复杂度是常数级别的 O(1)。

Go完整代码如下:

package main

import (
	"fmt"
	"slices"
)

func perfectPairs(nums []int) (ans int64) {
	for i, x := range nums {
		if x < 0 {
			nums[i] *= -1
		}
	}

	slices.Sort(nums)
	left := 0
	for j, b := range nums {
		for nums[left]*2 < b {
			left++
		}
		// a=nums[i],其中 i 最小是 left,最大是 j-1,一共有 j-left 个
		ans += int64(j - left)
	}
	return
}

func main() {
	nums := []int{-3, 2, -1, 4}
	result := perfectPairs(nums)
	fmt.Println(result)
}

在这里插入图片描述

Python完整代码如下:

# -*-coding:utf-8-*-

from typing import List

def perfectPairs(nums: List[int]) -> int:
    # 将所有数字转为非负
    nums = [abs(x) for x in nums]
    
    # 排序
    nums.sort()
    
    ans = 0
    left = 0
    
    for j, b in enumerate(nums):
        # 移动左指针,找到满足 a*2 >= b 的最小索引
        while left < j and nums[left] * 2 < b:
            left += 1
        
        # a = nums[i],其中 i 从 left 到 j-1,共 j-left 个
        ans += j - left
    
    return ans

def main():
    nums = [-3, 2, -1, 4]
    result = perfectPairs(nums)
    print(result)  

if __name__ == "__main__":
    main()

在这里插入图片描述

C++完整代码如下:

#include <vector>
#include <algorithm>
#include <cstdlib> // for abs
#include <iostream>

long long perfectPairs(std::vector<int>& nums) {
    // 将所有数字转为非负
    for (int& num : nums) {
        if (num < 0) {
            num = -num;
        }
    }

    // 排序
    std::sort(nums.begin(), nums.end());

    long long ans = 0;
    int left = 0;
    int n = nums.size();

    for (int j = 0; j < n; ++j) {
        int b = nums[j];
        // 移动左指针,找到满足 a*2 >= b 的最小索引
        while (left < j && nums[left] * 2 < b) {
            ++left;
        }

        // a = nums[i],其中 i 从 left 到 j-1,共 j-left 个
        ans += j - left;
    }

    return ans;
}

int main() {
    std::vector<int> nums = {-3, 2, -1, 4};
    long long result = perfectPairs(nums);
    std::cout << result << std::endl;
    return 0;
}

在这里插入图片描述