力扣刷题笔记

单调栈

Problem: [496. 下一个更大元素 I]
思路

  • 典型单调栈题型
  • 从右往左遍历nums2,对于nums2[i],将栈中小于等于nums2[i]的元素全部出栈,因为对于nums2[i]左边的元素,他们右边第一个大于自身的元素不可能是刚刚出栈的元素(因为nums2[i]大于刚刚出栈的这些元素。此时若栈非空则大于nums2[i]的右边第一个元素就是栈顶元素,若栈空,则nums2[i]的右边无大于它的元素。再将nums2[i]入栈即可。
  • 重复第二步直至遍历完nums2
  • 在此过程中可以用哈希表存储结果
  • 找左/右第一个大于自身的元素,单调栈单调递减(栈底到栈顶)
    找左/右第一个小于自身的元素,单调栈单调递增(栈底到栈顶)

Code


class Solution {
public:
  vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
      map<int,int> hashMap;
      stack<int> stk;//单调栈
      for(int i=nums2.size()-1;i>=0;i--){
          while(!stk.empty()&&nums2[i]>=stk.top())
              stk.pop();
          
          if(!stk.empty()){
              hashMap[nums2[i]]=stk.top();
          }
          else{
              hashMap[nums2[i]]=-1;
          }
          stk.push(nums2[i]);
      }

      vector<int> ans;
      for(int i=0;i<nums1.size();i++){
          ans.push_back(hashMap[nums1[i]]);
      }
      return ans;
  }
};

Problem: [2866. 美丽塔 II]
思路

  • 构造前缀数组pre和后缀数组suf
  • pre[i]表示以i为山峰时,左侧的高度和最大值(包括自身)
    suf[i]表示 以i为山峰时,右侧的高度和最大值(包括自身)
  • 最后只需遍历pre和suf求最大高度即可
  • 问题是如何构造pre和suf,根据题意我们不难想到用单调栈构造
  • 对于pre[i],若maxHeights[i]>maxHeights[i-1]则pre[i]=maxHeights[i-1]+pre[i-1];
    否则我们需要找到i左边第一个小于maxHeight[i]的位置j,则pre[i]=maxHeights[i]*(i-j)+pre[j];

Code


class Solution {
public:
  long long maximumSumOfHeights(vector<int>& maxHeights) {
      vector<long long> pre(maxHeights.size());//以i为山峰时,左侧的高度和最大值(包括自身)
      vector<long long> suf(maxHeights.size());//以i为山峰时,右侧的高度和最大值(包括自身)
      stack<long long> stk1;//单调栈 帮助构建pre  存下标
      stack<long long> stk2;//单调栈 帮助构建suf  存下标

      //构建pre
      for(long long i=0;i<maxHeights.size();i++){
          while(!stk1.empty()&&maxHeights[i]<maxHeights[stk1.top()]){
              stk1.pop();
          }

          if(i>0&&maxHeights[i]>maxHeights[i-1]){
              pre[i]=maxHeights[i]+pre[i-1];
          }
          else{
              if(!stk1.empty())
                pre[i]=maxHeights[i]*(i-stk1.top())+pre[stk1.top()]; 
              else
                pre[i]=maxHeights[i]*(i+1);
          }
          stk1.push(i);
      }

      //构建suf
      for(long long i=maxHeights.size()-1;i>=0;i--){
          while(!stk2.empty()&&maxHeights[i]<maxHeights[stk2.top()]){
              stk2.pop();
          }

          if(i<maxHeights.size()-1&&maxHeights[i]>maxHeights[i+1]){
              suf[i]=maxHeights[i]+suf[i+1];
          }
          else{
              if(!stk2.empty())
                suf[i]=maxHeights[i]*(stk2.top()-i)+suf[stk2.top()]; 
              else
                suf[i]=maxHeights[i]*(maxHeights.size()-i);
          }
          stk2.push(i);
      }


      long long maxH=0;
      for(long long i=0;i<maxHeights.size();i++){
          maxH=max(maxH,pre[i]+suf[i]-maxHeights[i]);
      }
      return maxH;
  }
};

并查集

990. 等式方程的可满足性

思路

  • 典型并查集题型,但注意此题要将2种情况分开进行判断,比较有意思

Code

class Solution {
public:
    int N=500;
    int father[500];
    void Initial(){
        for(int i=0;i<N;i++){
            father[i]=i;
        }
    }
    int FindRoot(int x){
        return x==father[x]?FindRoot(father[x]);
    }
    //在同一集合返回true
    void Union(int x,int y){
        int xF=FindRoot(x);
        int yF=FindRoot(y);
        if(xF!=yF)
            father[xF]=yF;
    }
    
    bool equationsPossible(vector<string>& equations) {
        Initial();
        for(int i=0;i<equations.size();i++){
            string str=equations[i];
            char c1=str[0];
            char c2=str[3];
            
            if(str[1]=='='){
               Union(c1,c2);
            }
        }
        for(int i=0;i<equations.size();i++){
            string str=equations[i];
            char c1=str[0];
            char c2=str[3];

            if(str[1]=='!')
                if(FindRoot(c1)==FindRoot(c2))
                    return false;

        }

        return true;
    }
};

