一、盛最多水的容器
题目描述:
给定一个长度为 n 的整数数组 height,有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i])。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水,返回容器可以存储的最大水量。
说明:不能倾斜容器。
示例:
- 输入:
height = [1,8,6,2,5,4,8,3,7],输出:49 - 输入:
height = [1,1],输出:1
解题思路:
采用双指针法优化暴力枚举的时间复杂度:
- 初始化左右指针
left(数组开头)、right(数组末尾),记录最大面积ret。 - 容器面积由「短边高度」和「指针间距」决定:
面积 = min(height[left], height[right]) * (right - left)。 - 移动策略:每次移动较短边的指针(若移动长边,间距减小且短边不变/更小,面积必然减小;移动短边可能找到更长的边,从而增大面积)。
- 遍历过程中持续更新最大面积,直到左右指针相遇。
完整代码:
cpp
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(v, ret);
if(height[left] < height[right]) left++;
else right--;
}
return ret;
}
};
复杂度分析:
- 时间复杂度 :O(n)O(n)O(n)。仅遍历数组一次,左右指针最多移动 nnn 次。
- 空间复杂度 :O(1)O(1)O(1)。仅使用常数级额外变量。
二、有效三角形的个数
题目描述:
给定一个包含非负整数的数组 nums,返回其中可以组成三角形三条边的三元组个数。
三角形条件:任意两边之和大于第三边。
示例:
- 输入:
nums = [2,2,3,4],输出:3(有效组合:[2,3,4](两个2分别组合)、[2,2,3]) - 输入:
nums = [4,2,3,4],输出:4
解题思路:
先排序数组,利用三角形的简化性质(排序后,若较小两边之和大于最大边,则必然满足三角形条件):
- 排序数组,使
nums[a] ≤ nums[b] ≤ nums[c],只需判断nums[a] + nums[b] > nums[c]。 - 固定最大边
nums[i](从数组末尾往前遍历),用双指针left=0、right=i-1找符合条件的较小两边:- 若
nums[left] + nums[right] > nums[i],则right-left个组合(left到right-1都满足),right左移。 - 否则
left右移,尝试更大的较小边。
- 若
完整代码:
cpp
class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end());
int ret = 0;
for(int i = nums.size() - 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;
}
};
复杂度分析:
- 时间复杂度 :O(n2)O(n^2)O(n2)。排序占 O(nlogn)O(n\log n)O(nlogn),固定最大边的循环+双指针遍历占 O(n2)O(n^2)O(n2),总复杂度由后者主导。
- 空间复杂度 :O(logn)O(\log n)O(logn)。仅排序所需的递归栈/临时空间。
三、查找总价格为目标值的两个商品
题目描述:
购物车内的商品价格按升序记录于数组 price,请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。
示例:
- 输入:
price = [3, 9, 12, 15], target = 18,输出:[3,15]或[15,3] - 输入:
price = [8, 21, 27, 34, 52, 66], target = 61,输出:[27,34]或[34,27]
解题思路:
利用数组升序排列的特性,采用双指针法:
- 初始化
left=0(数组开头)、right=price.size()-1(数组末尾)。 - 计算当前和
price[left] + price[right]:- 若和小于
target,left右移(需要更大的数)。 - 若和大于
target,right左移(需要更小的数)。 - 若和等于
target,直接返回这两个数。
- 若和小于
完整代码:
cpp
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target) {
int left = 0, right = price.size() - 1;
while(left < right){
if(price[left] + price[right] < target) left++;
else if(price[left] + price[right] == target) return {price[left], price[right]};
else right--;
}
return {-1, -1};
}
};
复杂度分析:
- 时间复杂度 :O(n)O(n)O(n)。仅遍历数组一次,双指针最多移动 nnn 次。
- 空间复杂度 :O(1)O(1)O(1)。仅使用常数级额外变量。
四、三数之和
题目描述:
给你一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k,同时满足 nums[i] + nums[j] + nums[k] == 0。请返回所有和为 0 且不重复的三元组(答案中不可包含重复的三元组)。
示例:
- 输入:
nums = [-1,0,1,2,-1,-4],输出:[[-1,-1,2],[-1,0,1]] - 输入:
nums = [0,1,1],输出:[]
解题思路:
先排序数组,结合固定单元素+双指针的策略,同时处理去重:
- 排序数组,便于去重和双指针查找。
- 固定第一个元素
nums[i]:- 若
nums[i] > 0,直接break(排序后后续数更大,和不可能为0)。 - 若
nums[i]与前一个数重复,跳过(避免重复三元组)。
- 若
- 用双指针
left=i+1、right=nums.size()-1找nums[left] + nums[right] = -nums[i]:- 找到符合条件的三元组后,
left、right分别跳过重复元素。 - 否则根据和的大小移动指针。
- 找到符合条件的三元组后,
完整代码:
cpp
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ret;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); ) {
if(nums[i] > 0) break;
int left = i + 1, right = nums.size() - 1, target = -nums[i];
while(left < right) {
if(nums[left] + nums[right] < target) left++;
else if (nums[left] + nums[right] > target) right--;
else {
ret.push_back({nums[i], nums[left++], nums[right--]});
while(left < right && nums[left] == nums[left - 1]) left++;
while(left < right && nums[right] == nums[right + 1]) right--;
}
}
i++;
while(i < nums.size() && nums[i] == nums[i - 1]) i++;
}
return ret;
}
};
复杂度分析:
- 时间复杂度 :O(n2)O(n^2)O(n2)。排序占 O(nlogn)O(n\log n)O(nlogn),固定元素+双指针遍历占 O(n2)O(n^2)O(n2)。
- 空间复杂度 :O(logn)O(\log n)O(logn)(排序所需空间),存储结果的空间不计入额外复杂度。
五、四数之和
题目描述:
给你一个由 n 个整数组成的数组 nums 和一个目标值 target,请找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]:
- 0≤a,b,c,d<n0 ≤ a, b, c, d < n0≤a,b,c,d<n 且互不相同
- nums[a]+nums[b]+nums[c]+nums[d]==targetnums[a] + nums[b] + nums[c] + nums[d] == targetnums[a]+nums[b]+nums[c]+nums[d]==target
答案可以按任意顺序返回。
示例:
- 输入:
nums = [1,0,-1,0,-2,2], target = 0,输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]] - 输入:
nums = [2,2,2,2,2], target = 8,输出:[[2,2,2,2]]
解题思路:
在三数之和的基础上扩展为固定双元素+双指针,同时注意数值溢出问题:
- 排序数组,便于去重和双指针查找。
- 固定前两个元素
nums[i]、nums[j]:- 若
nums[i]与前一个数重复,跳过;若nums[j]与前一个数(且j > i+1)重复,跳过。
- 若
- 用双指针
left=j+1、right=nums.size()-1找四数之和等于target:- 计算和时用
long long避免整数溢出。 - 找到符合条件的四元组后,
left、right分别跳过重复元素。 - 否则根据和的大小移动指针。
- 计算和时用
完整代码:
cpp
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
int n=nums.size();
vector<vector<int>> ret;
for(int i=0;i<n-3;)
{
for(int j=i+1;j<n-2;)
{
int left=j+1,right=n-1;
while(left<right)
{
long long sum=(long)nums[i]+nums[j]+nums[left]+nums[right];
if(sum>target)
right--;
else if(sum<target)
left++;
else
{
ret.push_back({nums[i],nums[j],nums[left],nums[right]});
left++;right--;
while(left<right&&nums[left]==nums[left-1]) left++;
while(left<right&&nums[right]==nums[right+1]) right--;
}
}
++j;
while(j<n&&nums[j]==nums[j-1]) j++;
}
++i;
while(i<n&&nums[i]==nums[i-1]) i++;
}
return ret;
}
};
复杂度分析:
- 时间复杂度 :O(n3)O(n^3)O(n3)。排序占 O(nlogn)O(n\log n)O(nlogn),两层固定循环+双指针遍历占 O(n3)O(n^3)O(n3)。
- 空间复杂度 :O(logn)O(\log n)O(logn)(排序所需空间),存储结果的空间不计入额外复杂度。