【双指针】算法中等难度题目题解——盛水最多的容器、有效三角形的个数、三数之和

盛水最多的容器(medium)

1. 题⽬描述:

给定⼀个⻓度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) (i, height[i])。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的⽔。
返回容器可以储存的最⼤⽔量。
说明:你不能倾斜容器。
⽰例 1:
输⼊: [1,8,6,2,5,4,8,3,7]
输出: 49
解释:图中垂直线代表输⼊数组 [1,8,6,2,5,4,8,3,7] 。在此情况下,容器能够容纳⽔(表示为蓝⾊部分)的最⼤值为 49

2. 解法⼀(暴⼒求解)(会超时):

算法思路:
枚举出能构成的所有容器,找出其中容积最⼤的值。
容器容积的计算⽅式:
设两指针 i , j ,分别指向⽔槽板的最左端以及最右端,此时容器的宽度为 j - i 。由于
容器的⾼度由两板中的短板决定,因此可得容积公式 : v = (j - i) * min(
height[i], height[j])
算法代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int ret = 0;
// 两层 for 枚举出所有可能出现的情况
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
// 计算容积,找出最⼤的那⼀个
ret = max(ret, min(height[i], height[j]) * (j - i));
}
}
return ret;
}
};

3. 解法⼆(对撞指针):

算法思路:
设两个指针 left right 分别指向容器的左右两个端点,此时容器的容积 :
v = (right - left) * min( height[right], height[left])
容器的左边界为 height[left] ,右边界为 height[right]
为了⽅便叙述,我们假设「左边边界」⼩于「右边边界」。
如果此时我们固定⼀个边界,改变另⼀个边界,⽔的容积会有如下变化形式:
容器的宽度⼀定变⼩。
由于左边界较⼩,决定了⽔的⾼度。如果改变左边界,新的⽔⾯⾼度不确定,但是⼀定不会超
过右边的柱⼦⾼度,因此容器的容积可能会增⼤。
如果改变右边界,⽆论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会
超过现在的⽔⾯⾼度,但是由于容器的宽度减⼩,因此容器的容积⼀定会变⼩的。
由此可⻅,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继
续去判断下⼀个左右边界。
当我们不断重复上述过程,每次都可以舍去⼤量不必要的枚举过程,直到 left right
遇。期间产⽣的所有的容积⾥⾯的最⼤值,就是最终答案。
算法代码:
class Solution
{
public:
 int maxArea(vector<int>& height) 
 {
 int left = 0, right = height.size() - 1, ret = 0;
 while(left < right)
 {
 int v = min(height[left], height[right]) * (right - left);
 ret = max(ret, v);
 // 移动指针

 if(height[left] < height[right]) left++;
 else right--;
 }
 return ret;
 }
};

有效三⻆形的个数(medium)

1. 题⽬描述:

给定⼀个包含⾮负整数的数组 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
解释:
4,2,3
4,2,4
4,3,4
2,3,4

2. 解法⼀(暴⼒求解)(会超时):

算法思路:
三层 for 循环枚举出所有的三元组,并且判断是否能构成三⻆形。
虽然说是暴⼒求解,但是还是想优化⼀下:
判断三⻆形的优化:
如果能构成三⻆形,需要满⾜任意两边之和要⼤于第三边。但是实际上只需让较⼩的两条边
之和⼤于第三边即可。
因此我们可以先将原数组排序,然后从⼩到⼤枚举三元组,⼀⽅⾯省去枚举的数量,另⼀⽅
⾯⽅便判断是否能构成三⻆形。
算法代码:
class Solution {
public:
 int triangleNumber(vector<int>& nums) {
 // 1. 排序
 sort(nums.begin(), nums.end());
 int n = nums.size(), ret = 0;
 // 2. 从⼩到⼤枚举所有的三元组
 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;
 }
};

3.解法⼆(排序 + 双指针):