Problem: [LCP 399. 除法求值]
思路

  • 类似于上一题的做法,只不过此题更难
  • 注意,此题使用并查集必须使用路径压缩,因为FindRoot(x)使用路径压缩后,x会直接指向树根节点,利用此特性我们才可求解两个比值

Code

class Solution {
public:
    const static int N=500;
    int father[N];
    double weight[N];//表示该点与其父节点的倍数关系
    //初始化
    void Initial(){
        for(int i=0;i<N;i++){
            father[i]=i;
            weight[i]=1.0;
        }
    }
    //找根,同时压缩路径(压缩路径后,x指向其根节点)
    int FindRoot(int x){
       if(x==father[x])
           return x;
       
       int orginFather=father[x];
       father[x]=FindRoot(father[x]);
       weight[x]=weight[x]*weight[orginFather];
       
       return father[x];

    }
    //合并
    void Union(int x,int y,double value){
        int xRoot=FindRoot(x);
        int yRoot=FindRoot(y);
        //FindRoot操作后,x和y都会直接指向其各自树的树根

        if(xRoot!=yRoot){
            father[xRoot]=yRoot;
            weight[xRoot]=weight[y]*value/weight[x]; 
        }
            
    }
    //计算x/y,若无法计算(即x与y不在同一集合中)则返回-1.0
    double Calculate(int x,int y){
        int xRoot=FindRoot(x);
        int yRoot=FindRoot(y);
        //FindRoot之后,x和y都指向该集合的根节点
     
        if(xRoot!=yRoot){
            return -1.0;
        }
        return weight[x]/weight[y];


    }
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        Initial();
        vector<double> result;

        //建立string和id的映射,同时合并
        map<string,int> hashMap;
        int id=0;
        for(int i=0;i<equations.size();i++){
            string str1=equations[i][0];
            string str2=equations[i][1]; 
            if(hashMap.find(str1)==hashMap.end()){
                hashMap[str1]=id++;
            }
            if(hashMap.find(str2)==hashMap.end()){
                hashMap[str2]=id++;
            }
            Union(hashMap[str1],hashMap[str2],values[i]);//合并
        }



        for(int i=0;i<queries.size();i++){
            string str1=queries[i][0];
            string str2=queries[i][1]; 
           
            if(hashMap.find(str1)==hashMap.end()||hashMap.find(str2)==hashMap.end()){
                result.push_back(-1.0);
            }
            else{
                result.push_back(Calculate(hashMap[str1],hashMap[str2]));
            }
        }


        return result;
    }
};

Problem: [LCP 07. 传递信息]
思路

  • 不同于一般的DFS,此题的DFS不需要visited数组,也就是每个结点可以多次遍历

Code


class Solution {
public:
    int ans=0;
    vector<int> Adj[20];
    
    void DFS(int i,int n,int k,int curK){//当前结点 结点总数 k 当前轮数
        if(curK>k){
           return;
        }
        if(i==n-1&&curK==k){
            ans++;
        }

        for(int j=0;j<Adj[i].size();j++){
           DFS(Adj[i][j],n,k,curK+1);
        }
 

    }
    int numWays(int n, vector<vector<int>>& relation, int k) {
        for(int i=0;i<relation.size();i++){
            Adj[relation[i][0]].push_back(relation[i][1]);
        }
        DFS(0,n,k,0);

        return ans;
    }
};

Problem: [133. 克隆图]
思路

  • 不同于一般的DFS,此题的DFS当if(!visited[nextNode])时候需要处理(一般的DFS不对该情况做额外处理),且此题的DFS是有返回值的

Code


/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> neighbors;
    Node() {
        val = 0;
        neighbors = vector<Node*>();
    }
    Node(int _val) {
        val = _val;
        neighbors = vector<Node*>();
    }
    Node(int _val, vector<Node*> _neighbors) {
        val = _val;
        neighbors = _neighbors;
    }
};
*/

class Solution {
public:
    map<Node*,Node*> hashMap;//key:原图结点  value:克隆图对应的结点
    bool visited[101]={0};
    Node* DFS(Node* node){//返回当前遍历的结点的克隆结点
        if(node==nullptr){
            return nullptr;
        }
        visited[node->val]=true;  
       
        Node* cloneNode=new Node(node->val);
        hashMap[node]=cloneNode;
        for(int i=0;i<node->neighbors.size();i++){
            cout<<node->neighbors[i]->val;
            if(!visited[node->neighbors[i]->val]){//未遍历,则遍历,并且返回该结点的克隆结点
                cloneNode->neighbors.push_back(DFS(node->neighbors[i]));
            }
            else{//之前遍历过,我们不再遍历,但是存在当前结点到该点的一条边,我们需要把边构造出来
                cloneNode->neighbors.push_back(hashMap[node->neighbors[i]]);
            }
              
        }

        return cloneNode;
    }
    Node* cloneGraph(Node* node) {

        return DFS(node);
    }
};

Problem: [207. 课程表]
思路

  • 典型的拓扑排序
  • 此外,思考如果要输出路径怎么办

Code


class Solution {
public:
    vector<int> Adj[2000];
    int inDegree[2000];
    
