优选算法-005 有效三角形的个数(medium)

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

解题思路

三角形的有效条件:任意两边之和大于第三边 。通过排序 + 双指针优化复杂度:

  1. 排序数组:将数组升序排列,简化条件判断(只需保证较小两边之和 > 最大边)。
  2. 固定最大边 :遍历数组,以当前元素 nums[i] 作为最大边。
  3. 双指针找有效对 :用左指针 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+1k = j+1,需要至少留两个位置);
    • j的最大范围可以是arr.length - 2
    • 原代码的循环范围虽然正确,但会多执行几次外层循环(比如i = arr.length-2时,j = arr.length-1k没有值,循环直接结束),属于无意义的循环。

优化后的代码:

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;
}
}
相关推荐
yuuki2332332 小时前
【C++】类和对象下
数据结构·c++·算法
huohuopro2 小时前
结构体与链表
数据结构·算法·链表
CoovallyAIHub2 小时前
告别“消失的小目标”:航拍图像检测新框架,精度飙升25.7%的秘诀
深度学习·算法·计算机视觉
第二只羽毛2 小时前
外卖订餐管理系统
java·大数据·开发语言·算法
发疯幼稚鬼2 小时前
希尔排序与堆排序
c语言·数据结构·算法·排序算法
小尧嵌入式2 小时前
Linux的shell命令
linux·运维·服务器·数据库·c++·windows·算法
Jeremy爱编码2 小时前
leetcode热题路径总和 III
算法·leetcode·职场和发展
CoovallyAIHub2 小时前
滑雪季又来了!如何用计算机视觉帮雪场解决最头疼的问题
深度学习·算法·计算机视觉
懂AI的老郑3 小时前
深入理解C++中的堆栈:从数据结构到应用实践
java·数据结构·c++