引言
在今天的讨论中,我们将聚焦于一个引人入胜的算法挑战——“有效三角形的个数”。这个问题不仅考验了我们对三角形构成条件的理解,还挑战了我们的编程技巧和算法设计能力。通过解决这个问题,我们能够更深入地理解排序算法和双指针技巧在解决实际问题中的巧妙应用。
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:先排序1,后暴力法
最直接的方法是先对数组进行排序,然后使用三重循环遍历所有可能的三元组组合,检查是否满足三角形的构成条件(任意两边之和大于第三边)。虽然这种方法在数组较小的情况下可以工作,但其时间复杂度为 O(n^3),在数组较大时效率较低。
思路2:先排序,后碰撞指针,如下图所示。
为了优化算法,我们可以利用排序后的数组特性,采用双指针技巧来减少不必要的比较。具体地,我们固定最长边(即数组末尾的元素),然后使用两个指针(左指针和右指针)分别指向起始位置和次末尾位置,根据当前最长边与左右两边之和的关系移动指针。这种方法的时间复杂度降低到 O(n^2),显著提高了效率。
代码实现
以下是使用思路1,C++实现的代码示例:
class Solution {
public:
int triangleNumber(vector<int>& nums) {
int n = nums.size();
int ret = 0;
sort(nums.begin(),nums.end());
for(int i = 0; i< n; i++){
for(int j = i+1; j<n; j++){
for(int k = j+1; k<n; k++){
if(nums[i] + nums[j] > nums[k]){
ret++;
}
}
}
}
return ret;
}
};
以下是使用思路2,C++实现的代码示例:
class Solution {
public:
int triangleNumber(vector<int>& nums) {
int longest = nums.size()-1;
int ret = 0;
sort(nums.begin(), nums.end());
for(longest = nums.size()-1; longest > 1; longest--){
int left = 0, right = longest-1;
while(left < right){
if(nums[left] + nums[right] > nums[longest]){
ret += (right - left);
right--;
}else{
left++;
}
}
}
return ret;
}
};
通过这两种方法的对比,我们可以看到,在解决此类问题时,合理的算法设计和优化技巧对于提高程序效率至关重要。
关于为什么先排序在解决“有效三角形的个数”问题中如此重要的解释,主要有以下几点:
利用三角形的性质:
三角形的两边之和必须大于第三边。当我们对数组进行排序后,较小的数会排在前面,较大的数会排在后面。这样,当我们固定一个“最长边”(实际上是当前考虑的三个数中的最大数),然后寻找能与它组成三角形的其他两边时,我们就可以利用排序后的数组特性来优化搜索过程。
减少不必要的比较:
如果没有排序,我们需要对数组中任意三个数都进行三角形条件的检查,这会导致大量的、可能是不必要的比较。而排序后,我们可以利用双指针技巧,通过移动指针来快速跳过那些明显不可能与当前最长边组成三角形的数,从而大大减少比较次数。
优化搜索空间:
排序后的数组允许我们使用双指针来优化搜索空间。具体来说,当我们固定一个最长边后,左指针从数组起始位置开始,右指针从最长边前一个位置开始。如果当前左右两边之和大于最长边,那么我们可以确定左指针到右指针之间的所有数(不包括右指针所指的数)都可以与左指针和最长边组成三角形。这是因为这些数都比右指针所指的数小,与左指针相加的结果会更大,因此更容易满足三角形的条件。然后,我们可以将右指针向左移动一位,继续检查新的右指针位置与左指针之间的数是否能与最长边组成三角形。
简化逻辑:
排序后的数组使得我们可以更直观地理解问题,并简化算法的实现逻辑。在排序后的数组中,我们只需要关注如何有效地移动指针,而不需要担心数组中的数是否乱序导致的复杂情况。
综上所述,先排序是解决“有效三角形的个数”问题的一个关键步骤,它允许我们利用三角形的性质、减少不必要的比较、优化搜索空间,并简化算法的实现逻辑。 ↩︎