    bool topo(int numCourses, vector<vector<int>>& prerequisites){
        queue<int> q;
        for(int i=0;i<numCourses;i++){
            if(inDegree[i]==0)
                q.push(i);
        }

        int total=0;
        while(!q.empty()){
            int n=q.front();
            q.pop();
            total++;

            for(int i=0;i<Adj[n].size();i++){
                inDegree[Adj[n][i]]--;
                if(inDegree[Adj[n][i]]==0){
                    q.push(Adj[n][i]);
                   
                }
               
            }

        }

        return total==numCourses;
    }   
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        if(prerequisites.size()==0)
            return true;
        for(int i=0;i<prerequisites.size();i++){
               Adj[prerequisites[i][1]].push_back(prerequisites[i][0]);
               inDegree[prerequisites[i][0]]++;          
        }
        return topo(numCourses,prerequisites);
    
    }
};

Problem: [261. 以图判树]
思路

  • 并查集
  • 思考:并查集可以判断无向图是否存在环,拓扑排序可以判断有向图是否存在环
    Code
class Solution {
public:
    int N;
    int father[2000];
    void initial(){
        for(int i=0;i<N;i++)
            father[i]=i;
    }
    int FindFather(int x){
        return x==father[x]?x:FindFather(father[x]);
    }
    bool Union(int x,int y){
        int xFather=FindFather(x);
        int yFather=FindFather(y);
        if(xFather!=yFather){
            father[xFather]=yFather;
            return false;
        }
        else
            return true;
    }
    int TotalUnions(){
        int sum=0;
        for(int i=0;i<N;i++)
            if(father[i]==i)
                sum++;
        return sum;
    }
    bool validTree(int n, vector<vector<int>>& edges) {
        N=n;
        initial();

        //判断是否存在环
        for(int i=0;i<edges.size();i++){
            int start=edges[i][0];
            int end=edges[i][1];
            bool flag=Union(start,end);
            if(flag)
                return false;//存在环返回false
        }

        //判断连通分量是否只有1个
        if(TotalUnions()==1)
            return true;
        else    
            return false;
    }
};

Problem: [277. 搜寻名人]
思路

  • 暴力算法超时,虽然我不知道为什么会超时,O(n2)在本题的数据量应该不会超时
/* The knows API is defined for you.
      bool knows(int a, int b); */

class Solution {
public:
    int findCelebrity(int n) {
       vector<int> visited(n,0);

       for(int i=0;i<n;i++){
           for(int j=0;j<n;j++){
               if(i!=j){
                   if(!knows(i,j)){//如果i不认识j则j不可能是名人
                       visited[j]=1;
                   }
                   else{
                       visited[i]=1;//如果i认识任意一个j则i不可能是名人
                   }
               }
           }
       }

       int ans=-1;
       for(int i=0;i<n;i++){
           if(!visited[i]){
               if(ans!=-1){//表示此时发现2个名人,不符合题意,返回-1
                   return -1;
               }
               ans=i;
           }
            
       }

       return ans; 
    }
};
  • 寻求O(n)时间复杂度的解法,如下
  • 此外,注意官方题解的法三,比较有意思

Code


/* The knows API is defined for you.
      bool knows(int a, int b); */

class Solution {
public:
    int findCelebrity(int n) {
       
       //在n次循环后,必当能够排除n-1个不同的顶点(模拟迭代过程可看出来),且candidate最后存放的是我们暂时不能排除那个顶点
       int candidate=0;
       for(int i=0;i<n;i++){
           if(knows(candidate,i)){
               candidate=i;
           }
       }

        
       //检查candidate是否是候选人
       for(int i=0;i<n;i++){
           if(i!=candidate){
               if(knows(candidate,i)||!knows(i,candidate)){
                   candidate=-1;
                   break;
               }
           }
       }


       return candidate;

        
    }
};

哈希表+前缀数组

Problem: [930. 和相同的二元子数组]

思路

  • 纯暴力枚举左右边界,对于每个左右边界求一次和,时间复杂度O(N3)
  • 考虑前缀数组,每次枚举左右边界,对于每个左右边界用preSum[j]-preSum[i]得到和,当preSum[j]-preSum[i]=goal时,即为找到一个满足题意的子数组,时间复杂度O(N2)
  • 为了进一步降低时间复杂度,考虑式子preSum[j]-preSum[i]=goal可以变为preSum[j]-goal=preSum[i],即我们可以从左到右枚举右边界j,对于每个右边界j,我们若能知道在j之前有几个i满足preSum[j]-goal=preSum[i]我们就能得到该右边界情况下的满足题意的子数组,若我们在每个右边界处以遍历的方式寻找之前满足该条件的i时间复杂度就为O(N2),我们可以用hash表来保存之前出现过的值为preSum[j]-goal的个数,时间复杂度可降为O(N)

Code


class Solution {
public:
    int numSubarraysWithSum(vector<int>& nums, int goal) {
        int preSum[3*10000+1]={0};//preSum[i] nums前i个数之和
        for(int i=1;i<=nums.size();i++){
            preSum[i]=preSum[i-1]+nums[i-1];
        }
        

        /*
            寻找满足preSum[j]-preSum[i]==goal(i:0~n-1  j:1~n)的所有i和j即可
            即preSum[j]-goal=preSum[i](i<j)
            枚举右边界j,对于每个j,我们只需要寻找小于j且满足该条件的i:preSum[i]=preSum[j]-goal
        */
        int ans=0;
        map<int,int> hashMap;//key:数值preSum[i](i<j)  value:数值preSum[i]出现的次数 
        hashMap[0]=1;
        for(int j=1;j<=nums.size();j++){//枚举右端点
            if(hashMap.find(preSum[j]-goal)!=hashMap.end()){
                 ans+=hashMap[preSum[j]-goal];
            }
               
            hashMap[preSum[j]]++;
        }
        return ans;
    }
};

