从数组中寻找周长最小的三角形,常规的思路是先排序,再从小到大依次搜索,再添加一些小优化就OK了。
假设最短三角形的三条边依次是a、b、c(a<b<c),通过分析我们可以知道,b、c在排序后的数组中一定是相邻的,因为如果b、c中间还有数据(设为x),那么a、b、x会形成一个更短的三角形,与假设矛盾。
如此,我们仅需将c设为遍历变量,便可以直接确定b,接下来只需要找到a就可以了,根据三角形性质,我们有a>c-b,借助二分查找法我们可以查找到0~b范围内比c-b大的最小值,这个值就是我们要的a。
需要注意的是,从左到右按以上逻辑遍历数组查找到的第一组数据,不一定是最终结果(最优解),因为以上算法实际上仅能保证b+c是最小的,而不能保证b+c+a也是最小的,考虑一个实际例子:2,88,90,100,101,我们查找到的第一组数据是88,90,100,周长为278,但实际上答案是2,100,101,周长为203,因此我们还要继续遍历,看看后面有没有更短的三角形。
后续遍历的终止条件是已有周长<2c+1,因为当我们确定c时,由它所构成的三角形的最短周长为:c+b+(c-b+1)=2c+1,这个值越往后遍历越大,因此当2c+1比已知的答案更大时,便没有继续遍历的必要了。
参考代码如下,由于题目已经保证了数据的合法性,因此这里忽略了很多合法性检查:
class Solution { public: //使用二分法寻找三条边中最小的边,第21行的条件保证了该边一定存在 int find_shortest_edge(vector<int>& nums, int endIdx, int minLimit) { int leftIdx = 0, rightIdx = endIdx, mid = 0; while (leftIdx < rightIdx) { mid = (leftIdx + rightIdx) / 2; if (nums[mid] < minLimit) leftIdx = mid + 1; else if (nums[mid] >= minLimit) rightIdx = mid; } return nums[leftIdx]; } int hongstriangle(vector<int>& nums) { sort(nums.begin(), nums.end()); unsigned int shortestPerimeter = UINT32_MAX; unsigned int currPerimeter = 0; for (int i = 2; shortestPerimeter == UINT32_MAX || (i < nums.size() && shortestPerimeter > 2 * nums[i]); i++) { if (nums[i - 2] + nums[i - 1] > nums[i]) { currPerimeter = nums[i] + nums[i - 1] + find_shortest_edge(nums, i - 2, nums[i] - nums[i - 1] + 1); if (currPerimeter < shortestPerimeter) { shortestPerimeter = currPerimeter; } } } return shortestPerimeter; } };