给定一个包含非负整数的数组 nums
,返回其中可以组成三角形三条边的三元组个数。
示例 1:
输入: nums = [2,2,3,4] 输出: 3 解释:有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 2) 2,2,3
示例 2:
输入: nums = [4,2,3,4] 输出: 4
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
解法1:排序 + 二分查找
对于正整数 a,b,c,它们可以作为三角形的三条边,当且仅当:
a+b>c
a+c>b
b+c>a
均成立。如果我们将三条边进行升序排序,使它们满足 a ≤ b ≤ c,那么 a+c>b 和 b+c>a 使一定成立的,我们只需要保证 a+b>c。
因此,我们可以将数组 nums 进行升序排序,随后使用二重循环枚举 a 和 b。设 a=nums[i],b=nums[j],为了防止重复统计答案,我们需要保证 i<j。剩余的边 c 需要满足 c<nums[i]+nums[j],我们可以在 [ j+1,n−1 ] 的下标范围内使用二分查找(其中 n 是数组 nums 的长度),找出最大的满足 nums[k]<nums[i]+nums[j] 的下标 k,这样一来,在 [ j+1,k ] 范围内的下标都可以作为边 c 的下标,我们将该范围的长度 k−j 累加入答案。
当枚举完成后,我们返回累加的答案即可。
注意到题目描述中 nums 包含的元素为非负整数,即除了正整数以外,nums 还会包含 0。三角平行的边长不能为0,因此我们对数组排完序之后需要先跳过0。
Java版:
class Solution {
public int triangleNumber(int[] nums) {
// 数组先排序,才能进行二分查找
Arrays.sort(nums);
int n = nums.length;
int ans = 0;
for (int i = 0; i <= n - 3; i++) {
if (nums[i] == 0) {
continue;
}
for (int j = i + 1; j <= n - 2; j++) {
int k = binarySearch(nums, nums[i] + nums[j], j + 1, n - 1);
ans += k - j;
}
}
return ans;
}
// 在搜索范围内,找到最大的小于target的下标
private int binarySearch(int[] nums, int target, int l, int r) {
while (l <= r) {
// nums[l - 1] < target
// nums[r + 1] >= target
int mid = (r - l) / 2 + l;
if (nums[mid] < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return r;
}
}
Python3版:
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
nums.sort()
n = len(nums)
ans = 0
for i in range(n - 2):
if nums[i] == 0:
continue
for j in range(i + 1, n - 1):
k = self.binarySearch(nums, nums[i] + nums[j], j + 1, n - 1)
ans += k - j
return ans
def binarySearch(self, nums: List[int], target: int, l: int, r: int) -> int:
while l <= r:
mid = l + (r - l) // 2
if nums[mid] < target:
l = mid + 1
else:
r = mid - 1
return r
复杂度分析
- 时间复杂度:O(n^2 logn),其中 n 是数组 nums 的长度。我们需要 O(nlogn) 的时间对数组 nums 进行排序,随后需要 O(n^2 logn) 的时间使用二重循环枚举 a,b 的下标以及使用二分查找得到 c 的下标范围。
- 空间复杂度:O(logn),即为排序需要的栈空间。
解法2:排序 + 双指针
我们将当 a=nums[i],b=nums[j] 时,最大的满足 nums[k]<nums[i]+nums[j] 的下标 k 记为 k。可以发现,如果我们固定 i,那么随着 j 的递增,不等式右侧 nums[i]+nums[j] 也是递增的,因此 k 也是递增的。
这样一来,我们就可以将 j 和 k 看成两个同向(递增)移动的指针,将方法一进行如下的优化:
我们使用一重循环枚举 i。当 i 固定时,我们使用双指针同时维护 j 和 k,它们的初始值为 i + 1 , i + 2;
我们每一次将 j 向右移动一个位置,并尝试不断向右移动 k,使得 k 是最大的满足 nums[k]<nums[i]+nums[j] 的下标。我们将 k−j 累加入答案。
当枚举完成后,我们返回累加的答案即可。
Java版:
class Solution {
public int triangleNumber(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
int ans = 0;
for (int i = 0; i <= n - 3; i++) {
if (nums[i] == 0) {
continue;
}
int k = i + 2;
for (int j = i + 1; j <= n - 2; j++) {
if (k < j) {
k = j + 1;
}
while (k < n && nums[k] < nums[i] + nums[j]) {
k++;
}
// nums[k] >= nums[i] + nums[j]
// k - 1 是满足要求的最大下标
ans += k - 1 - j;
}
}
return ans;
}
}
Python3版:
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
nums.sort()
n = len(nums)
ans = 0
for i in range(n - 2):
if nums[i] == 0:
continue
k = i + 2
for j in range(i + 1, n - 1):
while k < n and nums[k] < nums[i] + nums[j]:
k += 1
ans += k - 1 - j
return ans
复杂度分析
- 时间复杂度:O(n^2),其中 n 是数组 nums 的长度。我们需要 O(nlogn) 的时间对数组 nums 进行排序,随后需要 O(n^2) 的时间使用一重循环枚举 a 的下标以及使用双指针维护 b,c 的下标。
- 空间复杂度:O(logn),即为排序需要的栈空间。