背景:這個題目解法是利用排序降低複雜度的一個例子
給定一個包含非負整數的數組,你的任務是統計其中可以組成三角形三條邊的三元組個數。
示例 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)排序再設計往往能降低複雜度。如果複雜度到不了這個級別,排序似乎沒有太大必要,畢竟排序的開銷也不能忽視。