LeetCode 有效三角形的个数

背景:这个题目解法是利用排序降低复杂度的一个例子

给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。

示例 1:

输入: [2,2,3,4]
输出: 3
解释:
有效的组合是: 
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3

注意:

  1. 数组长度不超过1000。
  2. 数组里整数的范围为 [0, 1000]。

分析:

 三角形的条件:第三边大于两边差,小于两边之和 因此,对于这个问题,需要判断可能的搭配:

c > abs(a - b)
c < a + b

那么,穷举每种可能来判断就可以啦:代码如下

class Solution:
    def triangleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        rnum = 0
        bvld = False
        p1 = 0
        p2 = 1
        p3 = 2
        le = len(nums)
        for p1 in range(le - 2):
            for p2 in range(p1 + 1, le - 1):
                ma = nums[p1] + nums[p2]
                mi = abs(nums[p1] - nums[p2])
                for p3 in range(p2 + 1, le):
                    if nums[p3] in range(mi + 1, ma):
                        rnum += 1
                        #print(mi, ma)
                        #print(p1, p2, p3)
        return rnum

算法复杂度o(n^3),时间超限

转变思路:能否不遍历每种可能性?可以,只要数组有序,那么找到边界就可以找到所有满足的组合!因此,思路如下:

1. 排序
2. 找边界

第一版:外层做2层循环,第三层变遍历为找边界,两边同时开始找,找到以后,左右边界可以做差得到满足结果的组合数量,累加得到结果即可:如下:

class Solution:
    def triangleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        rnum = 0
        nums.sort()
        p1 = 0
        p2 = 1
        p3 = 2
        le = len(nums)
        for p1 in range(le - 2):
            for p2 in range(p1 + 1, le - 1):
                bvld = False
                ma = nums[p1] + nums[p2]
                mi = abs(nums[p1] - nums[p2])
                pl = p2 + 1
                pr = le - 1
                while pr >= pl:
                    if nums[pr] < ma:
                        bvld = True
                        break
                    if nums[pr] >= ma:
                        pr -= 1                    
                if bvld:
                    rnum += pr - pl + 1
        return rnum

速度提升的比较明显,第一个算法只能跑一半的用例,这个算法能跑到最后几个,但是还是时间超限。接下来的任务还是降低复杂度,能不能把外层的2层循环拆掉一层呢?

即:每次先拿到第三条边,然后找前两条边的范围!这样复杂度可以下降到0(n^2)!代码如下(C++)

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        int count = 0, size = nums.size();
        sort(nums.begin(), nums.end());
        for (int i = size - 1; i >= 2; i--) {    // 先拿到第3条边
            int left = 0, right = i - 1;    // 前2条边
            while(left < right) {
                if (nums[left] + nums[right] > nums[i]) {
                    count += (right - left);    // 找到区间以后,就更新第2条边,构建新组合,并记录上个组合的总数
                    right--;
                }
                else {
                    left++;    // 调整第1条边
                }
            }
        }
        return count;
    }
};

解析如代码注释,这个算法更好的利用了排序的结果

总结:

算法中的数组如果有明显的大小比较判定环节,如果不排序的算法复杂度已经到了0(n^2)这个级别,那么先用O(nlgn)排序再设计往往能降低复杂度。如果复杂度到不了这个级别,排序似乎没有太大必要,毕竟排序的开销也不能忽视。

点赞