2020秋招_leetcode刷题记录


动态规划之正则表达 -> 总结了leetcode的刷题套路

归并排序之多线程实现(C++)

面试题 17.22. 单词转换 -> 还未通过

6. Z 字形变换 -> 找规律

top k 问题

面试题 17.14. 最小K个数 -> 优先队列,构建规模为k的大顶堆
最大的K个数 -> 构建规模为k的小顶堆

LRU

146. LRU缓存机制
双向链表存放数据(key和value),哈希表通过key映射到双向链表上的节点。
实现get()和put()函数。
注:下面代码,中最近最少使用的节点为双向链表的尾部。

struct DLinkedNode{
    
    
    DLinkedNode* pre, *next;
    int key, val;
    DLinkedNode(int _key, int _val): key(_key), val(_val), pre(nullptr), next(nullptr){
    
    }
};

class LRUCache {
    
    
public:

    LRUCache(int capacity) {
    
    
        head = new DLinkedNode(0,0);
        tail = new DLinkedNode(0,0);
        head -> next = tail;
        tail -> pre = head;
        cap = capacity;
    }
    
    int get(int key) {
    
    
        if(mp.find(key)==mp.end()){
    
    
            return -1;
        }
        DLinkedNode* p =  mp[key];
        deleteNode(p);
        insertInHead(p);
        return p->val;
        
    }
    
    void put(int key, int value) {
    
    
        if(mp.find(key)!=mp.end()){
    
    
            DLinkedNode* p = mp[key];
            p->val = value;
            deleteNode(p);
            insertInHead(p);
            return;
        }
        if(mp.size() == cap){
    
    
            DLinkedNode* tmp = tail->pre;
            mp.erase(tail->pre->key);  // map删除元素
            deleteNode(tail->pre);
            // delete tail->pre; //deleteNode后tail->pre的指向节点已经改变
            delete tmp;
        }
        DLinkedNode* p = new DLinkedNode(key, value);
        insertInHead(p);
        mp[key] = p;
    }

private:
    void deleteNode(DLinkedNode* p){
    
    
        DLinkedNode* pre = p->pre;
        DLinkedNode* next = p->next;
        pre->next = next;
        next->pre = pre;
        // delete p; // 节点P后续还需要被插入,因此此处内存不需要释放
    }

    void insertInHead(DLinkedNode* p){
    
    
        p->next = head->next;
        head->next->pre = p;
        head->next = p;
        p->pre = head;
    }
    // 辅助的头节点和尾节点
    DLinkedNode* head;
    DLinkedNode* tail;
    unordered_map<int, DLinkedNode*> mp;
    int cap;
};

剑指 Offer 42. 连续子数组的最大和 -> 贪心算法

    int maxSubArray(vector<int>& nums) {
    
    
        if(nums.size()==0){
    
    
            cout << "error!" << endl;
            return -1;
        }
        // 贪心算法
        int sum = nums[0], re = nums[0];
        for(int i = 1; i < nums.size(); i++){
    
    
            // 如果s连续子数组的和小于0了,则从当前重新开始计算连续子数组的和
            if(sum < 0){
    
    
                sum = nums[i];
            }else{
    
    
                sum += nums[i];
            }
            re = max(sum,re);
        }
        return re;

上面的题目扩展到二维数组:面试题 17.24. 最大子矩阵

动态规划

300. 最长上升子序列

    // dp[i]表示前i个元素中以nums[i]作为最大元素的最长上升子序列长度
    int lengthOfLIS(vector<int>& nums) {
    
    
        if(nums.empty()) return 0;
        int n = nums.size();
        vector<int> dp(n+1,1); // 初始化均为1
        for(int i = 2; i <= n; i++ ){
    
    
            for(int k = 1; k < i; k++){
    
    
                if(nums[i-1] > nums[k-1]){
    
    
                    dp[i] = max(dp[i],dp[k]+1);
                }
            }
        }
        int m = 0;
        for(int num:dp){
    
    
            m = max(m,num);
        }
        return m;
    }

5. 最长回文子串

        string longestPalindrome(string s) {
    
    
        int n = s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));
        int m = 1, start = 0;
        // 长度为1和2的情况
        for(int i = 0; i < n; i++){
    
    
            dp[i][i] = true;
            if(i+1 < n){
    
    
                dp[i][i+1] = s[i]==s[i+1]; 
                if(2 > m && dp[i][i+1]){
    
    
                    start = i;
                    m = 2;
                }
            }
        }
        for(int len=3; len <= n ; len++){
    
    
            for(int j = 0; j+len-1 < n; j++){
    
    
                dp[j][j+len-1] = dp[j+1][j+len-2] && s[j]==s[j+len-1];
                if(len > m && dp[j][j+len-1]){
    
    
                    start = j;
                    m = len;
                }
            }
        }
        return s.substr(start,m);
    }

