背景:这个题目解法是利用排序降低复杂度的一个例子
给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。
示例 1:
输入: [2,2,3,4] 输出: 3 解释: 有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 2) 2,2,3
注意:
- 数组长度不超过1000。
- 数组里整数的范围为 [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)排序再设计往往能降低复杂度。如果复杂度到不了这个级别,排序似乎没有太大必要,毕竟排序的开销也不能忽视。