Problem: [2845. 统计趣味子数组的数目]
思路

  • 同上一题

Code


class Solution {
public:
    long long countInterestingSubarrays(vector<int>& nums, int modulo, int k) {
          vector<int> pre(100000+1,0);
          //int pre[100000+1];//pre[i]表示nums前i个元素中,满足nums[i] % modulo == k的个数
          pre[0]=0;
          for(int i=1;i<=nums.size();i++){
               if(nums[i-1]%modulo==k){
                   pre[i]=pre[i-1]+1;
               }
               else{
                   pre[i]=pre[i-1];
               }
          }


          //对于每个左右边界i和j,若满足(pre[j]-pre[i])% modulo=k (i:0~n-1  j:1~n),即为符合题意的一个子数组
          //上式可化为 (pre[j]%modulo-k+modulo)%modulo=p[i]%modulo
          long long ans=0;
          map<int,int> hashMap;//key:数值pre[i]%modulo+k  value:该数值出现的次数
          hashMap[pre[0]%modulo]=1;
          for(int j=1;j<=nums.size();j++){
              if(hashMap.find((pre[j]%modulo-k+modulo)%modulo)!=hashMap.end()){  
                  ans+=hashMap[(pre[j]%modulo-k+modulo)%modulo];
              }
              hashMap[pre[j]%modulo]++;
          }

          return ans;

    }
};

链表

Problem: [146. LRU 缓存]
思路

  • 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的
  • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置

Code


class LRUCache {
public:
//双向链表存放key-value 表头存放最近使用过的key 表尾存放最久未使用过的key 
    struct ListNode{
        int key;
        int value;
        ListNode* pre;
        ListNode* next;
        ListNode(int x,int y){
            key=x;
            value=y;
        }
    };
     
    void InsertInHead(int key,int value){
        /*
        if(hashMap.size()==capacity)
               RemoveTail();//如果超了则先移除表尾元素
        思考这里为什么不先RemoveTail
        因为如果有这种情况capacity=1,put(1,1),put(2,2)就会报错 
        */
        ListNode* node=new ListNode(key,value);
        if(head==nullptr){
            head=node;//插入第一个结点的情况
            head->next=head;
            head->pre=head;
        }
        else{
            node->pre=head->pre; 
            head->pre->next=node;
            node->next=head;
            head->pre=node;
            head=node;
        }
       

        hashMap[key]=node;
    }
    void MoveToHead(ListNode* node){
        if(node==head)
            return;
        node->pre->next=node->next;
        node->next->pre=node->pre;
        node->next=head;
        node->pre=head->pre;
        head->pre->next=node;
        head->pre=node;
  
        head=node;
    }
    void RemoveTail(){
        ListNode* tmp=head->pre;
        head->pre=head->pre->pre;
        head->pre->next=head;
        hashMap.erase(tmp->key);
        delete(tmp);
    }
    void print(){//调试用的函数,别管
  
        ListNode* node=head;
        for(int i=0;i<hashMap.size();i++){
            cout<<node->key<<" ";
            node=node->next;
        }
        cout<<endl;
    }
    
    map<int,ListNode*> hashMap;//存放某个key对应的结点,为了O(1)找到value
    int capacity;
    ListNode  *head=nullptr;

    LRUCache(int capacity) {
        this->capacity=capacity;
    }
    
    int get(int key) {
        if(hashMap.find(key)==hashMap.end()){
             //print();
             return -1; 
        }
           
        else{
            ListNode* node=hashMap[key];
            MoveToHead(node);
           //print();
            return node->value;
        }

       
    }
    
    void put(int key, int value) { 
        //如果不存在
        if(hashMap.find(key)==hashMap.end()){  
            InsertInHead(key,value);//插入头部

            if(hashMap.size()>capacity)
               RemoveTail();//如果超了则移除表尾元素
        }
        //如果存在,则修改value值并且移到头部
        else{
            hashMap[key]->value=value;
            MoveToHead(hashMap[key]);
        }

       // print();
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

Problem: [328. 奇偶链表]
思路

  • 对于偶数项和奇数项都各自使用一对双指针,其实还比较简单,但这题难在比较难想清特殊情况的处理

Code


/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head==nullptr||head->next==nullptr||head->next->next==nullptr)
            return head;
        

        ListNode *evenL1,*evenL2;//偶数双指针
        ListNode *oddL1,*oddL2;//奇数双指针


        oddL1=head;
        oddL2=head->next->next;
        evenL1=head->next;
        evenL2=evenL1->next->next;

        ListNode *oddHead=oddL1;
        ListNode *evenHead=evenL1;

        while(evenL2!=nullptr&&oddL2!=nullptr){
            oddL1->next=oddL2;
            evenL1->next=evenL2;
            

            oddL1=oddL2;
            evenL1=evenL2;
            if(evenL2!=nullptr){
                oddL2=evenL2->next;
            }

            if(oddL2!=nullptr){
                evenL2=oddL2->next;
            }
            
        }
        
        
        if(oddL1!=oddL2)
           oddL1->next=oddL2;
        if(evenL1!=evenL2)
           evenL1->next=evenL2;

