一:LeetCode中的盛最多水的容器

暴力破解
当我们刚拿到这道题的时候,最直观想到的办法就是暴力破解,通过两个for循环,通过计算所有所有可能的情况,然后将其中最大的结果进行返回,相信肯定会有人这样想,现在我们就来通过这样的方式看看能不能通过这道题。
class Solution {
public:
int maxArea(vector<int>& height) {
int ret = 0;
for (int i = 0; i < height.size(); i++) {
for (int j = i + 1; j < height.size(); j++) {
int num = (j - i) * min(height[i], height[j]);
if (num > ret) {
ret = num;
}
}
}
return ret;
}
};

可以看到,如果我们直接使用暴力破解的方式是会超时的,所以这种方式不可取,既然这种方式不可取,那么我们就来使用另外一种方式进行破解------就是双指针的思想。
双指针
大家可能会说高手是怎么想到能用双指针的呢,我们只需要通过分析就可以看到:
[1,8,6,2,5,4,8,3,7]
这是题目示例中的一个例子,我们现在就来想一想题目要求我们计算容器中可以储藏的最大水量V,V = X * Y(X就是横坐标,Y就代表纵坐标),所以想要V最大,我们就要让X和Y的值都尽可能的大。
所以我们先让我们的X值取到最大,也就是取区间的左右两个端点,这个时候X的值是最大的,但是由于我们的左端点是1,所以我们的Y值只能取1,肉眼可见这个时候的V不是最大的,那么我们应该如何在保证V变的更大呢?
这个时候由于我们的X已经是最大的了,但是我们的Y值确实很小,要想让V变大,就得让我们的X值变小之后,同时Y变大,这样才能让我们的V变大,所以我们只需要让X变小的同时,让两端点中Y小的那一方进行移动,这样就可以增大Y值,这样我们的V就可以变大。
所以通过这样的方式,我们只需要遍历一遍数组就可以解决这道题,时间复杂度为O(n),不需要像暴力破解那样O(n²)的时间复杂度。


class Solution {
public:
int maxArea(vector<int>& height) {
int ret = 0;
int left = 0;
int right = height.size() - 1;
while (left < right) {
int num = (right - left) * min(height[left], height[right]);
if (num > ret) {
ret = num;
}
if (height[left] < height[right]) {
left++;
}
else
{
right--;
}
}
return ret;
}
};
二:LeetCode中的有效三角形的个数

暴力破解
首先,我们得知道三角形得三条边是两条较小的边之和要大于另一条最大的边,所以我们可以先进行排序,这样就保证了我们的整个数组中的前两个数就是三角形中较小的那两天边,接下来只要判断两边之和大于第三边即可,所有我们通过三层for循环,然后对其进行判断即可。

class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end());
int count = 0;
for (int i = 0; i < (int)nums.size() - 2; i++) {
for (int j = i + 1; j < nums.size() - 1; j++) {
for (int x = j + 1; x < nums.size(); x++) {
if (nums[i] + nums[j] > nums[x]) {
count++;
}
}
}
}
return count;
}
};
所以可以看到这种暴力解法是通不过测试用例的,它一定会超时的,所以我们得使用其它方法进行破解。
双指针
[2,2,3,4,4,5,6,6,9]

我们通过这个例子来看看我们应该如何破解这道题,首先既然我们要找到a + b > c,现在我们就先来固定整个数组中最大的数字。将7作为我们的c,然后将2作为我们的a,将6作为我们的b,这个时候,2 + 6 > 7 ,所以我们确定这就是一个三角形。
但是我们可以仔细观察,就会发现,由于我们的数组是排序之后是递增的,那么既然当a取开始的2,与末尾的6进行相加,大于c的话,那么当a移动到下一位的时候,它们两进行相加的结果肯定也是大于c的,所以这个时候,我们就不需要再进行判断,只需要知道a和b之间有多少个元素,就代表我们会有多少个三角形的形成。
所以既然a进行移动的时候是增加两边之和,所以我们直接通过计算它两之间有多少个元素就可以知道有多少个三角形了。

