题目来源:https://leetcode-cn.com/problems/valid-triangle-number/
大致题意:
给出一个数组,判断数组的数作边能组成多少个三角形。
思路
三角形满足的条件:任意两条边的和大于第三边。
于是,我们可以在数组中取两个数,然后再枚举其他数有多少满足条件。
排序 + 二分
如果一个个数枚举,时间复杂度将为O(n^3)。
可以将数组排序后,然后使用二重循环枚举两个数,然后在剩余的数中二分查找最大的 小于 之前两个数的和 的数的位置,该位置的数以及其与两个数中间的数都满足三角形的条件。
这样时间复杂度为O(n^2 logn)
排序加扫描线
该方法和上个方法几乎一致。
不过上个方法是二分查找满足条件的第三个数,该方法使用扫描线,整体时间复杂度为O(n^2)。
在本方法中,使用二重循环枚举出两个较小数 nums[i] 和 nums[j] 后,从前往后枚举出合适的 nums[k],保证 nums[k] < nums[i] + nums[j]。
当 当前的nums[k]不满足条件时,则直接将 k - j 个数目统计入三角形数目。
在下一轮遍历中,nums[i] 不变,新的 nums[j] 大于之前的,那么同样 nums[k] 也应大于之前的才能满足条件 nums[k] < nums[i] + nums[j]。
故只需两轮遍历。
代码:
public int triangleNumber(int[] nums) {
Arrays.sort(nums);
int ans = 0;
int n = nums.length;
// 方法一:排序+二分
// for (int i = 0; i < n - 2; i++) {
// for (int j = i + 1; j < n - 1; j++) {
// int sum = nums[i] + nums[j];
// int pos = j;
// int left = j + 1;
// int right = n - 1;
// while (left <= right) { // 二分查找最大的满足条件的pos
// int mid = (left + right) / 2;
// if (nums[mid] > sum) {
// pos = mid;
// left = mid + 1;
// }
// else {
// right = mid - 1;
// }
// }
// ans += pos - j; // 累计数目
// }
// }
// 方法二:排序 + 扫描线
for (int i = 0; i < n - 2; i++) {
int k = i + 1; // 第三个数
for (int j = i + 1; j < n - 1; j++) {
while (k+1 < n && nums[k+1] < nums[i] + nums[j]) { // 枚举第三个数
k++;
}
ans += Math.max(k - j, 0); // 防止统计负数
}
}
return ans;
}