每日leetcode

611. 有效三角形的个数 - 力扣(LeetCode)

题目

给定一个包含非负整数的数组 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. 首先需要对数组进行排序,有序数组才能按规律进行分析。
  2. 组成三角形要求任意两边之和大于第三边,如果边长数组已经是有序了的,那么只需要看小的两边之和是否大于第三边就可以只用一个条件判断来确定是否可以组成三角形。
  3. 要统计可以组成三角形的个数,可以不断固定前两条边,然后找第三条边最长可以到多少,那么这中间的所有边都是可行的,直接加上即可,这部分可以通过二分查找来实现(而且是能加速的,因为下一个循环)。

代码实现

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        int n = nums.size()-1;
        if(n < 2) return 0;
        int cnt = 0;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < n-1; ++i) {
            int left = i+2, ans = i+1;
            for(int j = i+1; j < n; ++j) {
                if(j>i+1 && nums[j]==nums[j-1]) {
                    cnt += ans - j;
                    ans = max(ans, j+1);
                    left = ans;
                    continue;
                }
                int sum = nums[i]+nums[j], right = n;
                while(left <= right) {
                    int mid = left + (right-left) / 2;
                    if(nums[mid] < sum) {
                        left = mid + 1;
                        ans = mid;
                    }
                    else right = mid - 1;
                }
                cnt += ans - j;
                ans = max(ans, j+1);
                left = ans;
            }
        }
        return cnt;
    }
};

复杂度分析

  • 时间复杂度:数组排序的时间复杂度是O(nlogn)的,双重循环的复杂度是O(n^2),循环内的二分查找是O(logn)的,所以总的时间复杂度是O(n^2logn)的。
  • 空间复杂度:排序需要占用O(logn)的空间复杂度,其他部分的空间复杂度是O(1)的,所以总的空间复杂度是O(logn)的。

题解

  • 官解提出了一种排序+双指针的方法替换排序+二分查找从而将时间复杂度降低到O(n^2),学习一下:
  • 简单来说其实和我的优化的方向差不多,但是因为用了二分查找要处理有重复数字的下界的问题上逻辑比较绕,而且不太好做(但也不是不能做)。如果改用双指针,那么内循环就只用逐渐递增即可了,虽然递增是O(n)的,但是是对内部的两重循环来说只用O(n),但是二分查找如果不做优化则是对内部两重循环需要O(nlogn)的,其差别在于二分查找每次都需要重复考虑后面没办法组成三角形的那部分,而双指针只需要考虑那些是可以的直到最后,规模大了之后重复考虑的部分开销就大了。
  • 复现:
  • class Solution {
    public:
        int triangleNumber(vector<int>& nums) {
            int cnt = 0, n = nums.size();
            sort(nums.begin(), nums.end());
            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;
                    cnt += max(k-j, 0);
                }
            }
            return cnt;
        }
    };
  • 看了其他大佬的方法,还有进一步优化的空间,也是双指针,每次确定最大的那条边,然后设置首尾指针分别指向0和i-1,那么他们每次成立,就有left到right-1个组合可以符合,即right-left个组合,直接在每个新的长边出现后都这么缩,如果大于小于长边说明最短边不够长,缩下界,如果够长了就记录,然后把上界缩小统计更小的组合。

  • 这一步主要精简了官解的循环判断,直接通过双指针的大小来判断是否停步,且双指针自动控制了搜索范围就是有效范围,所以不用担心越界的问题,精简了官解while循环中k+1<n的判断。

  • 复现(官解的解法基本比我的解法快了一倍,这个方法比官解的快了将近一倍):

  • class Solution {
    public:
        int triangleNumber(vector<int>& nums) {
            int cnt = 0, n = nums.size()-1;
            sort(nums.begin(), nums.end());
            for(int i = n; i >= 2; --i) {
                int left = 0, right = i-1;
                while(left < right) {
                    if(nums[left]+nums[right] > nums[i]) {
                        cnt += right - left;
                        --right;
                    }
                    else ++left;
                }
            }
            return cnt;
        }
    };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值