        //奇数链表尾->偶数链表头
        if(oddL2==nullptr){
            oddL1->next=evenHead;
            evenL2->next=nullptr;
        } 
        else{
            oddL2->next=evenHead;
        }

       

        return oddHead;

    }
};

双指针

Problem: [15. 三数之和]
思路

  • 典型的双指针问题,本题的难点在于如何去重
  • 代码注释中的"去重1 去重2 去重3"这三处代码合在一起即可达到去重效果

Code

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());//去重1
        for(int i=0;i<nums.size();i++){
            if(i!=0&&nums[i]==nums[i-1])//去重2
                continue;
            int target=-nums[i];
            int left=i+1,right=nums.size()-1;
            
            while(left<right){
                if(left!=i+1&&nums[left]==nums[left-1])//去重3
                    left++;
                else{
                    if(nums[left]+nums[right]>target)
                        right--;
                    else if(nums[left]+nums[right]<target)
                        left++;
                    else{
                        result.push_back({nums[i],nums[left],nums[right]});
                        left++;
                        right--;
                    }
                }
               
            }
        }


        return result;

    }
};```

# 优先队列
[Problem: [23. 合并 K 个升序链表]](https://leetcode.cn/problems/merge-k-sorted-lists/description/)
思路
- 优先队列

Code
```cpp
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    struct cmp {
        bool operator()(ListNode* a, ListNode* b) {
            return a->val > b->val;//小根堆 大于号
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*, vector<ListNode*>, cmp> q;
        
        
        for(int i=0;i<lists.size();i++){
            if(lists[i]!=nullptr)
              q.push(lists[i]);
        }
         
        ListNode* head=new ListNode(0);
        ListNode* curNode=head;
        while(!q.empty()){
            curNode->next=q.top();
            curNode=curNode->next;
            q.pop();
            if(curNode->next!=nullptr)
               q.push(curNode->next);
        }


        return head->next;
    }
};

Problem: [1163. 按字典序排在最后的子串]
思路

  • 首先并非所有的子字符串都需要被考虑到,只有后缀子字符串才可能是排在最后的子字符串,所以我们只需找出字典序最大的后缀即可
  • 如果直接用substr比较则会超内存限制,其实我们可以跳过一些无需比较的位置
class Solution {
public:
    string lastSubstring(string s) {
     
        int i=0;//i指向以字典序最大的后缀  j指向当前以s[j]开始的后缀 
        for(int j=1;j<s.size();j++){
            if(s.substr(i)<s.substr(j)){
                i=j;
            }
        } 
        return s.substr(i);   
    }
};
  • 每次比较以s[maxIndex]开头的后缀和s[j]开头的后缀,k一开始为0,k++直到s[maxIndex+k]≠s[j+k],此时分两种情况:
    (1)s[maxIndex+k]>s[j+k]
    无需更新maxIndex;对于j:
    s[j]开头的后缀字典序<s[maxIndex]开头的后缀字典序,跳过j
    s[j+1]开头的后缀字典序<s[maxIndex+1]开头的后缀字典序,跳过j+1
    s[j+2]开头的后缀字典序<s[maxIndex+2]开头的后缀字典序,,跳过j+2

    s[j+k]开头的后缀字典序<s[maxIndex+k]开头的后缀字典序,,跳过j+k
    s[j]开头的后缀字典序与s[maxIndex]开头的后缀字典序我们无法得知,所以令j=j+k+1
    (2)s[maxIndex+k]<s[j+k]
    s[maxIndex]开头的后缀字典序<s[j]开头的后缀字典序,跳过maxIndex
    s[maxIndex+1]开头的后缀字典序<s[j+1]开头的后缀字典序,跳过maxIndex+1
    s[maxIndex+2]开头的后缀字典序<s[j+2]开头的后缀字典序,跳过maxIndex+2

    s[maxIndex+k+1]开头的后缀字典序与s[j+1]开头的后缀字典序我们无法得知,所以令maxIndex=maxIndex+1
    对于j:如果此时maxIndex大于等于j,则j=maxIndex+1

Code


class Solution {
public:
    string lastSubstring(string s) {
        //maxIndex指向以字典序最大的后缀  j指向当前以s[j]开始的后缀 
        int maxIndex=0;
        for(int j=1;j<s.size();){
             //比较以s[maxIndex]开头的后缀和s[j]开头的后缀
             int k=0;
             while(j+k<s.size()&&s[maxIndex+k]==s[j+k])
                k++;
             if(s[maxIndex+k]>s[j+k]){
                 j=j+k+1;
             }
             else{
                 maxIndex=maxIndex+k+1;
                 if(maxIndex>=j)
                     j=maxIndex+1;
             }
        } 

        return s.substr(maxIndex);   

    }
};

DFS

Problem: [46. 全排列]
思路

  • 一眼回溯法,注意恢复现场

Code

