力扣611 有效三角形的个数

朴素解法 三重遍历

通过三个嵌套for循环实现三角形三边所有组合的遍历,无需排序,只需要保证下标 (i < j < k) 避免重复即可;
时间复杂度 (O(n^3)), 空间复杂度 (O(n).)

排序 + 二分查找

针对上述三重遍历的方法,比较容易想到的改进方法为二分查找。我们注意到,在有序的情况下,确定第一条边a和第二条边b,第三条边c的选择范围是一个连续的区间,即(b <=c < a+b.) 值得注意的是,在值上,c可以等于b,但是在对应的下标上,必须保证$ j < k.$
根据上述思路,我们可以对所有的边长进行一次排序。然后通过双重for循环遍历前两条边,第三条边的寻找不需要进行遍历,可通过二分查找找到其可能的最大值,那么整个选择的范围也就被确定了。
时间复杂度(O(n^2logn)),空间复杂度(O(n).)

排序 + 双指针

本题最优解是通过双指针来解决。对于第三条边,如果是随机分布在第二条边之后,那么二分查找(O(logn))的复杂度已经最优,但本题仍隐含一个信息,第一条边确定时,随着第二条边的值不断变大,第三条边的上限是递增的。因此,可通过双指针,将第二重循环和第三重循环转化为总复杂度只有(O(n))的一个过程。
时间复杂度(O(n^2)),空间复杂度(O(n).)

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int res = 0;
        for(int i = 0; i < nums.size(); ++i) {
            // 遍历第一条边
            int j = i+1;              // 第二条边的下标
            int k = j;                // 第三条边没找到时的下标
            for(;j < nums.size(); ++j){
                // 遍历第二条边
                k = max(k,j);         // 上次没找到第三条边时,将第三条边初始化为当前第二条边
                while(k + 1 < nums.size()){
                    if(nums[i] + nums[j] > nums[k + 1])  ++k;
                    else break;  
                }
                res += k - j;         // (j,k] 共有 k - j 个元素
            }            
        }
        return res;
    }
};

总结

本题从暴力搜索出发,通过分析其内蕴的三边之间关系,对第三条边的遍历进行优化。遇到类似的场景可分析各重循环之间是否具有依赖性,可根据特殊的联系使用二分查找或双指针进行算法优化。

原文地址:https://www.cnblogs.com/jinjin-2018/p/15100902.html