力扣Hot100题单个人计划c++版(一)

力扣Hot100题单个人计划c++版(一)
力扣Hot100题单个人计划c++版(二)
力扣Hot100题单个人计划c++版(三)
力扣Hot100题单个人计划c++版(四)
力扣Hot100题单个人计划c++版(五)


刷题链接:力扣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复杂度想到算法不难,但是处理细节很麻烦,特别考虑两个数组边界问题,以及奇偶带来的问题,细节很难处理。

  1. 双指针写法
    思路不难,就是细节难以处理,一开始绕了很多弯,判断条件写了很多,繁杂反而写错了。大佬题解的这个写法真的妙。
    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);
    }
  1. 二分法:
    求第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));
    }
};
  1. 归并法待补!

5.最长回文子串

9.3打卡

  1. 中心扩展法,这个解法很容易想到,每遍历一个字符,从这个点向两边扩展,分奇偶扩展,即从这个点判断两边是否相等,或者从这个点和下个点对称扩展。找出最大的即可。
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);
    }
};
  1. 动态规划
    动态规划的思路也不难想到,而且递推方式多种多样,不管是哪种,只要把矩阵打印出来就立马明白了(注释的代码可以打印矩阵)。当然还可以把空间复杂度优化到 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);
    }
};
  1. 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打卡

  1. 一开始的思路和官方解法三一样,任何括号可以写成 ( 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);
    }
};
  1. 解法二是时间复杂度与上述解法一相同,但是空间复杂度仅为 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题合并两个有序链表

  1. 考虑到12题,最简单的做法便是一个一个按顺序合并。很简单但复杂度肯定是最高的,此处略。
  2. 更进一步考虑,利用归并的思想,或者说分治合并,两两合并。时间复杂度 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);
    }
};
  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打卡。鸽了两天,因为这两天在面华为西研所。

  1. 动态规划法。原理很简单,就是特判是否越界很麻烦。
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;
    }
};
  1. 用栈实现,这个思路真的太巧妙了。
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;
    }
};
  1. 双指针这个写法更巧妙。 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_boundupper_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打卡
大名鼎鼎的接雨水问题。

  1. 我只想到了动态规划法,思想很简单,对于每个坑,只要知道左边界最大值和右边界最大值就可以得到雨水的高度。求数组前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;
    }
};
  1. 双指针写法。优化思想是,对于每一个坑,不需要知道两边最大边界,只需要知道最小的那个就可以确定下来边界高度,即我们每次移动左右指针时,左右边界的临时最大高度恰好是最低边界,听起来似乎有点矛盾,建议看官方题解和动画琢磨。这个写法时空复杂度最优。
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;
    }
};
  1. 单调栈写法。此处不做详细说明。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值