那么接下来我们可以通过移动b来适当减小两边之和,看看新区间的两边之和是否依旧大于第三边,当b移动到5这个位置的时候,我们可以知道a+b = c,这个时候已经构不成三角形了,那么此时[a,b]这个区间中的值就不会再相加之后大于c,所以这个时候我们就需要增大我们左边的值,将a进行移动,这样才有可能保证我们接下来的两边之后可以大于第三边。
通过这样的方式,我们就可以成功解决这道题了。

class Solution {
public:
int triangleNumber(vector<int>& nums) {
int count = 0;
sort(nums.begin(), nums.end());
int end = nums.size() - 1;
while (end > 0) {
int left = 0;
int right = end - 1;
while (left < right) {
if (nums[left] + nums[right] > nums[end]) {
count += right -left;
right--;
} else {
left++;
}
}
end--;
}
return count;
}
};
三:LeetCode中的两数之和

暴力破解
同样这道题,如果我们使用暴力破解的方法的时候,结果会和上面的题目一样,出现超时的问题,结果如下:

class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> ret;
for (int i = 0; i < numbers.size(); i++) {
for (int j = i + 1; j < numbers.size(); j++) {
if (numbers[i] + numbers[j] == target) {
ret.push_back(i + 1);
ret.push_back(j + 1);
break;
}
}
}
return ret;
}
};
所以我们就必须使用另外一种方法来解决这道题,就是双指针思想。
双指针
这道题目中已经明确显示,所给的数组都是递增的,然后让我们查找出数组中的两数之和的结果等于target的两个数。现在我们准备两个指针,一个指针指向数组的最左边,一个指向数组的最右边。


class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> ret;
int left = 0;
int right = numbers.size() - 1;
while (left < right) {
if (numbers[left] + numbers[right] == target) {
ret.push_back(left + 1);
ret.push_back(right + 1);
break;
} else if (numbers[left] + numbers[right] < target) {
left++;
} else {
right--;
}
}
return ret;
}
};
四:LeetCode中的三数之和

其实看到这道题,结合之前我们分析两数之和是如何解决的,对于这道题我们可以使用两数之和的办法解决掉这道三数之和的问题;步骤如下:
- 首先我们进行排序。
- 再固定一个数。
- 根据题目要求,三数之和等于0,所以利用双指针的思想,找到两个数之和等于固定数的相反数即可。
但是这道题又有两个细节就是:
- 去重,因为这道题目要求我们必须返回的结果是不含重复元素的三元组,所以我们要把重复的三元组去掉才可以。那么我们应该如何去重呢?答案其实很简单,就是当我们找到一种结果之后,让我们的指针都跳过相同的元素,这样就可以保证我们返回的三元组不含重复的情况,
- 不漏,就是找到一种情况之后,我们不能像两数之和一样直接结束,应该继续缩小区间,查看时候还有新的三元组需要我们返回。


class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ret;
sort(nums.begin(), nums.end());
int cur = 0;
int left = 0;
int right = 0;
while (cur < nums.size() && nums[cur] <= 0) {
int target = -nums[cur];
left = cur + 1;
right = nums.size() - 1;
while (left < right) {
if (nums[left] + nums[right] == target) {
vector<int> s = {nums[cur], nums[left], nums[right]};
ret.push_back(s);
left++;
right--;
while (left < right && nums[left] == nums[left - 1]) {
left++;
}
while (left < right && nums[right] == nums[right + 1]) {
right--;
}
} else if (nums[left] + nums[right] < target) {
left++;
} else {
right--;
}
}
cur++;
while (cur < nums.size() && nums[cur] == nums[cur - 1]) {
cur++;
}
}
return ret;
}
};
总结
| 题目 | 核心技巧 |
|---|---|
| 盛水容器 | 移动短板 |
| 三角形个数 | 批量统计 |
| 两数之和 | 有序双指针 |
| 三数之和 | 固定 + 双指针 + 去重 |
双指针的本质:利用"有序性"减少搜索空间