二叉树

19. 二叉树的下一个节点

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode *father;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL), father(NULL) {}
 * };
 */
class Solution {
    
    
public:
    TreeNode* inorderSuccessor(TreeNode* p) {
    
    
     	// 分两种情况:第一种是该节点有右子树,则右子树的最左边的节点为中序遍历下一个节点
        if(p->right!=nullptr){
    
    
            TreeNode* q = p->right;
            while(q->left!=nullptr){
    
    
                q = q->left;
            }
            return q;
        }
        // 第二中情况是该节点没有右子树,则递归寻找父节点,
        // 若节点为父节点的左孩子,则此父节点为中序遍历的下一个节点
        // 如果节点的父节点为空,则说明节点p为中序遍历输出的最后一个节点
        TreeNode* q = p;
        while(q->father!=nullptr && q->father->right==q){
    
    
            q = q->father;
        }
        return q->father;
    }
};

根据条件构建二叉树

105. 从前序与中序遍历序列构造二叉树
106. 从中序与后序遍历序列构造二叉树
面试题 04.02. 最小高度树 -> 高度最小,取已排序的数组中间的数为根节点

二叉树后续遍历

根据"左右根"的遍历方式,可以利用左右子树返回的结果,自底向上计算
124. 二叉树中的最大路径和
二叉树的最近公共祖先
求一个二叉树中两个结点的最大距离
思路:将所有的结点的左右子树的高度和计算一下,然后取出最大值,就是最远的距离。

中序遍历二叉搜索树

解析文章:中序遍历团灭系列二叉搜索树问题
530. 二叉搜索树的最小绝对差 -> 中序遍历二叉搜索树获得的就是升序数组。

二分查找

基于排除法的二分查找,查找位置的左边界和右边界:
67. 数字在排序数组中出现的次数

	// 当只剩[1,1]两个元素,mid = l + (r-l)/2 = l = 0,所以必须 l = mid + 1 才能避免死循环
	// mid向下取整,取的是重复元素位置的左边界,l = mid + 1
    int lowBound(vector<int>& nums, int k){
    
    
        int l = 0, r = nums.size()-1;
        while(l < r){
    
    
            int mid = l + (r-l)/2;
            if(nums[mid] < k){
    
    
                l = mid + 1;
            }else{
    
    
                r = mid;
            }
        }
        return nums[l]==k ? l : -1;
    }
    
    int upBound(vector<int>& nums, int k){
    
    
        int l = 0, r = nums.size()-1;
        while(l < r){
    
    
            int mid = l + (r-l+1)/2;
            if(nums[mid] > k){
    
    
                r = mid - 1;
            }else{
    
    
                l = mid;
            }
        }
        return nums[l]==k ? l : -1;
    }


    int getNumberOfK(vector<int>& nums , int k) {
    
    
        if(nums.empty()){
    
    
            return 0;
        }
        int l = lowBound(nums,k);
        int r = upBound(nums,k);
        return  l==-1 ? 0 : r-l+1; 
    }

题目:给出一个字符串,每个字母都连续出现两次,且这两次是相邻的,只有一个字母出现了一次。返回出现一次的那个字母。 -> 二分查找(奔着目标元素的二分查找,循环条件为l<=r,然后找到目标元素就直接返回,出了循环表示找不到)
例子: aax, xaabb, aaxbb 返回都是x