class Solution {
public:
    vector<vector<int>> result;
    bool visited[10];
    void dfs(vector<int>& nums,vector<int> arr){
        if(arr.size()==nums.size()){
            result.push_back(arr);
            return;
        }

        for(int i=0;i<nums.size();i++){
            if(!visited[i]){
                visited[i]=true;
                arr.push_back(nums[i]);
                dfs(nums,arr);
                arr.pop_back();//恢复现场
                visited[i]=false;//恢复现场
            }
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        for(int i=0;i<nums.size();i++)
            visited[i]=false;
        
        vector<int> arr;
        dfs(nums,arr);

        return result;
    }
};

Problem: [236. 二叉树的最近公共祖先]
思路

Code


/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* ans=nullptr;
    //若以curNode为根结点的子树包含q或p则返回true 否则返回false
    bool dfs(TreeNode* curNode,TreeNode* p, TreeNode* q){
        if(curNode==nullptr)
            return false;
        
        bool flag1=dfs(curNode->left,p,q);
        bool flag2=dfs(curNode->right,p,q);

        if((flag1&&flag2)||(flag1&&curNode->val==p->val)||(flag1&&curNode->val==q->val)||(flag2&&curNode->val==p->val)||(flag2&&curNode->val==q->val)){
            ans=curNode;
        }

        return flag1|flag2|(curNode->val==p->val)|(curNode->val==q->val);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root,p,q);
        return ans;
    }
};

Problem: [206. 反转链表]
思路

Code


/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* ans;
    void dfs(ListNode* preNode,ListNode* curNode){
        if(curNode->next==nullptr){
            curNode->next=preNode;
            ans=curNode;
            return;
        }

        dfs(curNode,curNode->next);
        curNode->next=preNode;
        
    }
    ListNode* reverseList(ListNode* head) {
        if(head==nullptr)
            return nullptr;
        dfs(nullptr,head);
        return ans;
    }
};

DP

Problem: [LCR 089. 打家劫舍]
思路

  • dp[i] 表示前 i 间房屋能偷窃到的最高总金额

Code


class Solution {
public:
    int rob(vector<int>& nums) {
        
        if(nums.size()==1)
            return nums[0];

        vector<int> dp(nums.size());//dp[i] 表示前 i 间房屋能偷窃到的最高总金额
        //初始化
        dp[0]=nums[0];
        dp[1]=max(nums[0],nums[1]);

        //构造dp
        for(int i=2;i<nums.size();i++){
            dp[i]=max(nums[i]+dp[i-2],dp[i-1]);
        }

        return dp[nums.size()-1];
    }
};

Problem: [213. 打家劫舍 II]
法一:
思路

  • 将情况分为:偷第0家和不偷第0家
    在这里插入图片描述

Code

 
class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==1)
            return nums[0];
        if(nums.size()==2)
            return max(nums[0],nums[1]);
        if(nums.size()==3){
            return max(nums[0],max(nums[1],nums[2]));
        }
    
       

       //第0家偷 则第1家不能偷 最后一家不能偷
         vector<int> dp1(nums.size()); 
         dp1[0]=nums[0];
         dp1[1]=nums[0];
         dp1[2]=nums[2]+nums[0];
    
         for(int i=3;i<nums.size()-1;i++)
          dp1[i]=max(nums[i]+dp1[i-2],dp1[i-1]);
          dp1[nums.size()-1]=dp1[nums.size()-2];
          
     
            
      //第0家不偷
         vector<int> dp2(nums.size()); 
         dp2[0]=0;
         dp2[1]=nums[1];
         dp2[2]=max(nums[2],nums[1]);
         for(int i=3;i<nums.size();i++)
            dp2[i]=max(nums[i]+dp2[i-2],dp2[i-1]);
         
      
        return max(dp1[nums.size()-1],dp2[nums.size()-1]);
    }
};

法二
思路:

  • 将情况分为不偷第0家和不偷最后一家
    不偷第一家:[1,n-1]
    不偷最后一家:[0,n-2]
    (两家都不偷的情况也包含在里面)
    在这里插入图片描述

Code


class Solution {
public:
    //在num[left~right]范围内偷,返回最高金额
    int fun(vector<int>& nums,int left,int right){
        vector<int> dp(right-left+1);

        dp[0]=nums[left];
        dp[1]=max(nums[left],nums[left+1]);
        for(int i=2;i<dp.size();i++){
            dp[i]=max(nums[left+i]+dp[i-2],dp[i-1]);
        }
        return dp[dp.size()-1];

    }
    int rob(vector<int>& nums) {
        if(nums.size()==1)
            return nums[0];
        if(nums.size()==2)
            return max(nums[0],nums[1]);
        
        //不偷第一家:[1,n-1]  不偷最后一家:[0,n-2]  (两家都不偷的情况也包含在里面)
        return max(fun(nums,0,nums.size()-2),fun(nums,1,nums.size()-1));

        
    }
};

Problem: [122. 买卖股票的最佳时机 II]

思路

  • 考虑到每天交易结束后有2种状态:有股票和没股票,所以构造二维dp[n][2]

Code

 
class Solution {
public:
    
    int maxProfit(vector<int>& prices) {
        //dp[i][0]表示第i天交易结束后手上无股票的最大利润
        //dp[i][1]表示第i天交易结束后手上有股票的最大利润
        vector<vector<int>> dp(prices.size(),vector<int>(2));

        //初始化
        dp[0][0]=0;
        dp[0][1]=-prices[0];
        
        for(int i=1;i<prices.size();i++){
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
        }

        return dp[prices.size()-1][0];
        //return max(dp[prices.size()-1][0],dp[prices.size()-1][1]);
    }
};

Problem: [1143. 最长公共子序列]
思路

  • 最长公共子序列问题是典型的二维动态规划问题

