本系列总计六篇文章,是 基于STL实现的笔试题常考七大基本数据结构 该文章在《代码随想录》和《labuladong的算法笔记》题目中的具体实践,每篇的布局是这样的:开头是该数据结构的总结,然后是在不同场景的应用,以及不同的算法技巧。本文是系列第一篇,介绍了数组的相关题目,重点是要掌握双指针算法、二分查找算法。
下面文章是在《代码随想录》和《labuladong的算法笔记》题目中的具体实践:
【笔记】数组
【笔记】链表
【笔记】哈希表
【笔记】字符串
【笔记】栈与队列
【笔记】二叉树
0、总结
- 双指针,快慢双指针(用于数组覆盖),左右双指针(二分查找),滑动窗口(长度最小子数组)
- 有序数组+无重复,必然想到 二分查找 及其变体,找target,找左边界,找右边界,需要坚持闭区间
- 用循环不变量的模拟(螺旋数组)
1、二分查找-左右双指针
算法框架
注意,…号出现的四处位置,根据是否闭区间/左闭右开,是否返回一个索引/索引边界,而不同,下面我都坚持闭区间的写法
int binarySearch(int[] nums, int target) {
int left = 0, right = 1、...;
while(2、...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
3、...
} else if (nums[mid] < target) {
left = 3、...
} else if (nums[mid] > target) {
right = 3、...
}
}
return 4、...;
}
704. 二分查找 - 力扣(LeetCode)
思路:二分查找最基础版本,采用左闭右闭区间,因此注意这四处
1、high初始化指向最末尾元素
2、while中的判断是 <= ,因为当 left == right 时区间依然有意义
3、mid每次必须加减1,相等即返回
4、未找到返回-1
另外,写成 if - else if 形式,将逻辑分支判断展开,更有助于理解
【不足】若target有多个值,该解法只能返回其中一个值
class Solution {
public:
int search(vector<int>& nums, int target) {
int low = 0;
int high = nums.size() - 1; // 1
// 当left == right,区间[left, right]依然有效,所以用 <=
while (low <= high) { // 2
int mid = low + (high - low) / 2;
if (nums[mid] > target) high = mid - 1; // 3
if (nums[mid] < target) low = mid + 1; // 3
if (nums[mid] == target) return mid; // 3
}
return -1; // 4
}
};
35. 搜索插入位置 - 力扣(LeetCode)
思路:二分法的变体,能处理如下四种情况:
- 目标值在数组所有元素之前
- 目标值等于数组中某一个元素
- 目标值插入数组中的位置
- 目标值在数组所有元素之后
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int low = 0;
int high = nums.size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] > target) high = mid - 1;
if (nums[mid] < target) low = mid + 1;
if (nums[mid] == target) return mid;
}
return high + 1;
}
};
!!!以上两种情况,只适合无重复元素+数组升序的版本
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
思路:二分查找变体,能找到target的左右边界
寻找target在数组里的左右边界,有如下三种情况:
- 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
- 情况二:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
- 情况三:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
注意:寻找右边界时,nums[mid] > target时,直接减小right = mid - 1,**当 nums[mid] == target
时,不要立即返回,而是增大「搜索区间」的左边界 left
,通过left = mid + 1,rightBorder = left,使得区间不断向右靠拢,达到锁定右侧边界的目的,nums[mid] < target时,left = mid + 1,因此小于和等于target的情况合并为一种。**左边界同理。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
cout << leftBorder << endl;
cout << rightBorder << endl;
// 情况一,边界值从未被更新,说明 target 在数组范围的右边或者左边
if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
// 情况二,边界值相差大于1,说明中间至少夹着一个target
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
// 情况三,边界值相邻,target 在数组范围中,但是数组中不存在target
return {-1, -1};
}
// 寻找右边界,不包括target
int getRightBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
// 记录初始值
int rightBorder = -2;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] <= target) { // 寻找右边界,找到target时先别返回,继续更新left
left = mid + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
// 记录初始值
int leftBorder = -2;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] >= target) { // 寻找左边界,用 right 来逼近
right = mid - 1;
leftBorder = right;
}
}
return leftBorder;
}
};
69. x 的平方根 - 力扣(LeetCode)
思路:二分查找变体,在1到x中,用二分查找,寻找x的平方根。避免平方运算溢出,应用除法代替。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
class Solution {
public:
int mySqrt(int x) {
if (x == 0 || x == 1) return x;
int left = 1;
int right = x;
while (left <= right) {
int mid = left + (right - left) / 2;
// 用除法判断,避免溢出
int temp = x / mid;
if (temp > mid) { // mid平方小于x,增大左边界
left = mid + 1;
} else if (temp < mid) { // mid平方大于x,减小右边界
right = mid - 1;
} else if (temp == mid) { // 找到x = mid平方就返回
return mid;
}
}
return right;
}
};
367. 有效的完全平方数 - 力扣(LeetCode)
思路:二分搜索!注意在temp = mid时要做判断,如果num可以整除mid则返回true,否则说明出现了 mid * mid < num的情况,需要增大左边界,然后继续循环判断
class Solution {
public:
bool isPerfectSquare(int num) {
if (num == 1) return true;
int left = 0;
int right = num;
while (left <= right) {
int mid = left + (right - left) / 2;
// 用除法判断,避免溢出
int temp = num / mid;
if (temp > mid) { // mid平方小于x,增大左边界
left = mid + 1;
} else if (temp < mid) { // mid平方大于x,减小右边界
right = mid - 1;
} else {
if (num % mid == 0) return true; // num是完全平方数就返回true
else left = mid + 1; // 说明存在余数,即mid^2 < num的情况
}
}
return false;
}
};
2、从前到后原地修改数组-快慢双指针
27. 移除元素 - 力扣(LeetCode)
思路:双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。 要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
因此定义快慢指针,当快指针遇见不需要删除的元素时,才从慢指针开始重新覆盖数组
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
注意,本题不考虑resize的问题
class Solution {
public:
// 返回新长度,同时修改数组
int removeElement(vector<int>& nums, int val) {
int j = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != val) {
nums[j++] = nums[i];
}
}
// 本题不需要考虑数组中超出新长度后面的元素
// nums.resize(j);
return j;
}
};
26. 删除有序数组中的重复项 - 力扣(LeetCode)
思路:定义快慢指针,分别指向第一个和第二个元素,当快、慢指针所指向元素不相等时,才从慢指针指向的下一个元素开始覆盖数组
注意:先slow++再修改,最后返回slow + 1
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() == 1) return 1;
int slow = 0;
for (int fast = 1; fast < nums.size(); ++fast) {
if (nums[slow] != nums[fast]) nums[++slow] = nums[fast];
}
return slow + 1;
}
};
80. 删除有序数组中的重复项 II - 力扣(LeetCode)
思路:双指针,使得出现次数超过两次的元素只出现两次 ,fast指向的元素和slow - 2不相等时,才从slow开始覆盖
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
// 只含1或2个元素,直接返回
if (nums.size() <= 2) return nums.size();
// slow从指向第三个元素开始
int slow = 2;
for (int fast = 2; fast < nums.size(); ++fast) {
// 保证元素至多出现两次
if (nums[fast] != nums[slow - 2]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};
283. 移动零 - 力扣(LeetCode)
思路:双指针,每次fast遇到非0元素时,和slow交换,不会改变原来非0元素的相对位置
class Solution {
public:
void moveZeroes(vector<int>& nums) {
if (nums.size() == 1) return;
int slow = 0;
// 每次把fast的非零值和slow指向的0交换
for (int fast = 0; fast < nums.size(); ++fast) {
if (nums[fast] != 0) {
swap(nums[slow], nums[fast]);
++slow;
}
}
}
};
3、从后到前遍历-双指针、左右双指针
844. 比较含退格的字符串 - 力扣(LeetCode)
- 思路一:双指针,一次操作两个字符串,从后向前逐位比较,先删除两个字符串目前遇到的#,直到指向非#字符或遍历完串。消除完#后,进行比较,若不相等直接返回false,否则进行下一轮比较
class Solution {
public:
bool backspaceCompare(string s, string t) {
int sSkipNum = 0;
int tSkipNum = 0;
int i = s.size() - 1;
int j = t.size() - 1;
// 从后向前,逐位消除,逐位比较
while (1) {
// 如果遇到#,进行消除,直到当前指向不为#
while (i >= 0) {
if (s[i] == '#') ++sSkipNum;
else if (s[i] != '#') {
if (sSkipNum > 0) --sSkipNum;
else break;
}
--i;
}
while (j >= 0) {
if (t[j] == '#') ++tSkipNum;
else if (t[j] != '#') {
if (tSkipNum > 0) --tSkipNum;
else break;
}
--j;
}
// 消除完#后,进行比较,准备下一轮消除和比较
if (i < 0 || j < 0) break; // s或t遍历到头了
if (s[i] != t[j]) return false;
--i;
--j;
}
// 同时遍历完毕
if (i == -1 && j == -1) return true;
return false;
}
};
- 思路二:将string视为栈,利用栈实现匹配,将非#字符读入栈,若遇到#,弹出栈顶元素
class Solution {
public:
bool backspaceCompare(string s, string t) {
string ss;
string tt;
for (char c : s) {
if (c != '#') ss.push_back(c);
else if (!ss.empty()) ss.pop_back();
}
for (char c : t) {
if (c != '#') tt.push_back(c);
else if (!tt.empty()) tt.pop_back();
}
return ss == tt;
}
};
977. 有序数组的平方 - 力扣(LeetCode)
思路:左右双指针一次遍历,时间复杂度O(N),但是本题无法原地修改,只能引入空间复杂度O(N)的新数组用来存放平方后的数组。从两边向中间遍历,每次把较大的数从后向前写入到新数组中。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
vector<int> result(nums.size(), 0);
int index = result.size() - 1;
while (left <= right && index >= 0) {
if (abs(nums[right]) >= abs(nums[left])) {
result[index--] = pow(nums[right], 2);
--right;
} else {
result[index--] = pow(nums[left], 2);
++left;
}
}
return result;
}
};
4、滑动窗口-左右双指针+哈希表
算法框架
其中两处 ...
表示的更新窗口数据的地方,到时候直接往里面填就行了,坚持左闭右开版本,减少边界处理时候不必要的麻烦
/* 滑动窗口算法框架 */
void slidingWindow(string s) {
unordered_map<char, int> window;
int left = 0, right = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
1、...
/*** debug 输出的位置 ***/
// 注意在最终的解法代码中不要 print
// 因为 IO 操作很耗时,可能导致超时
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
2、...
}
}
return 需要的值;
}
4.1 基于数组的滑动窗口
209. 长度最小的子数组 - 力扣(LeetCode)
- hash思路:扩大窗口,将数组元素入windows,计算sum,一旦sum >= target满足条件就要更新result,然后缩小窗口。循环该操作
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT_MAX;
int sum = 0;
// 双指针最难版本,滑动窗口
unordered_map<int, int> window;
int left = 0;
int right = 0;
while (right < nums.size()) {
int i = nums[right];
right++;
window[i]++;
sum += i;
while (sum >= target) {
result = min(result, right - left);
int j = nums[left];
left++;
window[j]--;
sum -= j;
}
}
return result == INT32_MAX ? 0 : result;
}
};
- 非hash思路:本题只涉及求和,不涉及key的value数目,故可不用哈希
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT_MAX;
int sum = 0;
int left = 0;
for (int right = 0; right < nums.size(); ++right) {
// sum小于target时,不断扩大窗口
sum += nums[right];
while (sum >= target) {
result = min(result, right - left + 1);
// 满足条件,不断缩小窗口
sum -= nums[left++];
}
}
return result == INT32_MAX ? 0 : result;
}
};
904. 水果成篮 - 力扣(LeetCode)
- 思路,本题其实求 至多包含两种元素的最长连续子序列,与上题同理,但应注意,本题用size方法统计key的种类,通过window[j]–收缩窗口后,最后key对应的value为0时,但是key本身还存在,需要手动删除key,否则window.size()的结果不会改变
class Solution {
public:
int totalFruit(vector<int>& fruits) {
unordered_map<int, int> window;
int left = 0, right = 0;
int result = 0;
while (right < fruits.size()) {
// 元素种类不超过2时扩大窗口
int i = fruits[right++];
window[i]++;
// 元素种类大于2时缩小窗口
while (window.size() > 2) {
int j = fruits[left++];
window[j]--;
// 前面用size计算key的种类,因此key对应的value为0时,key本身还存在,需要手动删除key
if (window[j] == 0) window.erase(j);
}
result = max(result, right - left);
}
return result;
}
};
4.2 基于字符串的滑动窗口
3. 无重复字符的最长子串 - 力扣(LeetCode)
思路:
双指针 的最难版本,滑动窗口,辅助数据结构用unordered_map,map内始终维护着不重复的子串,未遇到重复元素,right++不断扩大窗口,若map中出现重复元素,left++减小窗口直到重复元素被删除
扩大窗口和缩小窗口的操作对称
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if (s.size() == 0) return 0;
unordered_map<char, int> window;
// 双指针
int left = 0, right = 0;
int result = 0;
while (right < s.size()) {
// 未遇到重复字符串就一直加,right右移
char c = s[right];
right++;
window[c]++;
// 有重复left左移直到删除该重复的字符串
while (window[c] > 1) {
char d = s[left];
left++;
window[d]--;
}
// 收缩完后肯定剩下不重复的字串,记录长度
result = max(result, right - left);
}
return result;
}
};
76. 最小覆盖子串 - 力扣(LeetCode)
思路:
首先,初始化 window
和 need
两个哈希表,记录窗口中的字符和需要凑齐的字符
然后,使用 left
和 right
变量初始化窗口的两端,区间 [left, right)
是左闭右开,所以初始情况下窗口没有包含任何元素
其中 valid
变量表示窗口中满足 need
条件的字符个数,如果 valid
和 need.size
的大小相同,则说明窗口已满足条件,已经完全覆盖了串 T
。
注意,本题需要返回 s 中满足覆盖 t 的最小子串,需要记录符合要求字串的起、止位置,便于返回,此外,只有length被更新时候,才能start = left,不因此能用min()
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> window, need;
for (char ch : t)
need[ch]++;
// 左右指针
int left = 0, right = 0;
// 符合要求字串的起、止位置
int start = 0, length = INT_MAX;
// window中符合need的元素数量
int valid = 0;
while (right < s.size()) {
// 扩大窗口,符合要求的元素才会被记录
char c = s[right];
right++;
// 遇到t中需要的元素,将其放入窗口
if (need.count(c)) {
window[c]++;
// 如果t中的该元素都找到(可能有多个),更新记录
if (window[c] == need[c])
valid++;
}
// 当t可以被s的子串覆盖时,更新长度
while (valid == need.size()) {
// 只有长度被更新时,才能重新记录子串起始位置,因此不能用min
if (right - left < length) {
start = left;
length = right - left;
}
// 收缩窗口
char d = s[left];
left++;
// 当要弹出的元素是t中需要的元素时
if (need.count(d)) {
// 只要少了元素就减少valid
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
return length == INT_MAX ? "" : s.substr(start, length);
}
};
567. 字符串的排列 - 力扣(LeetCode)
思路:
题目实际的含义是,s2
中是否存在一个子串,包含 s1
中所有字符,且不包含其他字符?就是不含冗余元素,s2中找到子串,恰好是s1串的排列,即窗口大小恰好等于s1.size()
对于这道题的解法代码,基本上和最小覆盖子串一模一样,只需要改变两个地方:
1、本题移动 left
缩小窗口的时机是窗口大小大于 t.size()
时,因为排列嘛,显然长度应该是一样的。
2、当发现 valid == need.size()
时,就说明窗口中就是一个合法的排列,所以立即返回 true
。
至于如何处理窗口的扩大和缩小,和最小覆盖子串完全相同。
PS:由于这道题中
[left, right)
其实维护的是一个定长的窗口,窗口大小为t.size()
。因为定长窗口每次向前滑动时只会移出一个字符,所以可以把内层的 while 改成 if,效果是一样的。
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need, window;
for (char c : s1) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s2.size()) {
char c = s2[right];
right++;
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 注意收缩窗口的时机
while (right - left >= s1.size()) {
// 只要s1中元素已经全部被找到,立即返回
if (valid == need.size())
return true;
char d = s2[left];
left++;
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
// 均未找到返回false
return false;
}
};
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
思路:
实际上就是找排列,只不过本题需要返回所有有效的排列的起始下标,上题是找到即返回
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
// 新建数组用于收集结果
vector<int> result;
result.clear();
unordered_map<char, int> need, window;
for (char c : p) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
char c = s[right];
right++;
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
while (right - left >= p.size()) {
// 满足条件不着急返回,存入result
if (valid == need.size())
result.push_back(left);
char d = s[left];
left++;
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
return result;
}
};
5、螺旋遍历数组
54. 螺旋矩阵 - 力扣(LeetCode)
剑指 Offer 29. 顺时针打印矩阵 - 力扣(LeetCode)
思路:按照右、下、左、上的顺序遍历数组,并使用四个变量圈定未遍历元素的边界
注意:如果考察逆时针,同理
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.empty()) return {};
vector<int> result;
// 四个边界
int left = 0;
int right = matrix[0].size() - 1;
int top = 0;
int bottom = matrix.size() - 1;
// res.size() == m * n 则遍历完整个数组
while (result.size() < matrix.size() * matrix[0].size()) {
if (top <= bottom) {
for (int i = left; i <= right; i++)
result.push_back(matrix[top][i]);
top++;
}
if (left <= right) {
for (int i = top; i <= bottom; i++)
result.push_back(matrix[i][right]);
right--;
}
if (top <= bottom) {
for (int i = right; i >= left; i--)
result.push_back(matrix[bottom][i]);
bottom--;
}
if (left <= right) {
for (int i = bottom; i >= top; i--)
result.push_back(matrix[i][left]);
left++;
}
}
return result;
}
};
59. 螺旋矩阵 II - 力扣(LeetCode)
思路:现在把矩阵换成方阵,然后由输出元素改为向矩阵中填值,稍微修改上题的代码即可
注意:如果考察逆时针,同理
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> matrix(n, vector<int>(n, 0));
int num = 1;
// 四个边界
int left = 0;
int right = n - 1;
int top = 0;
int bottom = n - 1;
// 打印
while (num <= n * n) {
if (top <= bottom) {
for (int i = left; i <= right; i++)
matrix[top][i] = num++;
top++;
}
if (left <= right) {
for (int i = top; i <= bottom; i++)
matrix[i][right] = num++;
right--;
}
if (top <= bottom) {
for (int i = right; i >= left; i--)
matrix[bottom][i] = num++;
bottom--;
}
if (left <= right) {
for (int i = bottom; i >= top; i--)
matrix[i][left] = num++;
left++;
}
}
return matrix;
}
};
6、顺时针旋转二维数组90度
48. 旋转图像 - 力扣(LeetCode)
- 思路一:先以主对角线为对称轴进行对称翻转,然后在按每行分别进行翻转,即可实现旋转90度
注意:
1、若问180度,270度(逆时针90度)可以重复上述操作多次
2、考察逆时针旋转90度的话,可以先以副对角线为对称轴进行对称翻转,然后在按每行分别进行翻转
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// 先主对角线做对称
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
swap(matrix[i][j], matrix[j][i]);
}
}
// 然后反转二维矩阵的每一行
for (int i = 0; i < n; i++)
reverse(matrix[i]);
}
void reverse(vector<int>& row) {
for (int i = 0, j = row.size() - 1; i < j; i++, j--)
swap(row[i], row[j]);
}
};
- 思路二:先上下翻转,再对角翻转,swap两种用法,翻转一对单个int元素和翻转一对数组
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
for (int i = 0; i < n / 2; i++) matrix[i].swap(matrix[n - 1 - i]);
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
swap(matrix[i][j], matrix[j][i]);
}
}
}
};
7、接雨水-左右双指针
42. 接雨水 - 力扣(LeetCode)
思路:某个位置能容纳的雨水量和左、右柱子高度较小者,以及该位置本身的高度有关。双指针记录位置,l_max
是 height[0..left]
中最高柱子的高度,r_max
是 height[right..end]
的最高柱子的高度,这两个值的较小者减去该位置的高度,即为该位置能容纳的雨水量。
难点:只知道 height[left]
和 height[right]
的高度,能算出 left
和 right
之间能够盛下多少水吗?不能,因为不知道 left
和 right
之间每个柱子具体能盛多少水,得通过每个柱子的 l_max
和 r_max
来计算才行,即本题考虑柱子宽度
class Solution {
public:
int trap(vector<int>& height) {
int left = 0, right = height.size() - 1;
int l_max = 0, r_max = 0;
int water = 0;
while (left < right) {
// 记录高度
l_max = max(l_max, height[left]);
r_max = max(r_max, height[right]);
// 较小者决定雨水量
if (l_max < r_max) {
water += l_max - height[left];
left++;
} else {
water += r_max - height[right];
right--;
}
}
return water;
}
};
11. 盛最多水的容器 - 力扣(LeetCode)
思路:本题不考虑柱子宽度,两柱子间盛水量只由较低的柱子高度和两柱子之间距离决定
注意:if 语句移动较低的一边原因,是由于盛水量只由较低的柱子高度决定,如果移动较低的那一边,那条边可能会变高,使得矩形的高度变大,进而就「有可能」使得矩形的面积变大;相反,如果移动较高的那一边,矩形的高度是无论如何都不会变大的,所以不可能使矩形的面积变得更大
class Solution {
public:
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1;
int area = 0;
while (left < right) {
int tempArea = (right - left) * min(height[left], height[right]);
area = max(area, tempArea);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return area;
}
};