char findChar(string s){
    
    
    int l = 0, r = s.size()-1;
    while(l <= r){
    
    
        int mid = l + (r-l+1)/2;
        // 与两边都不相等,前面两种情况分别是出现一次的字母在头部和尾部的情况
        if( (mid-1<0 && s[mid]!=s[mid+1]) || (mid+1 == s.size() && s[mid]!=s[mid-1]) || (s[mid]!=s[mid-1] && s[mid]!=s[mid+1])){
    
     
            return s[mid];
        }else if(s[mid]==s[mid+1]){
    
     // 与右边相等
            if((mid+1-1)%2==0){
    
     //判断左边0~mid-1子串长度是否为偶数
                l = mid;
            }else{
    
    
                r = mid-1;
            }
        }else{
    
     // 与左边相等
            if((mid+1)%2==0){
    
     //判断左边0~mid子串长度是否为偶数
                l = mid+1;
            }else{
    
    
                r = mid;
            }
        }
    }
    return '0'; // 找不到返回字符‘0’
}

int main(){
    
    
    string s = "aax";
    cout << findChar(s) << endl;
}

面试题 10.03. 搜索旋转数组 -> 两次二分查找过不了特殊例子(先找到最小元素的下标,然后确定目标元素所在区间,再进行第二次二分查找);一次二分查找理解起来有点麻烦,详情见提交的代码。
面试题 10.05. 稀疏数组搜索-> 遇到空字符串退化为线性查找,注意用基于排除法的二分查找时需要避免出现死循环的情况!并且在出了循环的时候还要判断一次nums[l]是否等于目标元素!(当不考虑元素重复时,奔着目标元素的二分查找即可,即循环条件为l<=r,然后找到目标元素就直接返回,出了循环表示找不到)

    int findString(vector<string>& words, string s) {
    
    
        int l = 0, r = words.size()-1;
        while(l < r){
    
    
            int mid = l + (r-l+1)/2;

            // 当words[mid]为空字符串的时候,退化为线性搜索
            // 注意边界是l和r,e而不是0和 words.size()-1!!
            while(mid<=r && words[mid]==""){
    
    
                mid++;
            }
            if(mid>r){
    
     // 说明[l,r]右半部分都为空字符串
                mid = l + (r-l+1)/2; // mid需要重新计算
                r = mid - 1;
                continue;
            }

            if(words[mid] <= s){
    
    
                l = mid;
            }else if(words[mid] > s){
    
    
                // 由于碰到空字符串mid会向右移动,可能会出现mid=r,
                // 因此r=mid会出现死循环情况,所以必须r=mid-1,此时mid向上取整,即mid = l + (r-l+1)/2
                r = mid-1; 
            }
        }
        return words[l]==s?l:-1;
    }

2020.06.15 -> 手写了冒泡排序,选择排序,插入排序,堆排序,快速排序,归并排序(堆排序还是不太熟练,快速排序遇到点小错误)

2020.06.27 -> 手写了堆排序、快速排序,

哈希

1. 两数之和 -> 暴力求解O(n^2);排序O(nlogn)+双指针O(n);哈希O(n),元素值哈希映射对应的下标

抽屉原理+原地哈希

题目:数组大小为n,数组中元素范围为[1,n],元素有重复,找出缺失(重复)的元素。
如果允许O(n)的空间复杂度,则可以用额外的哈希表记录元素出现的次数。
若只允许O(1)空间复杂度。
思路:在原来的数组中进行哈希。
哈希函数为:f(nums[i]) = nums[i] - 1
448. 找到所有数组中消失的数字
442. 数组中重复的数据

    vector<int> findDisappearedNumbers(vector<int>& nums) {
    
    
        int n = nums.size();
        for(int i = 0; i < n; i++){
    
    
            // 需要把nums[i]放到下标为nums[i]-1处,
            // 如果下标nums[i]-1处元素的值已经为nums[i],则不用交换
            while(nums[i]!=nums[nums[i]-1]){
    
     
                swap(nums[i],nums[nums[i]-1]);
            }
        }
        vector<int> v;
        // 如果所有元素只出现1次,那么交换后最终满足nums[i]=i+1
        for(int i = 0; i < n; i++){
    
    
            if(nums[i]!=i+1){
    
    
                v.push_back(i+1); // 没有出现过的数字
                // v.push_back(nums[i]); // 出现过两次的数字
            }
        }
        return v;
    }