Code


class Solution {
public:
    //最长公共子序列问题是典型的二维动态规划问题
    int longestCommonSubsequence(string text1, string text2) {
        

        //dp[i][j]表示text1前i个字符(text1[0~i-1])与text2前j个字符的最长子序列长度,这里思考为什么不表示text1[0~i]和text2[0~j]
        int dp[text1.size()+1][text2.size()+1];

        //初始化(回答上面的问题,因为这样便于初始化)
        for(int i=0;i<=text1.size();i++)
            dp[i][0]=0;
        for(int j=0;j<=text2.size();j++)
            dp[0][j]=0;

        //构造dp
        for(int i=1;i<=text1.size();i++)
            for(int j=1;j<=text2.size();j++){
                if(text1[i-1]==text2[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }

        return dp[text1.size()][text2.size()];


    }
};

Problem: [5. 最长回文子串]
思路

  • dp[i][j]表示s[i~j]是否是回文子串

Code


class Solution {
public:
    string longestPalindrome(string s) {
        bool dp[s.size()][s.size()];//dp[i][j]表示s[i~j]是否是回文子串
        int maxLen=1;
        string maxStr=string(1,s[0]);//s.substr(0,1)也行;

        //构造边界
        for(int i=0;i<s.size();i++){
            dp[i][i]=true;
            if(i!=s.size()-1){
                dp[i][i+1]=s[i]==s[i+1];
                if(dp[i][i+1]&&maxLen<2){
                     maxLen=2;
                     maxStr=s.substr(i,2);
                }
            }
              
        }

        //斜着向右上方构造dp
        //先枚举子串长度
        for(int k=0;k<(int)s.size()-2;k++){
            //构造长度为k的dp
            int i=0,j=i+2+k;
            while(j<s.size()){
                 dp[i][j]=dp[i+1][j-1]&(s[i]==s[j]);
                 //更新结果
                 if(dp[i][j]&&maxLen<(j-i+1)){
                     maxLen=j-i+1;
                     maxStr=s.substr(i,maxLen);
                 }
                 i++;
                 j++;
            }
        }

        return maxStr;
    }
};

二分法

Problem: [287. 寻找重复数]
思路

  • conut[i]表示nums中小于等于i的数字个数,
  • 经过分析可知count数组单调非减,且重复的数字是第一个count[i]>i的位置
  • 由于count单调非减,所以可用二分法查找所求位置,此时我们无需构造整个count数组,我们只需求每次的count[mid]即可(这样时间复杂度是nlogn级别,不会超时,若构造整个count数组则为n2级别,会超时)
  • 最后注意二分终止的特殊情况即可,当left==right-1时,答案必在其中一个

Code

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        /*
            1.conut[i]表示nums中小于等于i的数字个数,
            2.经过分析可知count数组单调非减,且重复的数字是第一个count[i]>i的位置
            3.由于count单调非减,所以可用二分法查找所求位置,此时我们无需构造整个count数组,我们只需求每次的count[mid]即可(这样时间复杂度是nlogn级别,不会超时,若构造整个count数组则为n2级别,会超时)
            
        */
        int ans=nums.size()-1;
        int left=1,right=nums.size();
        while(left<right){
            int mid=(left+right)/2;
            //计算count[mid]
            int countMid=0;
            for(int i=0;i<nums.size();i++){
                countMid+=(nums[i]<=mid);
            }

            if(countMid>mid){
                right=mid;
            }
            else{
                left=mid;
            }
            //当left==right-1时,答案必在其中一个
            if(left==right-1){
                int count1=0;
                int count2=0;
                for(int i=0;i<nums.size();i++){
                  count1+=(nums[i]<=left);
                  count2+=(nums[i]<=right);
                }
                if(count2>right){
                    ans=right;
                }
                if(count1>left){
                    ans=left;
                }
                break;
                 
            }
           
        }
      
        return ans;
        

    }
};

贪心

Problem: [402. 移掉 K 位数字]
思路

  • 构造result空字符串存放结果
  • 遍历num,对于num[i],直接放入result末尾,但在这之前看上一位数是否大于num[i],若大于则一直移除直到不大于num[i]
  • 注意一些特殊情况的处理和防止溢出

Code


class Solution {
public:
    string removeKdigits(string num, int k) {
        if(k==num.size())
            return "0";
            
        string result="";
        //对于num[i],直接放入result末尾,但在这之前看上一位数是否大于num[i],若大于则一直移除直到不大于
        for(int i=0;i<num.size();i++){
            while(k>0&&result.size()>0&&result[result.size()-1]>num[i]){
                result.pop_back();
                k--;
            }
            result.push_back(num[i]);
        }
        //若遍历完num后k仍然大于0,则此时num一定是非递减的,直接取前
        if(k>0)
            result=result.substr(0,result.size()-k);
       
        //移除前导0
        int pos=0;//记录result第一个不为0的下标
        if(result[0]=='0')
           while(result[pos]=='0')
               pos++;
        result=result.substr(pos);

        if(result.size()==0)
            return "0";
        else
            return result;
    }
};

Problem: [1047. 删除字符串中的所有相邻重复项]
思路

  • 由于栈中对象是字符,可将string直接当栈使用

Code

