盛水最多的容器(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