41. 缺失的第一个正数 -> 与[448. 找到所有数组中消失的数字]类似

int firstMissingPositive(vector<int>& nums) {
    
    
        int n = nums.size();
        for(int i = 0; i < n; i++){
    
    
            while(nums[i]>=1 && nums[i]<=n && nums[i]!=nums[nums[i]-1]){
    
    
                swap(nums[i],nums[nums[i]-1]);
            }
        }
        int re = n+1; // 如果数组置换后,为元素1~n,则缺失的是n+1
        for(int i = 0; i < n; i++){
    
    
            if(nums[i]!=i+1){
    
    
                re = i+1;
                break;
            }
        }
        return re;
    }

268. 缺失数字

int missingNumber(vector<int>& nums) {
    
    
        int n = nums.size();
        for(int i = 0; i < n; i++){
    
    
            while(nums[i] != n && nums[i]!=nums[nums[i]]){
    
    
                swap(nums[i],nums[nums[i]]);
            }
        }
        int re = n;
        for(int i = 0; i < n; i++){
    
    
            // if(nums[i]!=i){
    
    
            //     re = i;
            //     break;
            // }
            if(nums[i]==n){
    
     // 数字n会到缺失那个数为下标的位置中
                re = i;
                break;
            }
        }
        return re;
    }

由于是连续数字中只缺失一个数字,因此有更好的办法,可以不改变传入的数组。

    int missingNumber(vector<int>& nums) {
    
    
        int n = nums.size();
        int sum = (0+n)*(n+1)/2;
        for(int num:nums){
    
    
            sum-=num;
        }
        return sum;
    }

双指针

15. 三数之和 -> 排序,固定一个数,剩下两个数用双指针,O(n^2)。(如何去除重复是难点)
18. 四数之和 -> 排序,固定两个数,剩下两个数用双指针,O(n^3)。(如何去除重复是难点)

链表

23. 合并K个排序链表 -> 优先队列O(knlog(k));分治合并O(knlog(k));两两合并O(k^2*n)

// 仿函数:类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
struct cmp{
    
    
    bool operator() (ListNode* l1, ListNode* l2){
    
    
        return l1->val > l2->val; // 小顶堆
    }
};

class Solution {
    
    
public:
    // lists存放k个指向链表的头节点的指针
    // 维护堆O(logk),k个链表最多k*n个节点,所以时间复杂度O(k*n*logk)
    ListNode* mergeKLists(vector<ListNode*>& lists) {
    
    
        priority_queue<ListNode*,vector<ListNode*>,cmp> q;
        for(auto head:lists){
    
    
            if(head == nullptr){
    
     //空链表不加入优先队列
                continue;
            }
            q.push(head);
        }
        ListNode* head = new ListNode(0); // 辅助节点
        ListNode* p = head;
        while(!q.empty()){
    
    
            ListNode* t = q.top();
            q.pop();
            p->next = t;
            p = p->next;
            if(t->next != nullptr){
    
    
                q.push(t->next);
            }
        }
        return head->next;
    }
};

19. 删除链表的倒数第N个节点 -> 辅助节点+双指针

    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        ListNode* fast = head, *slow = head;
        ListNode* pre = new ListNode(0); // 辅助节点,在slow的前一步
        pre->next = head;
        head = pre;
        while(n > 0){
    
    
            fast = fast->next;
            n--;
        }
        // fast 比 slow 快 n步
        while(fast!=nullptr){
    
    
            fast = fast->next;
            pre = pre->next;
            slow = slow->next;
        }
        // 删除倒数第n个节点
        pre -> next = slow -> next;
        return head->next;
    }

