题目链接:611. 有效三角形的个数 - 力扣(LeetCode)



解题思路
三角形的有效条件:任意两边之和大于第三边 。通过排序 + 双指针优化复杂度:
- 排序数组:将数组升序排列,简化条件判断(只需保证较小两边之和 > 最大边)。
- 固定最大边 :遍历数组,以当前元素
nums[i]作为最大边。 - 双指针找有效对 :用左指针
left=0、右指针right=i-1,若nums[left] + nums[right] > nums[i],则[left, right-1]到right的所有组合都有效(计数 += right-left),并左移right;否则右移left。
方法一:暴力解法
解题思路:三层 for 循环枚举出所有的三元组,并且判断是否能构成三角形。虽然说是暴力求解,但是还是想优化一下:判断三角形的优化:
- 如果能构成三角形,需要满足任意两边之和大于第三边。但是实际上只需让较小的两条边之和大于第三边即可。
- 因此我们可以先将原数组排序,然后从小到大枚举三元组,一方面省去枚举的数量,另一方面方便判断是否能构成三角形。
java
package _005;
import java.util.Arrays;
public class demo1_force {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums1 = {2, 2, 3, 4};
System.out.println(solution.triangleNumber(nums1)); // 预期3
int[] nums2 = {4, 2, 3, 4};
System.out.println(solution.triangleNumber(nums2)); // 预期4
}
}
class Solution{
public int triangleNumber(int[] arr){
Arrays.sort(arr);
int count = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = i+1; j < arr.length; j++) {
for (int k = j+1; k <arr.length ; k++) {
if(arr[i] + arr[j] > arr[k]){
count++;
}
if(k == arr.length-1)
break;
}
}
}
return count;
}
}
代码问题有待优化:
1. 冗余代码:if(k == arr.length-1) break;
- 问题分析 :内层循环的条件是
k < arr.length,当k到达arr.length-1时,循环本身会自然结束,这个break语句完全没有必要,属于多余代码。 - 举例 :比如数组长度为 4,
k最大到 3(arr.length-1),执行完k=3后,k++变成 4,不满足k < 4,循环自动终止,break毫无意义。
2. 逻辑瑕疵:未跳过非正数元素
- 问题分析 :三角形的边长必须是正整数 (边长为 0 或负数无法构成三角形)。如果数组中包含 0 或负数,你的代码会错误地参与计算。
- 比如测试用例
nums = {0,0,0},你的代码会进入循环,但0+0>0不成立,最终返回 0(结果正确,但过程中做了无意义的判断); - 比如测试用例
nums = {0,2,3},你的代码会枚举(0,2,3),判断0+2>3?不成立,结果正确,但如果是nums = {0,3,4},同样做了无意义的判断。
- 比如测试用例
- 影响:结果不会错,但会多执行一些无效的循环判断,效率略低。
3. 循环范围可以优化(非错误,是优化点)
- 原代码中
i的循环范围是0 < arr.length,但实际上,三元组需要 3 个元素,因此:i的最大范围可以是arr.length - 3(因为j = i+1,k = j+1,需要至少留两个位置);j的最大范围可以是arr.length - 2;- 原代码的循环范围虽然正确,但会多执行几次外层循环(比如
i = arr.length-2时,j = arr.length-1,k没有值,循环直接结束),属于无意义的循环。
优化后的代码:
java
import java.util.Arrays;
public class demo1_force {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums1 = {2, 2, 3, 4};
System.out.println(solution.triangleNumber(nums1)); // 预期3
int[] nums2 = {4, 2, 3, 4};
System.out.println(solution.triangleNumber(nums2)); // 预期4
int[] nums3 = {0, 0, 0};
System.out.println(solution.triangleNumber(nums3)); // 预期0
// 边界测试用例:长度不足3
int[] nums4 = {1, 2};
System.out.println(solution.triangleNumber(nums4)); // 预期0
}
}
class Solution {
public int triangleNumber(int[] arr) {
// 边界条件:数组长度小于3,直接返回0
if (arr == null || arr.length < 3) {
return 0;
}
Arrays.sort(arr);
int count = 0;
int n = arr.length;
for (int i = 0; i < n - 2; i++) {
//这里判断是因为已经排序过了,小的在左边 i<0 即可
if (arr[i] <= 0) {
continue;
}
for (int j = i + 1; j < n - 1; j++) {
for (int k = j + 1; k < n; k++) {
if (arr[i] + arr[j] > arr[k]) {
count++;
}
}
}
}
return count;
}
}
方法二(排序 + 双指针):
**算法思路:**先将数组排序。
根据「解法一」中的优化思想,我们可以固定一个「最长边」,然后在比这条边小的有序数组中找出一个二元组,使这个二元组之和大于这个最长边。由于数组是有序的,我们可以利用「对撞指针」来优化。
设最长边枚举到 i 位置,区间 [left, right] 是 i 位置左边的区间(也就是比它小的区间):
- 如果
nums[left] + nums[right] > nums[i]:- 说明
[left, right - 1]区间上的所有元素均可以与nums[right]构成比nums[i]大的二元组 - 满足条件的有
right - left种 - 此时
right位置的元素的所有情况相当于全部考虑完毕,right--,进入下一轮判断
- 说明
- 如果
nums[left] + nums[right] <= nums[i]:- 说明
left位置的元素是不可能与[left + 1, right]位置上的元素构成满足条件的二元组 left位置的元素可以舍去,left++进入下轮循环
- 说明
java
package _005;
import java.util.Arrays;
public class demo2 {
public static void main(String[] args) {
Demo solution3 = new Demo();
int[] nums1 = {2, 2, 3, 4};
System.out.println(solution3.triangleNumber(nums1)); // 预期3
int[] nums2 = {4, 2, 3, 4};
System.out.println(solution3.triangleNumber(nums2)); // 预期4
}
}
class Demo{
public int triangleNumber(int[] arr) {
if(arr.length < 3 && arr.length > 0){
return 0;
}
int count = 0;
Arrays.sort(arr);
int maxNumber = arr.length-1;
for (; maxNumber >=2 ; maxNumber--) {
int left = 0,right = maxNumber -1;
while(left < right){
if(arr[left] + arr[right] > arr[maxNumber]){
count += right - left;
right--;
}else {
left++;
}
}
}
return count;
}
}