算法思路:
先将数组排序。
根据「解法⼀」中的优化思想,我们可以固定⼀个「最⻓边」,然后在⽐这条边⼩的有序数组中找
出⼀个⼆元组,使这个⼆元组之和⼤于这个最⻓边。由于数组是有序的,我们可以利⽤「对撞指
针」来优化。
设最⻓边枚举到 i 位置,区间 [left, right] i 位置左边的区间(也就是⽐它⼩的区
间):
如果 nums[left] + nums[right] > nums[i]
说明 [left, right - 1] 区间上的所有元素均可以与 nums[right] 构成⽐
nums[i] ⼤的⼆元组
满⾜条件的有 right - left
此时 right 位置的元素的所有情况相当于全部考虑完毕, right-- ,进⼊下⼀轮判断
如果 nums[left] + nums[right] <= nums[i]
说明 left 位置的元素是不可能与 [left + 1, right] 位置上的元素构成满⾜条件
的⼆元组
left 位置的元素可以舍去, left++ 进⼊下轮循环
算法代码:
class Solution
{
public:
 int triangleNumber(vector<int>& nums) 
 {
 // 1. 优化
 sort(nums.begin(), nums.end());
 // 2. 利⽤双指针解决问题
 int ret = 0, n = nums.size();
 for(int i = n - 1; i >= 2; i--) // 先固定最⼤的数
 {
 // 利⽤双指针快速统计符合要求的三元组的个数
 int left = 0, right = i - 1;
 while(left < right)
 {
 if(nums[left] + nums[right] > nums[i])
 {
 ret += right - left;
 right--;
 }
 else
 {
 left++;
 }
 }
 }
 return ret;
 }
};

三数之和(medium)

1. 题⽬描述:

给你⼀个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满⾜ i != j、i != k 且 j
!= k ,同时还满⾜ nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
⽰例 1:
输⼊:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
⽰例 2:
输⼊:nums = [0,1,1]
输出:[]
解释:唯⼀可能的三元组和不为 0 。
⽰例 3:
输⼊:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯⼀可能的三元组和为 0 。
提⽰:
3 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5

2. 解法(排序+双指针):

算法思路:
本题与两数之和类似,是⾮常经典的⾯试题。
与两数之和稍微不同的是,题⽬中要求找到所有「不重复」的三元组。那我们可以利⽤在两数之和
那⾥⽤的双指针思想,来对我们的暴⼒枚举做优化:
i. 先排序;
ii. 然后固定⼀个数 a
iii. 在这个数后⾯的区间内,使⽤「双指针算法」快速找到两个数之和等于 -a 即可。
但是要注意的是,这道题⾥⾯需要有「去重」操作~
i. 找到⼀个结果之后, left right 指针要「跳过重复」的元素;
ii. 当使⽤完⼀次双指针算法之后,固定的 a 也要「跳过重复」的元素。
C++ 算法代码:
class Solution
{
public:
 vector<vector<int>> threeSum(vector<int>& nums) 
 {
 vector<vector<int>> ret;
 // 1. 排序
 sort(nums.begin(), nums.end());
 // 2. 利⽤双指针解决问题
 int n = nums.size();
 for(int i = 0; i < n; ) // 固定数 a
 {
 if(nums[i] > 0) break; // ⼩优化
 int left = i + 1, right = n - 1, target = -nums[i];
 while(left < right)
 {
 int sum = nums[left] + nums[right];
 if(sum > target) right--;
 else if(sum < target) left++;
 else
 {
 ret.push_back({nums[i], nums[left], nums[right]});
 left++, right--;
 // 去重操作 left 和 right
 while(left < right && nums[left] == nums[left - 1]) left++;
 while(left < right && nums[right] == nums[right + 1]) 
right--;

 }
 }
 // 去重 i 
 i++;
 while(i < n && nums[i] == nums[i - 1]) i++;
 }
 return

  • 26
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风铃子加油

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值