206. 反转链表 -> 双指针pre和now,pre指向now的前一个节点,初始为空指针,now->next = pre。

	// 双指针版本
    ListNode* reverseList(ListNode* head) {
    
    
        if(head==nullptr){
    
     // 空链表情况
            return head;
        }
        ListNode* pre = nullptr;
        ListNode* now = head;
        ListNode* tmp = now->next;
        while(tmp!=nullptr){
    
    
            now->next = pre;
            pre = now;
            now = tmp;
            tmp = tmp->next;
        }
        // 出循环时tmp为为空指针的时候,now还需要指向前一个节点一次!
        now->next = pre;
        return now;
    }
    // 递归版本
    ListNode* reverseList(ListNode* head) {
    
    
        if(head==nullptr || head->next == nullptr){
    
     // head==nullptr是空链表的情况
            return head;
        }

        ListNode* t = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return t;
    }

695. 岛屿的最大面积 -> dfs

    int maxAreaOfIsland(vector<vector<int>>& grid) {
    
    
        int r = grid.size();
        if(r == 0){
    
    
            return 0;
        }
        int c = grid[0].size(), re = 0;
        vector<vector<int>> g = grid; // 为了不修改传入的矩阵
        for(int i = 0; i < r; i++){
    
    
            for(int j = 0; j < c; j++){
    
    
                if(g[i][j]==1){
    
    
                    re = max(dfs(g,i,j),re);
                }
            }
        }
        return re;
    }

    int dfs(vector<vector<int>>& g, int x, int y){
    
    
        // 边界条件判断
        if(x < 0 || x >= g.size() || y < 0 || y >=g[0].size() || g[x][y]==0){
    
    
            return 0;
        }else{
    
    
            g[x][y]=0;
        }

        int down = dfs(g,x+1,y);
        int up = dfs(g,x-1,y);
        int right = dfs(g,x,y+1);
        int left = dfs(g,x,y-1);

        return up+down+right+left+1;
    }  

343. 整数拆分

  1. 动态规划,O(n^2),当n很大的时候只能使用贪心法。
    // 假设 n 不小于 2 且不大于 58。
    int integerBreak(int n) {
    
    
        vector<int> dp(n+1,0);
        // 初始化条件(除了2和3,其余拆分成两个正整数后的积要大于等于i)
        for(int i = 1; i <= n; i++){
    
    
            dp[i] = i;
        }
        // 题目要求至少拆分两段,只有n等于2或3的时候拆分后积小于它本身
        if(n==2 || n==3){
    
     
            return n-1;
        }
        for(int i = 1; i <= n; i++){
    
    
            for(int j = 1; j < i ; j++){
    
    
                dp[i] = max(dp[i],dp[j]*dp[i-j]);
            }
        }
        return dp[n];
    }
  1. 贪心法,最后的结果一定是由若干个3和1或2个2组成,先拆成若干个3,最后拆成1个或者两个2。(理论证明略)
    int cuttingRope(int n) {
    
    
        long long re = 1;
        // 由于至少拆成两段,故n<=3时算特殊情况
        if(n==2 || n == 3){
    
    
            return n-1;
        }
        while(n > 4){
    
    
            re = (re*3)%1000000007;
            n-=3;
        }
        re = (re*n)%1000000007;
        return re;
    }

面试题 08.04. 幂集 -> 输出集合的所有不重复子集合,前提是集合中不包含重复的元素。dfs+回溯,每个集合元素都有包含或者不包含两种可能,因此求二叉树节点的路径,二叉树的高度等于集合的元素个数;另一种方法是,以空集开始,遍历集合中的每一个元素,每次在集合中的每一个子集末尾加添加新元素,并将添加新元素后的所有子集加入到原来的集合中。(如果是计算子集合的个数,dp[i] = dp[i-1] +dp[i-1],dp[0]=1,包括空集)
940. 不同的子序列 II -> 集合中包含重复元素,求非空子序列的个数。动态规划:新加入元素与集合元素不同时: d p [ i ] = d p [ i − 1 ] + d p [ i − 1 ] + 1 , d p [ 0 ] = 0 , dp[i] = dp[i-1] +dp[i-1] + 1,dp[0]=0, dp[i]=dp[i1]+dp[i1]+1dp[0]=0不包括空集;新加入元素与集合中元素相同时, d p [ i ] = d p [ i − 1 ] + d p [ i − 1 ] − d p [ j − 1 ] dp[i] = dp[i-1] +dp[i-1] - dp[j-1] dp[i]=dp[i1]+dp[i1]dp[j1],j为集合中与新加入元素相同的元素的最大位置从1开始)。 解析
115. 不同的子序列 -> 动态规划,还未做

