题目链接
6355. 统计公平数对的数目
题目描述
给你一个下标从 0 开始、长度为 n 的整数数组 nums ,和两个整数 lower 和 upper ,返回 公平数对的数目 。
如果 (i, j) 数对满足以下情况,则认为它是一个 公平数对 :
0 <= i < j < n,且
lower <= nums[i] + nums[j] <= upper
示例 1:
输入:nums = [0,1,7,4,4,5], lower = 3, upper = 6
输出:6
解释:共计 6 个公平数对:(0,3)、(0,4)、(0,5)、(1,3)、(1,4) 和 (1,5) 。
示例 2:
输入:nums = [1,7,9,2,5], lower = 11, upper = 11
输出:1
解释:只有单个公平数对:(2,3) 。
提示:
1 <= nums.length <= 105
nums.length == n
-109 <= nums[i] <= 109
-109 <= lower <= upper <= 109
题解
lower <= nums[i] + nums[j] <= upper可以转化为找
lower - nums[j] <= nums[i] <= upper - nums[j]
因此我们可以先对nums排序,再加两次二分查找左边界lower - nums[j]和右边界upper - nums[j] + 1
需要理解的是为什么upper - nums[j] + 1,这个+1是什么原因
首先,二分查找的target,含义是指第一个大于等于target的数的索引,
举个例子从[1,3,5,7,9,9]中找8到9之间的数
我们的思路则应该是找到第一个大于等8的数的索引,以及最后一个小于等于9的数索引
找最后一个小于等于9的数字的索引,可以等价为找第一个大于等于9+1的数的索引index值-1
/**
* @param {number[]} nums
* @param {number} lower
* @param {number} upper
* @return {number}
*/
var countFairPairs = function(nums, lower, upper) {
// 1 2 5 7 9 11 11
nums.sort((x, y) => x - y)
let ans = 0
nums.forEach((num, index) => {
const l = bs(nums, index + 1, nums.length - 1, lower - num)
const r = bs(nums, index + 1, nums.length - 1, upper - num + 1)
ans += r - l
})
return ans
// 二分查找第一个 >= target的数的下标
function bs(nums, left, right, target) {
while (left <= right) {
const mid = ((right - left) >> 1) + left // 防止left + right溢出
if (nums[mid] < target) {
left = mid + 1 // [mid + 1, right]
} else {
right = mid - 1 // [left, mid - 1]
}
}
// right + 1 或者 left 为 “第一个 >= target的数的下标”
return left
}
};