力扣Hot100题单个人计划c++版(一)
力扣Hot100题单个人计划c++版(二)
力扣Hot100题单个人计划c++版(三)
力扣Hot100题单个人计划c++版(四)
力扣Hot100题单个人计划c++版(五)
刷题链接:力扣Hot 100
每日一题,每日一更,白板手写。
力扣Hot 100
1.两数之和
8.30打卡
暴力就不写了,哈希判断。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> hashmap;
for(int i=0;i<nums.size();i++){
auto it=hashmap.find(target-nums[i]);
if(it==hashmap.end())
hashmap.insert(pair<int,int>(nums[i],i));
else return vector<int>{it->second,i};
}
return vector<int>{-1,-1};
}
};
2.两数相加
8.31打卡
对应位置相加即可,但需要注意最高位可能会进位。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* ans=nullptr;
ListNode* nxt=nullptr;
int carry=0;
while(l1||l2){
int n1=l1?l1->val:0;
int n2=l2?l2->val:0;
int x=n1+n2+carry;
if(ans==nullptr)ans=nxt=new ListNode(x%10);
else{
nxt->next=new ListNode(x%10);
nxt=nxt->next;
}
carry=x/10;
if(l1)l1=l1->next;
if(l2)l2=l2->next;
}
if(carry)nxt->next=new ListNode(carry);
return ans;
}
};
3.无重复字符的最长子串
9.1打卡
一开始暴力加小优化,果不其然的TLE,题解很巧妙,双指针滑动窗口。由于字符只是ascii码,所以没必要用哈希表,用桶自己实现哈希即可。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len=s.size();
int maxn=0;
int ch[256]={0};
int sl=0;int sr=0;
while(sr<len){
while(ch[s[sr]]==0&&sr<len){
ch[s[sr]]=1;
sr++;
}
maxn=max(maxn,sr-sl);
while(ch[s[sr]]==1){
ch[s[sl]]=0;
sl++;
}
}
return maxn;
}
};
4.寻找两个正序数组的中位数
9.2打卡
连接后排序,这算是最直接的解法,但是是
O
(
(
n
+
m
)
l
o
g
(
n
+
m
)
)
O((n+m)log(n+m))
O((n+m)log(n+m))复杂度。自己想到的解法是双指针遍历两个数组到一半找到中位数即可,处理奇偶即可,但是仍是线性复杂度。二分和归并的写法让本题成为hard,提示log复杂度想到算法不难,但是处理细节很麻烦,特别考虑两个数组边界问题,以及奇偶带来的问题,细节很难处理。
- 双指针写法
思路不难,就是细节难以处理,一开始绕了很多弯,判断条件写了很多,繁杂反而写错了。大佬题解的这个写法真的妙。
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m=nums1.size();
int n=nums2.size();
int l,r,leftn,rightn;
l=r=0;
leftn=rightn=0;
int mid=(m+n)/2;
for(int i=0;i<=mid;i++){
leftn=rightn;
if(l<m&&((r>=n)||nums1[l]<nums2[r])){
rightn=nums1[l++];
}
else {
rightn=nums2[r++];
}
}
if((m+n)&1)return rightn;
else return 0.5*(leftn+rightn);
}
- 二分法:
求第k大值这个思路真的巧妙,封装成函数非常方便分奇偶情况。
class Solution {
public:
int getkitem(const vector<int> &a,const vector<int> &b,int k){
int m=a.size();
int n=b.size();
int idx1,idx2;
idx1=idx2=0;
while(true){
if(idx1==m)return b[idx2+k-1];
if(idx2==n)return a[idx1+k-1];
if(k==1)return min(a[idx1],b[idx2]);
int nidx1=min(idx1+k/2-1,m-1);
int nidx2=min(idx2+k/2-1,n-1);
int num1=a[nidx1];
int num2=b[nidx2];
if(num1<=num2){
k-=nidx1-idx1+1;
idx1=nidx1+1;
}
else{
k-=nidx2-idx2+1;
idx2=nidx2+1;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len=nums1.size()+nums2.size();
if(len&1)return getkitem(nums1,nums2,(len+1)/2);
else return 0.5*(getkitem(nums1,nums2,len/2)+getkitem(nums1,nums2,len/2+1));
}
};
- 归并法待补!
5.最长回文子串
9.3打卡
- 中心扩展法,这个解法很容易想到,每遍历一个字符,从这个点向两边扩展,分奇偶扩展,即从这个点判断两边是否相等,或者从这个点和下个点对称扩展。找出最大的即可。
class Solution {
public:
int palindromelen(string s,int left,int right){
int l=left,r=right;
while(l>=0&&r<s.size()&&s[l]==s[r]){
--l,++r;
}
return r-l-1;
}
string longestPalindrome(string s) {
int len=s.size();
int ans=0;
if (!len) return "";
int l,r;
for(int i=0;i<len;i++){
int l1=palindromelen(s,i,i);
int l2=palindromelen(s,i,i+1);
int tmp=max(l1,l2);
if(tmp>ans){
ans=tmp;
l=i-(tmp-1)/2;
r=i+tmp/2;
}
}
return s.substr(l,ans);
}
};
- 动态规划
动态规划的思路也不难想到,而且递推方式多种多样,不管是哪种,只要把矩阵打印出来就立马明白了(注释的代码可以打印矩阵)。当然还可以把空间复杂度优化到 O ( n ) O(n) O(n),但是有马拉车算法就不再继续写了。
class Solution {
public:
int palindromelen(string s,int left,int right){
int l=left,r=right;
while(l>=0&&r<s.size()&&s[l]==s[r]){
--l,++r;
}
return r-l-1;
}
string longestPalindrome(string s) {
int len=s.size();
int ans=0;
int idx=0;
if (len<2) return s;
vector<vector<bool>> dp(len,vector<bool>(len));
for(int i=len-2;i>=0;i--){
for(int j=i;j<len;j++){
if(s[i]==s[j]){
if(j<=i+1)dp[i][j]=true;//一位、两位特判
else{
if(dp[i+1][j-1]==true)dp[i][j]=true;
else dp[i][j]=false;
}
}
else dp[i][j]=false;
if(dp[i][j]&&(j-i+1)>ans){
ans=j-i+1;
idx=i;
}
}
}
// for(int i=0;i<len;i++){
// for(int j=0;j<len;j++){
// cout<<dp[i][j]<<" ";
// }
// cout<<endl;
// }
return s.substr(idx,ans);
}
};
- Manacher’s Algorithm马拉车算法待补!
6.正则表达式匹配
9.4打卡
动态规划题,注意dp数组下标从1开始而字符串下标从0开始即可。也可以添加给s,p字符串开头添加一位使之对齐,但内存和时间会增大。
class Solution {
public:
bool match(char a,char b){//b为p的字符
if(b=='.')return true;
return a==b;
}
bool isMatch(string s, string p) {
int m=s.size();
int n=p.size();
vector<vector<bool>> f(m+1,vector<bool>(n+1,0));
f[0][0]=true;
for(int i=2;i<=n;i++){
if(p[i-1]=='*')f[0][i]=f[0][i-2];
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(p[j-1]!='*'){
if(match(s[i-1],p[j-1])){
f[i][j]=f[i-1][j-1];
}
}
else{
if(match(s[i-1],p[j-2])){
f[i][j]=f[i-1][j]||f[i][j-2];
}
else{
f[i][j]=f[i][j-2];
}
}
}
}
return f[m][n];
}
};
7.盛最多水的容器
9.5打卡
贪心题。主要证明出边界值小就往里移动是最优解。
class Solution {
public:
int maxArea(vector<int>& height) {
int n=height.size();
int i=0;
int j=n-1;
int ans=0;
while(i<j){
ans=max(ans,(--n)*min(height[i],height[j]));
if(height[i]<height[j])i++;
else j--;
}
return ans;
}
};
8.三数之和
9.6打卡
本题和之前两数之和很像,但是唯一不同是把给的target换成了数组的任意值。本题关键在于不能重复的解集。解集不重复一般用unordered_map去重最终解集或者选择时就有序选择。本题显然由于和为0的特殊关系,选两个就可以确定另一个的选择大小顺序,这样思路就有了。排序后,先确定第一个非正数(第一个数不能重复选择),从大于等于第一个数开始选第二个数,因为第一个数不断严格递增,所以第三个数也是从n-1严格递减的。时间复杂度就为
O
(
n
2
)
O(n^2)
O(n2)。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
int len=nums.size();
vector<vector<int>> ans;
for(auto i=0;i<len;i++){
if(nums[i]>0)break;
if(i>0&&nums[i]==nums[i-1])continue;
int h=len-1;//c的边界一定是越来越小的,如果放在j循环里面就是n^3时间复杂度,放在这里就是n^2
int j=i+1;
for(;j<len;j++){
if(j>(i+1)&&nums[j]==nums[j-1])continue;
while(h>j&&nums[i]+nums[j]+nums[h]>0)h--;
if(j==h)break;
if(nums[i]+nums[j]+nums[h]==0)
ans.push_back({nums[i],nums[j],nums[h]});
}
}
return ans;
}
};
9.电话号码的字母组合
9.7打卡
简单递归,连不符合条件的都不用排除,所以也可以写成循环方式。
class Solution {
private:
const vector<string> charmp={"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
int n;
vector<string> ans;
string s="";
void dfs(int cur,const string &dig){
if(cur==n){
ans.push_back(s);
return;
}
int idx=dig[cur]-'2';
const int len=charmp[idx].size();
string curs=charmp[idx];//string提出来占用内存会更小
for(int i=0;i<len;i++){
s.push_back(curs[i]);
dfs(cur+1,dig);
s.pop_back();
}
}
public:
vector<string> letterCombinations(string digits) {
n=digits.size();
if(n==0)return ans;
dfs(0,digits);
return ans;
}
};
10.删除链表的倒数第 N 个结点
9.8打卡
因为给定了k一定小于sz,即不可能越界,所以不需要判断这种越界情况。另外题解没有删除那个k节点,而是直接将k节点的前一位只想后一位,应该与垃圾回收机制有关,尚不清楚。本题唯一值得注意的就是删除节点为头结点不能直接返回head。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *dummy=new ListNode(-1,head);
ListNode *pre=head;
ListNode *last=dummy;
while(n--){
//if(pre==nullptr)return nullptr;
pre=pre->next;
}
while(pre){
pre=pre->next;
last=last->next;
}
ListNode *ddd=last->next;
last->next=last->next->next;
ListNode *ans=dummy->next;
delete ddd;
delete dummy;
return ans;
}
};
11.有效的括号
9.9打卡
括号匹配,简单数据结构题。
class Solution {
public:
bool isValid(string s) {
int n=s.size();
if(n&1)return false;
stack<char> sc;
for(int i=0;i<n;i++){
if(s[i]=='(')sc.push(')');
else if(s[i]=='[')sc.push(']');
else if(s[i]=='{')sc.push('}');
else{
if(!sc.empty()){
if(s[i]==sc.top())sc.pop();
else return false;
}
else return false;
}
}
return sc.empty();
}
};
12.合并两个有序链表
9.10打卡
每次两个取链表头结点判断大小即可。题解还有递归写法,递归会产生空间开销,链表过长还会爆栈,但时间复杂度和循环是一样的。纯属炫技但没用的写法。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* prehead=new ListNode(-1);
ListNode* tmp=prehead;
while(l1&&l2){
if(l1->val<l2->val){
tmp->next=l1;
l1=l1->next;
}
else{
tmp->next=l2;
l2=l2->next;
}
tmp=tmp->next;
}
if(l1==nullptr)tmp->next=l2;
else tmp->next=l1;
return prehead->next;
}
};
13.括号生成
9.11打卡
- 一开始的思路和官方解法三一样,任何括号可以写成 ( a ) b (a)b (a)b的形式,a和b也是符合规律的括号,也就说将a从0取到n-1,相应的将b从n-1取到0,这样n个括号就全部包含进去了。当然如果a=generate(i),b=generate(n-i-1)时(为什么要减一?因为a外围的一对括号已经固定),显然会重复搜索,这样效率会很低。所以采用记忆化搜索。思路不难,但是不会题解的智能指针的话,编程就会很麻烦。第一次使用智能指针,mark一下,以后系统学习c++智能指针。
class Solution {
shared_ptr<vector<string>> ans[10]={nullptr};
public:
shared_ptr<vector<string>> generate(int n){
if(ans[n]!=nullptr)return ans[n];
auto tmp=shared_ptr<vector<string>>(new vector<string>);
for(int i=0;i<n;i++){
auto sa=generate(i);
auto sb=generate(n-i-1);
for(const string &a:*sa){
for(const string &b:*sb){
tmp->push_back("("+a+")"+b);
}
}
}
return ans[n]=tmp;
}
void init(){
ans[0]=shared_ptr<vector<string>>(new vector<string>{""});
}
vector<string> generateParenthesis(int n) {
init();
return *generate(n);
}
};
- 解法二是时间复杂度与上述解法一相同,但是空间复杂度仅为
O
(
n
)
O(n)
O(n),递归写法。因为注意到符合规律的括号满足两个点
1、左右括号数量相等
2、任意前缀中左括号数量 >= 右括号数量 (也就是说每一个右括号总能找到相匹配的左括号)
发现上述规律后就可以进行递归,每次左括号数量不大于 n,我们可以放一个左括号。如果右括号数量小于左括号的数量,我们可以放一个右括号。
第一种写法:这种写法一目了然,简单易懂。但是每次递归会额外产生一个string型的空间,所以了解思想后可以看第二种写法。
class Solution {
vector<string> ans;
public:
void dfs(string cur,int left,int right,const int n){
if(left+right==(n<<1)){
ans.push_back(cur);
return;
}
if(left<n)dfs(cur+"(",left+1,right,n);
if(left>right)dfs(cur+")",left,right+1,n);
}
vector<string> generateParenthesis(int n) {
string cur;
dfs(cur,0,0,n);
return ans;
}
};
第二种写法时间复杂度和空间复杂度都优于第一种。因为递归传参是string&,即对cur的引用。string池不会产生额外的字符串。
class Solution {
vector<string> ans;
public:
void dfs(string& cur,int left,int right,const int n){
if(left+right==(n<<1)){
ans.push_back(cur);
return;
}
if(left<n){
cur.push_back('(');
dfs(cur,left+1,right,n);
cur.pop_back();
}
if(left>right){
cur.push_back(')');
dfs(cur,left,right+1,n);
cur.pop_back();
}
}
vector<string> generateParenthesis(int n) {
string cur;
dfs(cur,0,0,n);
return ans;
}
};
14.合并K个排序链表
9.12打卡
看该题之前先看12题合并两个有序链表
- 考虑到12题,最简单的做法便是一个一个按顺序合并。很简单但复杂度肯定是最高的,此处略。
- 更进一步考虑,利用归并的思想,或者说分治合并,两两合并。时间复杂度 O ( k n × l o g k ) O(kn×logk) O(kn×logk),空间复杂度 O ( l o g k ) O(log k) O(logk)。
class Solution {
public:
ListNode* mergetwo(ListNode* a,ListNode* b){
ListNode* dummy=new ListNode(-1);
ListNode* pre=dummy;
while(a&&b){
if(a->val<b->val){
pre->next=a;a=a->next;
}else{
pre->next=b;b=b->next;
}
pre=pre->next;
}
if(a)pre->next=a;
else pre->next=b;
return dummy->next;
}
ListNode* merge(vector<ListNode*>& lists,int left,int right){
if(left==right)return lists[left];
if(left>right)return nullptr;
int mid=(left+right)/2;
ListNode* l=merge(lists,left,mid);
ListNode* r=merge(lists,mid+1,right);
return mergetwo(l,r);
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
return merge(lists,0,lists.size()-1);
}
};
- 另外一种做法:我们可以考虑当合并两个节点的做法:即我们每次取两链表的首元素,将最小的放入下一个节点,下移该头结点,然后继续比较头结点。显然我们可以用一个最小堆维护这个头结点,每次取最小的数出队,取该头结点的下一个进队。时间复杂度 O ( k n × l o g k ) O(kn×logk) O(kn×logk),空间复杂度 O ( k ) O(k) O(k)。
class Solution {
public:
struct heap{
int val;
ListNode *ptr;
heap()=default;
heap(int x,ListNode *next):val(x),ptr(next){}
bool operator <(const heap &rhs)const{
return val>rhs.val;
}
};
priority_queue<heap> pq;
ListNode* mergeKLists(vector<ListNode*>& lists) {
for(auto list:lists)
if(list)pq.push(heap(list->val,list));
ListNode *dummy=new ListNode(-1);
ListNode *pre=dummy;
while(!pq.empty()){
heap f=pq.top();pq.pop();
pre->next=f.ptr;
pre=pre->next;
if(f.ptr->next)pq.push(heap(f.ptr->next->val,f.ptr->next));
}
return dummy->next;
}
};
最后额外拓展一下这个题,因为有面试官问过,该题如果有一个链表长度非常长怎么办?即内存可能存不下这个链表,即必须需要外部排序了,此处不再多言,可以了解一下败者树这种数据结构。
15.下一个排列
9.13打卡
编码倒不难,但是题解思路真的很巧妙。看了题解才想明白,mark一下以后经常回头看,官方题解。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
if(nums.size()==1)return;
int n=nums.size();
int i,j;
for(i=n-1;i>0;i--){
if(nums[i]>nums[i-1]){
break;
}
}
if(i==0){
nums=vector<int>(nums.rbegin(),nums.rend());
return;
}
for(j=n-1;j>=i;j--)
if(nums[i-1]<nums[j]){
swap(nums[j],nums[i-1]);
break;
}
reverse(nums.begin()+i,nums.end());
}
};
16.最长有效括号
9.16打卡。鸽了两天,因为这两天在面华为西研所。
- 动态规划法。原理很简单,就是特判是否越界很麻烦。
class Solution {
public:
int longestValidParentheses(string s) {
int maxn=0,n=s.size();
vector<int>dp(n,0);
for(int i=1;i<n;i++){
if(s[i]==')'){
if(s[i-1]==')'){
if(i-dp[i-1]>0){
int t=i-dp[i-1]-1;
if(s[t]=='(')dp[i]=dp[i-1]+(t>1?dp[t-1]:0)+2;
}
}
else dp[i]=(i>=2?dp[i-2]:0)+2;
}
maxn=max(maxn,dp[i]);
}
return maxn;
}
};
- 用栈实现,这个思路真的太巧妙了。
class Solution {
public:
int longestValidParentheses(string s){
int maxn=0;
stack<int> sk;
sk.push(-1);
for(int i=0;i<s.size();i++){
if(s[i]=='(')sk.push(i);
else{
sk.pop();
if(sk.empty())sk.push(i);
else maxn=max(maxn,i-sk.top());
}
}
return maxn;
}
};
- 双指针这个写法更巧妙。 O ( n ) O(n) O(n)时间复杂度, O ( 1 ) O(1) O(1)空间复杂度。回头看。
17.搜索旋转排序数组
9.17打卡
二分真的很难写。特别是边界的等号太易混淆。只能背一种写法。还好数组元素都不相等。否则lower_bound和upper_bound写法更易混淆。
class Solution {
public:
int search(vector<int>& nums, int target) {
int n=nums.size();
if(n==1)return nums[0]==target?0:-1;
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]==target)return mid;
if(nums[l]<=nums[mid]){
if(target>=nums[l]&target<=nums[mid])r=mid;
else l=mid+1;
}
else{
if(target>=nums[mid]&&target<=nums[r])l=mid+1;
else r=mid;
}
}
return -1;
}
};
18.在排序数组中查找元素的第一个和最后一个位置
9.18打卡
果然下一个题就开始写lower_bound和upper_bound了,lower_bound找出第一个大于等于目标值的位置;upper_bound找出第一个大于等于目标值的位置。详细分析可见刘汝佳算法竞赛入门经典(紫书)第二版8.2.3二分查找部分。
这种写法是版本一写法。如果是找出第一个大于等于目标值的数。即不断缩小右边界为m,如果找出找第一个大于目标值的数,需要不断增大左边界为m+1。
版本1
当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1,计算mid时不需要加1,即mid = (l + r)/2。
版本2
当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid,此时为了防止死循环,计算mid时需要加1,即mid = ( l + r + 1 ) /2。
class Solution {
public:
int lower(vector<int> A,int l,int r,int v){
int m;
while(l<r){
m=l+(r-l)/2;
if(A[m]>=v)r=m;
else l=m+1;
}
return l;
}
int upper(vector<int> A,int l,int r,int v){
int m;
while(l<r){
m=l+(r-l)/2;
if(A[m]<=v)l=m+1;
else r=m;
}
return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
int n=nums.size();
int low=lower(nums,0,n,target);
int up=upper(nums,0,n,target);
if(low<n&&(up-1)<n&&nums[low]==target&&nums[up-1]==target){
return {low,up-1};
}
return {-1,-1};
}
};
19.组合总和
9.19打卡
本题较为简单,注意同一个数可以多次选取,所以数组没有重复元素。深搜+剪枝即可。考虑每个数开头的可能即可。如果以及当前总和超出目标值就剪掉。
还有一种搜索方法每一次对一个数是考虑选或不选。此处不提。
class Solution {
public:
vector<int> tmp;
void dfs(const vector<int>& nums,vector<vector<int>>& ans,int target,int sum,int idx,int n){
if(target==sum){
ans.push_back(tmp);
return;
}
for(int i=idx;i<n;i++){
int tt=sum+nums[i];
if(tt>target)break;
else{
tmp.push_back(nums[i]);
dfs(nums,ans,target,tt,i,n);//注意仍是i,因为可以重复选取
tmp.pop_back();
}
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
vector<vector<int>> ans;
dfs(candidates,ans,target,0,0,candidates.size());
return ans;
}
};
不过这里额外说一下本题的下一题,另一道变式,博主在华为机试遇到了此题。仍是给定数组求目标和为target的所有可能序列,但需要考虑数组元素重复情况。题目链接:40. 组合总和 II
除了官方题解用哈希表每次一次性处理重复元素做法之外,还有一直更简单巧妙的解法,即每次考虑不同数开头的序列。实际上只需要加一句即可:
if(i>idx&&nums[i-1]==nums[i])continue;
因为不能重复选取元素,即递归需要坐标加一。
class Solution {
public:
vector<int> tmp;
void dfs(const vector<int>& nums,vector<vector<int>>& ans,int target,int sum,int idx,int n){
if(target==sum){
ans.push_back(tmp);
return;
}
for(int i=idx;i<n;i++){
if(i>idx&&nums[i-1]==nums[i])continue;
int tt=sum+nums[i];
if(tt>target)break;
else{
tmp.push_back(nums[i]);
dfs(nums,ans,target,tt,i+1,n);
tmp.pop_back();
}
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
vector<vector<int>> ans;
dfs(candidates,ans,target,0,0,candidates.size());
return ans;
}
};
20.接雨水
9.20打卡
大名鼎鼎的接雨水问题。
- 我只想到了动态规划法,思想很简单,对于每个坑,只要知道左边界最大值和右边界最大值就可以得到雨水的高度。求数组前i元素的最大值很显然的动态规划,或者也可以理解为剑指offer里的单调栈思路。
class Solution {
public:
int trap(vector<int>& height) {
int n=height.size();
if(n<3)return 0;
vector<int> lh(n,0);
vector<int> rh(n,0);
lh[0]=height[0];rh[n-1]=height[n-1];
for(int i=1;i<n;i++){
lh[i]=max(lh[i-1],height[i]);
}
for(int i=n-2;i>=0;i--){
rh[i]=max(rh[i+1],height[i]);
}
int ans=0;
for(int i=1;i<n-1;i++){
ans+=min(lh[i],rh[i])-height[i];
}
return ans;
}
};
- 双指针写法。优化思想是,对于每一个坑,不需要知道两边最大边界,只需要知道最小的那个就可以确定下来边界高度,即我们每次移动左右指针时,左右边界的临时最大高度恰好是最低边界,听起来似乎有点矛盾,建议看官方题解和动画琢磨。这个写法时空复杂度最优。
class Solution {
public:
int trap(vector<int>& height) {
int n=height.size();
if(n<3)return 0;
int lmax=height[0],rmax=height[n-1];
int l=0,r=n-1;
int ans=0;
while(l<r){
if(height[l]<=height[r]){
lmax=max(height[l],lmax);
ans+=lmax-height[l];
l++;
}else{
rmax=max(height[r],rmax);
ans+=rmax-height[r];
r--;
}
}
return ans;
}
};
- 单调栈写法。此处不做详细说明。