注意:下面相减小于0时需要特殊处理,即dp[i]+=mod;

    int distinctSubseqII(string S) {
    
    
        vector<int> dp(S.size()+1,0); // 求非空子集个数,dp[0]=0
        vector<int> hash(128,-1); // 存储字符最近一次出现的下标
        int mod = pow(10,9)+7;
        for(int i = 1; i <= S.size(); i++){
    
    
            if(hash[S[i-1]] == -1){
    
      
                dp[i] = (dp[i-1] + dp[i-1] + 1)%mod;
            }else{
    
    
                int p = hash[S[i-1]]+1; // 与新加入字符相同字符的位置(1开始)
                dp[i] = (dp[i-1] + dp[i-1] - dp[p-1])%mod; // 此处dp[i]可能会小于0
                if(dp[i] < 0){
    
    
                    dp[i]+=mod;
                }
            }
            hash[S[i-1]] = i-1; // 更新字符的位置,使其始终为最近一次出现的下标
        }
        return dp[S.size()];
    }

下面三题有通用的解法,即n循环除以2或3或4,最后判断n是否为1。(n小于等于0肯定不是,首先排除)
231. 2的幂

	// 解法1:位运算
    bool isPowerOfTwo(int n) {
    
    
        if(n <= 0){
    
     //0和负数不可能是2的幂次方
            return false;
        }
        int count = 0;
        // 思路:2的幂,二进制中除了符号位,其余位有且仅有有一个位为1
        // 对于int类型,最后一位是符号位,故循环退出条件为 i < 31
        for(int i = 0; i < 31 && count <= 1; i++){
    
    
            if(((n>>i) & 1 ) == 1){
    
    
                count++;
            }
        }
        return count==1;
    }
    // 解法2:数学原理
    bool isPowerOfTwo(int n) {
    
    
        // 当n大于0,(n & (n-1)) == 0 是n为2的幂次方的充要条件
        return n > 0 && (n & (n-1)) == 0; //注意运算优先级== > & > &&
    }

326. 3的幂 -> 通用解法

    bool isPowerOfThree(int n) {
    
    
        if(n <= 0){
    
     // n可能为负数
            return false;
        }
        if(n == 1){
    
     // 0次幂
            return true;
        }
        while(n > 1){
    
    
            if(n%3==0){
    
    
                n/=3;
            }else{
    
    
                return false;
            } 
        }
        return true;
    }

342. 4的幂

    bool isPowerOfFour(int num) {
    
    
    	// 第二个条件是判断是否为2的幂次,第三个条件是在第二个条件的基础上判断是否为4的幂次
        return num > 0 && (num&(num-1))==0 && num%3 ==1;
    }

剑指 Offer 62. 圆圈中最后剩下的数字

    // 解法1:递归,状态转移方程f[n]= (f[n-1]+m)%n,f[n]表示有n个数时,最后剩下的那个数的下标
    int lastRemaining(int n, int m) {
    
    
        return f(n,m);
    }

    int f(int n, int m){
    
    
        if(n==1){
    
     // 当只剩一个数,最后剩下那个数下标必为0
            return 0;
        }

        return (f(n-1,m)+m)%n;
    }
    // 解法2:倒推,f[n]= (f[n-1]+m)%n
        int lastRemaining(int n, int m) {
    
    
        // 取模的时候会出现0,因此计算的时候假设位置从0开始(如果数字从1开始,最后pos需要加1)
        int pos = 0; 
        for(int i = 2; i <= n; i++){
    
    
            pos = (pos + m)%i;
        }
        return pos;
    }