class Solution {
public:
    string removeDuplicates(string s) {
         string result="";//c++的字符串可以当栈用
         for(int i=0;i<s.size();i++){
             if(!result.empty()&&result.back()==s[i]){
                 result.pop_back();
             }
             else{
                 result.push_back(s[i]);
             }
         }
          
         return result;
    }
};

滑动窗口

代码模板

int left = 0, right = 0;//区间 [left, right) 是左闭右开

while (right < s.size()) {
    // 右移窗口
    char ch=s[right];
    if(满足特定条件){
   	    window[ch]++;//更新窗口数据
    }
    right++;

    while (window needs shrink) {
        // 左移窗口
        char ch=s[left];
        if(满足特定条件){
    	   window[ch]--;//更新窗口数据
    	}
    	left++;
    }
}

关键三个问题:

  1. 窗口中存放什么信息,何时修改窗口中的值
  2. 何时左移窗口
  3. 何时修改题目所求结果

Problem: [3. 无重复字符的最长子串]
思路

  • 窗口中存放s[left,right)中各个字符的个数
  • 当某个字符的个数大于1时,左移窗口
  • 窗口左移完毕直到每个字符个数都为1时,更新题目所求结果

Code

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
          if(s.size()==0)
            return 0;
            
          int maxLen=INT_MIN;
          map<char,int> window;//s窗口[left,right)中字符出现的次数

          int left=0,right=0;
          while(right<s.size()){
              char ch=s[right];
              window[ch]++;
              right++;

              while(window[ch]>1){
                  window[s[left]]--;
                  left++;
              }

              maxLen=max(maxLen,right-left);
          }
          
          return maxLen;

          
    }
};

Problem: [76. 最小覆盖子串]
思路

  • 窗口中存放s[left,right)的各个字符数目
  • 当s[left,right)中的字符涵盖p中所有字符时左移窗口
  • 在left左移的过程中更新题目所需结果

Code

class Solution {
public:
    string minWindow(string s, string t) {
         map<char,int> window;//存放s窗口[left,right)中各个字符的个数
         map<char,int> tMap;//存放t中各个字符的个数
         for(int i=0;i<t.size();i++)
             tMap[t[i]]++;

         int start=0,minLen=INT_MAX;//最小字串的开始位置和长度
         int valid=0;//用于标记当前窗口子串与t中字符匹配的种类数 
         int left=0,right=0;
         while(right<s.size()){
             char ch=s[right];
             window[ch]++;
             if(tMap.find(ch)!=tMap.end()&&window[ch]==tMap[ch]){
                 valid++;
             }
             right++;
             
             //左移窗口直到不符合条件的最小left
             while(valid==tMap.size()){
                 //更新结果
                 if(minLen>right-left){
                    start=left;
                    minLen=right-left;
                 }
                
                 
                 window[s[left]]--;//更新窗口
                 if(tMap.find(s[left])!=tMap.end()&&window[s[left]]<tMap[s[left]]){
                     valid--;//更新标记参数
                 }
                 left++;
             }
         }
         
         if(minLen==INT_MAX)
            return "";
         else 
            return s.substr(start,minLen);
    }
};

Problem: [438. 找到字符串中所有字母异位词]
思路

  • 窗口中存放s[left,right)的各个字符数目
  • 当s[left,right)中的字符涵盖p中所有字符时左移窗口
  • 在左移窗口时,直到移到s[left,right)长度等于p的长度时修改题目所需结果
    Code
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> result;

        map<char,int> window;//s[left,right)中各个字符个数
        map<char,int> pMap;
        for(int i=0;i<p.size();i++){
            pMap[p[i]]++;
        }

        int valid=0;
        int left=0,right=0;
        while(right<s.size()){
            char ch=s[right];
            window[ch]++;
            if(pMap.find(ch)!=pMap.end()&&pMap[ch]==window[ch])
                valid++;
            right++;

        
            while(valid==pMap.size()){
                if(right-left==p.size()){
                    result.push_back(left);
                }

                char ch=s[left];
                window[ch]--;
                if(pMap.find(ch)!=pMap.end()&&pMap[ch]>window[ch]){
                    valid--;
                }
                left++;
                
            }
        
        }

        return result;
    }
};

Problem: [209. 长度最小的子数组]
思路

Code


class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int minLen=INT_MAX;
        int windowSum=0;
        int left=0,right=0;//[left,right)
        while(right<nums.size()){
            windowSum+=nums[right];
            right++;

            while(windowSum>=target){
                minLen=min(minLen,right-left);
                windowSum-=nums[left];
                left++;

            }
        }

        if(minLen==INT_MAX)
            return 0;
        else 
            return minLen;
       

    }
}; 

其它

Problem: [186. 反转字符串中的单词 II]
思路

  • 先反转每个单词,再反转整个字符串

Code


class Solution {
public:
    
    void reverse(vector<char>& s,int i,int j){
        while(i<j){
            char c=s[i];
            s[i]=s[j];
            s[j]=c;
            i++;
            j--;
        }
    }
    void reverseWords(vector<char>& s) {
        //先反转每个单词 i j分别表示单词的左右边界
        for(int i=0;i<(int)s.size();){
            int j=i;
            while(j<s.size()&&s[j]!=' ')
               j++;

            reverse(s,i,j-1);
            i=j+1;
        }

        //再反转整个字符串
        reverse(s,0,s.size()-1);
    }
};```

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值