面试题 16.16. 部分排序 -> 左右两次遍历,从左向右遍历确定右边界n,从右向左遍历确定左区间m,返回[m,n]

     // 默认array为升序
    vector<int> subSort(vector<int>& array) {
    
    
        int len = array.size();
        int m = -1, n = -1;
        // 索引区间[m,n]右边的数应该满足大于等于其左侧最大的值
        int MAX = INT_MIN;
        for(int i = 0; i < len; i++){
    
    
            if(array[i] >= MAX){
    
    
                MAX = max(array[i], MAX);
            }else{
    
     // 不满足array[i] >= MAX条件说明在区间[m,n]内,最后一个不满足的元素下标为n
                n = i;
            }
        }
        // 索引区间[m,n]左边的数应该满足小于等于其右侧最小的值
        int MIN = INT_MAX;
        for(int i = len-1; i >= 0; i--){
    
    
            if(array[i] <= MIN){
    
    
                MIN = min(array[i], MIN);
            }else{
    
    
                m = i;
            }
        }
        return vector<int>{
    
    m,n};
    }

dfs+回溯(枚举所有的可能,或者剪枝)

679. 24 点游戏

模式匹配

C语言strstr()函数:返回字符串中首次出现子串的地址。
28. 实现 strStr()
方法一:

    // sunday算法,最坏O(n*m),平均O(n)
    int strStr(string haystack, string needle) {
    
    
        int m = needle.size();
        // 空字符串返回0
        if(m == 0){
    
    
            return 0;
        }
        // 记录当前字符下标(有重复字符,取下标大的值)
        unordered_map<char,int> table;
        for(int i = 0; i <= m; i++){
    
    
            table[needle[i]] = i;
        }

        for(int i = 0; i + m <= haystack.size();){
    
    
            // needle与haystack中[i,i+m-1]子串进行比较
            if(needle == haystack.substr(i,m)){
    
    
                return i;
            }
            if(needle.find(haystack[i+m]) != string::npos){
    
    
                // needle中找到haystack中第i+m个字符,进行偏移
                i += m - table[haystack[i+m]]; 
            }else{
    
    
                // needle中找不到haystack第i+m个字符,needle头部直接跳到i+m+1位置进行比较
                i += m + 1; 
            }
        }
        return -1;
    }

方法二:字符串匹配的KMP算法 -> O(m+n)

10. 正则表达式匹配
动态规划:12ms

    // dp[i][j]表示s的前i个字符和p的前j个字符能否匹配上
    bool isMatch(string s, string p) {
    
    
        vector<vector<bool>> dp(s.size()+1,vector<bool>(p.size()+1,false));
        dp[0][0] = true; // 初始化状态,其余为false
        for(int i = 0; i <= s.size(); i++){
    
    
            for(int j = 1; j <= p.size(); j++){
    
    
                // 注意:s和p的下标是从0开始的
                if(p[j-1] != '*'){
    
    
                    dp[i][j] = i>0 && dp[i-1][j-1] && (s[i-1]==p[j-1] || p[j-1]=='.');
                }else{
    
    
                    if(j>=2){
    
    
                        if(i==0){
    
    
                        	// 对于空字符串,只能不匹配
                            dp[0][j] = dp[0][j-2];
                        }else{
    
     
                        // dp[i][j-2]表示不匹配的情况,( (s[i-1]==p[j-2] || p[j-2]=='.') && dp[i-1][j])表示匹配的情况
                            dp[i][j] = dp[i][j-2] || ( (s[i-1]==p[j-2] || p[j-2]=='.') && dp[i-1][j]);
                        }
                    }
                }
            }
        }
        return dp[s.size()][p.size()];
    }

递归:780ms

    bool isMatch(string s, string p) {
    
    
        if(p.empty()){
    
    
            return s.empty();
        }

        bool match = false;
        if(!s.empty() && (p[0]==s[0] || p[0]=='.')){
    
    
            match = true;
        }

        if(p.size() > 1 && p[1]=='*'){
    
    
            if(match){
    
    
                // 当第一个字符匹配上,可以匹配多次(递归中一次一次匹配),也可以不匹配
                return isMatch(s.substr(1),p) || isMatch(s,p.substr(2)); 
            }else{
    
    
                // 当第一个字符匹配不上,只能不匹配
                return isMatch(s,p.substr(2)); 
            } 
        }else{
    
    
            return match && isMatch(s.substr(1),p.substr(1));
        }
    }

单调队列 -> 滑动窗口

求最大值:单调非严格递队列(栈);
求最小值:单调非严格递队列(栈);

239. 滑动窗口最大值
「单调队列」的核心思路和「单调栈」类似。单调队列的 push 方法依然在队尾添加元素,但是要把前面比新元素小的元素都删掉。
需要关注两点:

  1. 为什么保证队列中的元素非严格递减?非严格递增可不可以?
  2. !q.empty() && nums[i-1]==q.front()为什么可以判断队列头部元素可以出列?

举例:[1,2,3,8,5,5,7,3],滑动窗口大小为3。
那么初始时候队列应该为[3],因为1和2早于3出滑动窗口,3又大于1和2,因此在1和2出滑动窗口时,滑动窗口内最大元素仍是3。这就是为什么要把前面比新元素小的元素都删掉的原因。
队列中元素的变化:
[8]
[8,5]
[8,5,5]
[7] -> 8出窗口,7进入,5,5从队列中删除。
[7,3]

根据条件!q.empty() && nums[i-1]==q.front()能安全地pop_front是因为队列是非严格递减,窗口中的最大值有重复时保留重复。

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    
    
        deque<int> q;
        vector<int> mval;
        if(k > nums.size()){
    
    
            return mval;
        }
        // 单调队列,保证队列中的元素非严格递减
        for(int i = 0; i < k; i++){
    
    
            while(!q.empty() && nums[i] > q.back()){
    
    
                q.pop_back();
            }
            q.push_back(nums[i]);
        }
        mval.push_back(q.front());
        for(int i = 1; i <= nums.size()-k; i++){
    
    
            if(!q.empty() && nums[i-1]==q.front()){
    
    
                q.pop_front();
            }
            while(!q.empty() && nums[i+k-1] > q.back()){
    
    
                q.pop_back();
            }
            q.push_back(nums[i+k-1]);
            mval.push_back(q.front());
        }
        return mval;
    }

单调栈

题目描述:有一个字符串s,仅有数字组成(第一位数字不为0),现在将字符串的删除k个字符,使得剩下的字符组成的数字最小,不能打乱字符的顺序。

/*
71245323308
4
1223308

1221
2
11

324682385
3
242385
*/
// 思路:数字最小,必定是一个非严格递增的序列,可以使用单调栈
// 从字符串的左边开始,保证放入栈的数字非严格递增(后一个数大于等于前一个数),
// 栈顶大于将要放入的数字,则将栈顶弹出,直到栈顶弹出k次
int main() {
    
    
    string s;
    int k;
    cin >> s;
    cin >> k;
    stack<char> st; // 单调栈
    st.push(s[0]);
    int count = 0;
    for(int i = 1; i < s.size(); i++){
    
    
        if(count < k){
    
    
            while(!st.empty() && s[i] < st.top()){
    
    
                st.pop();
                count++;
                if(count == k){
    
    
                    break;
                }
            }
        }
        st.push(s[i]);
    }
    // 字符串全部数字入栈后弹出次数小于k,则继续弹出栈顶,直到弹出k次
    while(count < k){
    
    
        st.pop();
        count++;
    }
    string out;
    while(!st.empty()){
    
    
        out += st.top();
        st.pop();
    }
    reverse(out.begin(),out.end());
    cout << stoi(out) << endl; // stoi()是为了去掉头部的0
    return 0;
}

猜你喜欢

转载自blog.csdn.net/XindaBlack/article/details/106631440
今日推荐