LeetCode(601—)

这篇博客涵盖了从LeetCode 601到1143的多道题目,包括种花问题、二叉树操作、查找重复文件、数学问题、字符串处理、链表操作等。通过这些题目,可以锻炼和提升解决算法问题的能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

LeetCode 605. 种花问题

/*
从前往后遍历数组,若发现空的位置,则判断前后是否已经有花,若没有则安放即可
*/
class Solution {
public:
    bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        for (int i = 0; i < flowerbed.size(); i++)
            if (flowerbed[i] == 0) {
                if ((i == 0 || flowerbed[i - 1] == 0) && 
                (i == flowerbed.size() - 1 || flowerbed[i + 1] == 0)) {
                    flowerbed[i] = 1;
                    n--;
                }
            }
        return n <= 0;
    }
};
/*
给定一个01数组,其中没有两个连续的1,判断是否能再添加n个1使得所有的1还是不相邻,以01数组中连续两个1(不是相邻,也没有相邻)为一段,这一段中能填多少个1取决于中间有多少个0,可看出规律,如果是内部的k个0的话,可以填(k-1)/2,如果是有一边是边界上的0的话,k/2
如果两边都是边界的0的话 (k+1)/2,所以要扫描每一段有多少个连续的0可以用双指针算法
*/
class Solution {
public:
    bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        if (!n) return true;
        int res = 0;
        for (int i = 0; i < flowerbed.size(); i ++ ) {
            if (flowerbed[i]) continue;
            int j = i;//此时i是第一个0,前面要么没有要么是1
            while (j < flowerbed.size() && flowerbed[j]==0) j ++ ;
            int k = j - i - 1;//去掉i,j两个边界之后连续的0的个数
            if (i==0) k ++ ;//如果左边是边界的话
            if (j == flowerbed.size()) k ++ ;//如果右边是边界的话
            res += k / 2;
            if (res >= n) return true;
            i = j;
        }
        return false;
    }
};

LeetCode 606. 根据二叉树创建字符串

class Solution {
public:
/*
根节点(左子树)(右子树)
*/
    string ans;

    string tree2str(TreeNode* t) {
        dfs(t);
        return ans;
    }

    void dfs(TreeNode* t) {
        if (!t) return;
        ans += to_string(t->val);
        if (t->left || t->right) {
            ans += '(';//当前节点只要有子节点,左子树必须要建
            dfs(t->left);
            ans += ')';
        }
        if (t->right) {
            ans += '(';//当前节点只要有右节点,右子树才能建
            dfs(t->right);
            ans += ')';
        }
    }
};

LeetCode 609. 在系统中查找重复文件


LeetCode 611. 有效三角形的个数

class Solution {
public:
/*
(枚举+线性扫描) O(n^2)
将整个数组从小到大排序。
在排序后的数组上,从前向后枚举三角形的两个较短的边 nums[i] 和 nums[j],较短的边不能为 0。
在确定了最短的边 nums[i] 后,令 k = i + 1,然后再枚举次短的边 nums[j] 后,不断向后更新 k,使得 nums[i] + nums[j] <= nums[k]。由于 nums[j] 单调递增,故 k 也会单调递增。最终对于每次枚举的 nums[j],累计的答案个数就是 k - j - 1。
时间复杂度
数组排序时间复杂度为 O(nlogn),枚举最短边的时间复杂度为 O(n)。确定了最短边之后,每个值最多只会遍历两次,故总时间复杂度为 O(n^2)。
*/
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int res = 0;
        for (int i = 0; i < nums.size(); i ++ )
            for (int j = i - 1, k = 0; j > 0 && k < j; j -- ) {
                while (k < j && nums[k] <= nums[i] - nums[j]) k ++ ;
                res += j - k;
            }
        return res;
    }
};

LeetCode 617. 合并二叉树

/*
(递归) O(n+m)
主要思路是以第一棵树为基准,将第二棵树往第一课树上添加。
从根结点开始递归,递归时分为四种情况:t1 和 t2 均为空,则返回空;t1 为空,t2 不为空,则返回 t2;t1 不为空,t2 为空,则返回 t1;t1 和 t2 均为空,则分别递归左子树和右子树,令 t1 的左儿子指向递归返回的左子树,t1 的右儿子指向递归返回的右子树,最后返回 t1。
时间复杂度
每个结点最多遍历一次,故时间复杂度为 O(n+m)
 */
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (!t1 && !t2)
            return NULL;
        if (!t1 && t2)
            return t2;
        if (t1 && !t2)
            return t1;
        t1 -> val += t2 -> val;
        t1 -> left = mergeTrees(t1 -> left, t2 -> left);
        t1 -> right = mergeTrees(t1 -> right, t2 -> right);
        return t1;
    }
};

LeetCode 621. 任务调度器

class Solution {
public:
/*
使用出现次数最多的任务去构造循环节,可以证明总是可以在循环间隙节中将非最频繁任务插完并且不违反规则。
先找出现次数最多的字母A,最多次数mc,如果只安排A类任务,就是第一行先放一个A,然后空n个位置,然后第二行再放一个A,然后再空n个位置...最后第mc行再放一个A,总共放mc个A,所以只考虑A的情况需要(mc-1)(n+1)+1个位置
如果另外一类任务B也出现了mc次,每一行内部只能放一个B,这样的话两类任务总共需要(mc-1)(n+1)+2个位置
所以如果总共有k类任务(k个字母)均出现mc次,总共需要(mc-1)(n+1)+k个位置
考虑边界情况,如果k>n怎么办,也就是n个空位放不下这些字母,其次剩余的次数没这么多的字母怎么排
对于其余字母来说,出现次数必然<mc,也就是前mc-1行的一整列一定能放完某一类出现次数小于mc的字母,然后这一列剩余的空位再继续排下一类字母,并且一定不会再同一行排到两个同类的字母,因为这样的话出现次数必然大于mc-1(这种字母是倒着排)
对于某些类的字母出现次数大于n的情况,直接在每一行末尾加空位即可
*/
    int leastInterval(vector<char>& tasks, int n) {
        unordered_map<char, int> hash;
        for (auto c: tasks) hash[c] ++ ;
        int maxc = 0, cnt = 0;
        for (auto [k, v]: hash) maxc = max(maxc, v);
        for (auto [k, v]: hash)
            if (maxc == v)
                cnt ++ ;
        return max((int)tasks.size(), (maxc - 1) * (n + 1) + cnt);//后者是有空位的情况
    }
};

LeetCode 622. 设计循环队列


LeetCode 623. 在二叉树中增加一行

class Solution {
public:
    TreeNode* addOneRow(TreeNode* root, int v, int d) {
        if (d == 1) {//特判
            auto cur = new TreeNode(v);
            cur->left = root;
            return cur;
        }
        queue<TreeNode*> q;
        q.push(root);
        for (int i = 0; i < d - 2; i ++ ) {//到达第d-1层,走d-2步
            for (int j = q.size(); j; j -- ) {
                auto t = q.front();
                q.pop();
                if (t->left) q.push(t->left);
                if (t->right) q.push(t->right);
            }
        }

        while (q.size()) {
            auto t = q.front();
            q.pop();
            auto left = new TreeNode(v), right = new TreeNode(v);
            left->left = t->left, right->right = t->right;
            t->left = left, t->right = right;
        }
        return root;
    }
};
/*
(递归,模拟) O(n)
若 d 为 1,则直接按照题目描述做即可。
否则,从根结点开始遍历,到 d - 1 层时,新创建两个结点,并初始化结点值,左右儿子为当前遍历结点的左右儿子。
时间复杂度
每个结点最多遍历一次,故时间复杂度为 O(n)。
 */
class Solution {
public:
    void dfs(TreeNode *r, int cur_d, int v, int d) {
        if (r == NULL)
            return;
        if (cur_d == d - 1) {
            TreeNode *new_left = new TreeNode(v);
            TreeNode *new_right = new TreeNode(v);
            new_left -> left = r -> left;
            new_right -> right = r -> right;
            r -> left = new_left;
            r -> right = new_right;
            return;
        }
        dfs(r -> left, cur_d + 1, v, d);
        dfs(r -> right, cur_d + 1, v, d);
    }
    TreeNode* addOneRow(TreeNode* root, int v, int d) {
        if (d == 1) {
            TreeNode *new_root = new TreeNode(v);
            new_root -> left = root;
            return new_root;
        }
        dfs(root, 1, v, d);
        return root;
    }
};

LeetCode 628. 三个数的最大乘积

/*
根据正负数个数分类讨论:
正正正:最大的三个正数
正正负:说明数组长度为3,否则但凡还有一个数,无法正负或为0都不可能出现这个情况
正负负:最大的正数和最小的两个负数
负负负:数组全负,否则但凡有一个正数或0都不会出现这种情况,选最大的三个数
所以三个数的最大乘积,必然是三个最大的数的乘积或者两个最小的(负)数与最大数的乘积
*/
class Solution {
public:
    int maximumProduct(vector<int>& nums) {
        int n = nums.size();
        if(n<3) return 0;
        sort(nums.begin(), nums.end());
        return max(nums[n - 1] * nums[n - 2] * nums[n - 3], nums[0] * nums[1] * nums[n - 1]);
    }
};
/*
三个数的最大乘积,必然是三个最大的数的乘积或者两个最小的(负)数与最大数的乘积。
线性扫描或者排序找出最大的三个数和最小的两个数即可。
*/
class Solution {
public:
    int maximumProduct(vector<int>& nums) {
        //假设max1 > max2 > max3
        int max1 = INT_MIN;
        int max2 = INT_MIN;
        int max3 = INT_MIN;
        //假设min1 < min2
        int min1 = INT_MAX;
        int min2 = INT_MAX;        
        for (int a : nums) {
            if (a > max1) {
                max3 = max2;
                max2 = max1;
                max1 = a;
            } else if (a > max2) {
                max3 = max2;
                max2 = a;
            } else if (a > max3) {
                max3 = a;
            }
            
            if (a < min1) {
                min2 = min1;
                min1 = a;
            } else if (a < min2) {
                min2 = a;    
            }            
        }
        return max(min1 * min2 * max1, max1 * max2 * max3);
    }
};

LeetCode 633. 平方数之和

class Solution {
public:
    bool judgeSquareSum(int c) {
        // for (long long i = 0; i * i <= c; i ++ ) {
        //     int j = c - i * i;
        //     int r = sqrt(j);
        //     if (r * r == j) return true;
        // }
        // return false;
        // 强行枚举
        // for (int a = 0; a <= sqrt(c); ++a){
        //     int b = sqrt(c - a * a);
        //     if (a * a  + b * b == c)
        //         return true;
        // }

        // return false;
        // 双平方和定理,一个自然数能被表示为两个平方数之和,数学,费马双平方和定理 O(n^0.5)
        // 当且仅当它的质因数分解中,模4余3的质数次方数均为偶数

        // for (int i = 2; i <= c / i; ++i){
        //     int cnt = 0;
        //     while (c % i == 0){
        //         c /= i;
        //         ++cnt;
        //     }

        //     if (i % 4 == 3 && cnt % 2 != 0)
        //         return false;
        // }
        // if (c % 4 == 3)
        //     return false;

        // return true;
        //双指针
        long i = 0, j = sqrt(c);
        while (i <= j) 
            if (i * i + j * j == c) return true;
            else if (i * i + j * j > c) j --;
            else i ++;
        return false;
    }
};

LeetCode 636. 函数的独占时间

class Solution {
public:
/*
用栈来维护当前函数调研递归序列
*/
    vector<int> exclusiveTime(int n, vector<string>& logs) {
        vector<int> res(n);
        stack<int> stk;//维护当前执行了哪个函数
        int last;// 维护上一个事件的开始时间
        for (auto& log: logs) {
            int x = log.find(':'), y = log.substr(x + 1).find(':') + x + 1;
            int id = stoi(log.substr(0, x)), ts = stoi(log.substr(y + 1));
            if (log.substr(x + 1, y - x - 1) == "start") {
                if (stk.size()) res[stk.top()] += ts - last;
                //比如时刻1到3在执行函数A,时刻3新开了一个函数B,则3-1这段时间是函数A的独占时间
                stk.push(id);//更新栈顶元素
                last = ts;
            } else {
/*
注意对于开始和结束两个事件要特判一下,比如题中所给例子,函数1在时刻 2 开始,结束于时刻 5,函数 0 再次在时刻 6 开始执行,那么从时刻5到6这一段时间仍然在执行函数1,所以在这一段要加的独占时间是5-2+1==4
*/
                res[stk.top()] += ts - last + 1;
                stk.pop();
                last = ts + 1;
            }
        }
        return res;
    }
};

LeetCode 637. 二叉树的层平均值

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        vector<double> res;
        if (!root) return res;
        queue<TreeNode*> q;
        q.push(root);
        while (q.size()) {
            int sz = q.size();
            double sum = 0;
            for (int i = 0; i < sz; i ++ ) {
                auto t = q.front();
                q.pop();
                sum += t->val;
                if (t->left) q.push(t->left);
                if (t->right) q.push(t->right);
            }
            res.push_back(sum / sz);
        }
        return res;
    }
};

LeetCode 640. 求解方程

class Solution {
public:
    pair<int, int> work(string str) {//返回两个系数
        int a = 0, b = 0;//a表示x的系数,b表示常数项
        if (str[0] != '+' && str[0] != '-') str = '+' + str;//补上+,方便处理
        for (int i = 0; i < str.size(); i ++ ) {//从前往后枚举
            int j = i + 1;//此时i表示正负号,从下一位开始
            while (j < str.size() && isdigit(str[j])) j ++ ;
            //跳出循环时,一般来说从i+1到j-1这一段是数字
            int c = 1;//如果数字不存在的话就是1
            if (i + 1 <= j - 1) c = stoi(str.substr(i + 1, j - 1 - i));//如果存在,把这一小段数字字符变为数字
            if (str[i] == '-') c = -c;//判断正负,是否需要取反
            if (j < str.size() && str[j] == 'x') {//判断当前是常数项还是一次项
            //由于要保证每次进入循环时,i要指向正负号,所以i要更新为当前项的最后一位,这样循环内+1之后才满足
                a += c;
                i = j;//指向一次项的x
            } else {
                b += c;
                i = j - 1;//指向常数项的最后一位数字
            }
        }
        return {a, b};
    }
/*
先将左边整理成ax+b的形式,右边整理成cx+d的形式 得到(a-c)x=d-b->Ax=B
如果A==0
    B==0:无穷解
    B不等于0:无解
否则 x=B/A
*/
    string solveEquation(string equation) {
        int k = equation.find('=');//先找到"="的位置
        //把等号两边合并同类项
        auto left = work(equation.substr(0, k)), right = work(equation.substr(k + 1));
        int a = left.first - right.first, b = right.second - left.second;
        if (!a) {
            if (!b) return "Infinite solutions";
            else return "No solution";
        }
        return "x=" + to_string(b / a);
    }
};

LeetCode 643. 子数组最大平均数 I

/*
对每个长度为k的子区间求平均数,滑动窗口扫描长度为k的区间即可
*/
class Solution {
public:
    double findMaxAverage(vector<int>& nums, int k) {
        double res = -1e5;
        for (int i = 0, j = 0, s = 0; i < nums.size(); i ++ ) {
            s += nums[i];
            if (i - j + 1 > k) s -= nums[j ++ ];
            if (i >= k - 1) res = max(res, s / (double)k);
        }
        return res;
    }
};

LeetCode 645. 错误的集合

class Solution {
public:
/*
数组中1个数出现了0次,1个数出现了两次,其他均只出现1次,且范围在1到n之间
注意要先返回重复的,再返回缺失的
(线性扫描) O(n)
利用原数组进行线性扫描。扫描过程中,将 nums[abs(nums[i]) - 1] 取相反数。若扫描过程中发现 nums[abs(nums[i]) - 1] 已经是负数了,就不再将其置负数,同时说明 abs(nums[i]) - 1 是重复的。
最后再扫描数组中,若发现 nums[i] 是正数,则说明 i + 1 是丢失的数。
注意数组中第i个数的下标是i-1
*/
    vector<int> findErrorNums(vector<int>& nums) {
        vector<int> res(2);
        for (auto x: nums) {
            int k = abs(x);//取绝对值是因为当前遍历到的这个数可能已经被取反了
            if (nums[k - 1] < 0)//如果第k个数以及取反,说明第数字k是重复的那个数
                res[0] = k;
            nums[k - 1] *= -1;
        }
//循环结束后,数组中只有两个数为正数,一个是重复数k对于的第k个数,它被取反两次,一个数缺失的数,被取反0次
        for (int i = 0; i < nums.size(); i ++ ) {
            if (nums[i] > 0 && i + 1 != res[0]) {//下标i对应的数是i+1
                res[1] = i + 1;
            }
        }

        return res;
    }
};
/*
异或位运算 timeO(n)
1.
将原数组和辅助数组[1, 2, ...n]所有元素进行异或,可知其中等于dup的有3个,mis有1个(在辅助数组中),其余元素各有2个。在异或运算过程中,成对出现的都会抵消为0(异或运算的先后顺序不影响,故可当做相等元素各自运算)。最后的结果就是 dup ^ mis
2.
根据dup和mis最后不同的那一位去把两个数组分类,其中一类中3个dup,其余元素成对出现;另一部分有1个mis,其余元素成对出现。对于这两部分,各自将自身所有元素按位异或^,发现结果正好一个是dup,一个是mis。
3.
区分谁重复,谁缺失,先返回重复的
*/
class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        int xorr = 0, n = nums.size();//0与任何数异或都为数自身,所以以0为基元素
        for (int num: nums)
            xorr ^= num;

        for (int i = 1; i <= n; ++i)
            xorr ^= i;
        //循环结束后,xorr为dup^mis

        //lowbit操作得到最后xorr最后一位1,遍历所有元素,将各自类中的数全部异或
        int lowbit = xorr & -xorr, a = 0, b = 0;
        for (int num: nums)
            if (num & lowbit)
                a ^= num;
            else 
                b ^= num;

        for (int i = 1; i <= n; ++i)
            if (i & lowbit)
                a ^= i;
            else 
                b ^= i;
        //上面两个循环结束后,a和b中一个数dup,一个是mis

        //然后再区别谁是dup,谁是mis,先返回dup
        for (int num: nums)
            if (num == a)
                return {a, b};
            else if (num == b)
                return {b, a};
        return {-1, -1};
    }
};

LeetCode 646. 最长数对链

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        sort(pairs.begin(), pairs.end(), [](vector<int>& a, vector<int>& b){
            return a[1] < b[1];//按照第二个元素从小到大排序,或者说按照区间右端点从小到大排序
        });

        int res = 1, ed = pairs[0][1];
        for (auto& p: pairs) {
            if (p[0] > ed) {
                res ++ ;
                ed = p[1];
            }
        }

        return res;
    }
};

LeetCode 647. 回文子串

/*
统计所给字符串所有的子串中,回文子串的数量,这道题直接枚举即可,因为输入的字符串长度不会超过1000
将所有回文串根据中心点分为若干类,以及要分奇数偶数情况
时间复杂度O(n^2)
*/
class Solution {
public:
    int countSubstrings(string s) {
        int res = 0;
        for (int i = 0; i < s.size(); i ++ ) {
            // 枚举长度为奇数的情况
            for (int j = i, k = i; j >= 0 && k < s.size(); j --, k ++ ) {//枚举回文串一半长度
                if (s[j] != s[k]) break;
                res ++ ;
            }

            // 偶数情况
            for (int j = i, k = i + 1; j >= 0 && k < s.size(); j --, k ++ ) {
                if (s[j] != s[k]) break;
                res ++ ;
            }
        }
        return res;
    }
};

LeetCode 649. Dota2 参议院

/*
贪心做法: O(n)
这题贪心的思路很简单,就是轮到每个还有用权利的人时,那么他肯定是尽量去禁止最近的当前轮还没有表决的对方选手的权利,如果没有这样的话,那么就从已经行使过权利的对方选手选择一个人禁止他在剩下轮的权力,如果还没有的话,那么说明已经没有敌方选手了,直接获胜。直接模拟的话代码就会比较丑陋,但是很容易理解:这里我们使用字符O代表当前选手被out禁止权利。
*/
class Solution {
public:
    string predictPartyVictory(string senate) {
        int n = senate.size();
        while(true)
        {
            for(int i = 0 ; i < n ; i ++)
            {
                int k = i + 1;
                if(senate[i] == 'R')
                {
                    while(k < n && (senate[k] == 'R' || senate[k] == 'O'))
                        k ++;
                    if(k < n) senate[k] = 'O';
                    else 
                    {
                        k = 0;
                        while(k < i && (senate[k] == 'R' || senate[k] == 'O'))
                            k ++;
                        if(k < i) senate[k] = 'O';
                        else return "Radiant";
                    }
                }else if(senate[i] == 'D')
                {
                    while(k < n && (senate[k] == 'D' || senate[k] == 'O'))
                        k ++;
                    if(k < n) senate[k] = 'O';
                    else 
                    {
                        k = 0;
                        while(k < i && (senate[k] == 'D' || senate[k] == 'O'))
                            k ++;
                        if(k < i) senate[k] = 'O';
                        else return "Dire";
                    }
                }
            }
        }
        return "";
    }
};
/*
贪心优化: O(n)
使用两个布尔变量,标记当前轮是否遇到过R或者D,cnt代表当前轮当目前为止有投票权利的人中R比D多的人数。
每一轮遍历每个人,这个人可能为r,也可能为d,他可能有权利,也可能权利被之前的对手禁止
如果当时当前人为R,如果cnt < 0,说明前面D的人更多,说明当前这个人肯定在之前的投票中被禁止权利了,那么我么把这个标记为O。
如果当时当前人为D,如果cnt > 0,说明前面R的人更多,说明当前这个人肯定在之前的投票中被禁止权利了,那么我么把这个标记为O。
*/
class Solution {
public:
    string predictPartyVictory(string senate) {
        int n = senate.length(),cnt = 0;
        bool flag_r = true,flag_d = true;
        while(flag_r && flag_d)
        {
            flag_r =false,flag_d = false;
            for(int i = 0 ; i < n ; i ++)
            {
                if(senate[i] == 'R')
                {
                    if(cnt < 0) senate[i] = 'O';
                    cnt ++;
                    flag_r = true;
                }else if(senate[i] == 'D')
                {
                    if(cnt > 0) senate[i] = 'O';
                    cnt --;
                    flag_d = true;
                }
            }
        }
        return flag_r ? "Radiant" : "Dire";
    }
};
class Solution {
public:
    string predictPartyVictory(string senate) {
        queue<int> r, d;
        for (int i = 0; i < senate.size(); i ++ ) {
            if (senate[i] == 'R') r.push(i);
            else d.push(i);
        }

        int n = senate.size();
        while (r.size() && d.size()) {
            if (r.front() < d.front()) r.push(r.front() + n);
            else d.push(d.front() + n);
            r.pop(), d.pop();
        }

        if (r.size()) return "Radiant";
        return "Dire";
    }
};

LeetCode 652. 寻找重复的子树

/*
(哈希表) O(n^2)
首先子树的定义为以树中某个节点为根并且包含该节点所有子节点的树,所以如果一个树有n个节点则它有n棵子树。

那么我们就可以序列化这n棵子树并且用哈希表来存储,当我们第一次遇见重复的子树时将当前根节点加入答案。

对于序列化我们可以将树的前序遍历变成一个字符串,注意这里要包含空节点不然不同的树可能序列化出相同的字符串,而且我们不能用树的中序遍历来序列化,原因是即使包含空节点不同的树依然有可能序列化出相同的字符串。
 */
class Solution {
public:
    vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
        vector<TreeNode*> res;
        unordered_map<string, int> hash;
        dfs(root, res, hash);
        return res;
    }

    string dfs(TreeNode* root, vector<TreeNode*>& res, unordered_map<string, int>& hash)
    {
        if (!root) return "#";
        string s = to_string(root->val) + "#";
        s += dfs(root->left, res, hash);
        s += dfs(root->right, res, hash);
        if (hash[s] == 1) res.push_back(root);
        hash[s] ++ ;
        return s;
    }
};
 /*
(哈希表) O(n)
本题还可以继续优化,可以发现时间复杂度主要取决于所有子树序列化后的字符串的长度,所以我们可以再做一层哈希,将每棵不同的子树(字符串)映射为一个数(即Id),这样对于每棵子树我们可以表示成(root->val, id of left subtree, id of right subtree)的三元组的形式,这个三元组每个元素都是一个数,是 log10nlog10⁡n 级别的,比如我们可以用 66 位数表示 106106 个不同的子树,这样总长度就是 O(n)O(n) 级别的了(近似)。另外由于我们需要记录重复的子树,所以我们需要另外一个哈希表来存储每棵子树出现的次数(也可以只用一个哈希表,用一个pair来分别存储id和次数)。
 */
class Solution {
public:
    unordered_map<string, int> ids;//存三元组与id的映射
    int cnt = 0;//当前数个数
    unordered_map<int, int> hash;//每个id出现多少次
    vector<TreeNode*> ans;

    vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
        dfs(root);//返回以root为根的子树的唯一id
        return ans;
    }

    int dfs(TreeNode* root) {
        if (!root) return 0;//0映射空树
        int left = dfs(root->left);
        int right = dfs(root->right);
        string key = to_string(root->val) + ' ' + to_string(left) + ' ' + to_string(right);
        if (ids.count(key) == 0) ids[key] = ++ cnt;//如果当前三元组未出现过,分配一个唯一的id
        int id = ids[key];
        if (++ hash[id] == 2) ans.push_back(root);
        return id;
    }
};

LeetCode 653. 两数之和 IV - 输入 BST

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
 /*
 BST有很多性质,中序遍历是有序的
这道题可以先中序遍历把整个一维有序数组求出来,然后再双指针扫描一遍
也可以开一个哈希表看每个数是否存在,当前节点值是x的话,查一下之前遍历的节点有没有k-x
 */
class Solution {
public:
    unordered_set<int> hash;//这里只需要看某个数是否出现过,所以set就行

    bool findTarget(TreeNode* root, int k) {
        return dfs(root, k);
    }

    bool dfs(TreeNode* root, int k) {
        if (!root) return false;
        if (dfs(root->left, k)) return true;
        int x = root->val;
        if (hash.count(k - x)) return true;
        hash.insert(x);
        return dfs(root->right, k);
    }
};

LeetCode 654. 最大二叉树

/*
(递归) O(n^2)
采用递归算法,假设 build(l, r) 表示对数组中 [l, r] 闭区间的部分构造二叉树;
首先找出最大值及其所在位置 max_i,然后构造一个新的结点 rt,递归 build(l, max_i - 1) 和 build(max_i + 1, r) 分别作为 rt 的左右儿子,最后返回该结点 rt。
时间复杂度
最坏情况下,每次寻找的最大值都在当前区间的最左边,即数组是有序数组,这样就会有 n 层递归,总共需要 n + (n - 1) + (n - 2) + … + 1 次计算,所以时间复杂度是 O(n^2)。
 */
class Solution {
public:
    TreeNode* build(const vector<int>& nums, int l, int r) {
        if (l > r)
            return NULL;
        int max_num = nums[l], max_i = l;
        for (int i = l + 1; i <= r; i++)
            if (max_num < nums[i]) {
                max_num = nums[i];
                max_i = i;
            }
        TreeNode *rt = new TreeNode(max_num);
        rt -> left = build(nums, l, max_i - 1);
        rt -> right = build(nums, max_i + 1, r);
        return rt;
    }

    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return build(nums, 0, nums.size() - 1);
    }
};
class Solution {
public:
/*
暴力算法的瓶颈在找最大值上面,如何快速求区间最值
1.线段树 O(lohn)
2.st RMQ 倍增 DP O(nlogn)初始化 O(1)查找
    f[i,j]表示从i开始,长度为2^j的最大值 [i,i+2^j-1]
    f[i,j]=max( f[i,j-1], f[i+2^(j-1), j-1] ) 预处理O(nlogn)

*/
    int n, k;//前者表示数组长度,后者表示logn
    vector<vector<int>> f;
    vector<int> nums;

    TreeNode* constructMaximumBinaryTree(vector<int>& _nums) {
        nums = _nums;
        n = nums.size();
        k = log(n) / log(2);
        f = vector<vector<int>>(n, vector<int>(k + 1));
        for (int j = 0; j <= k; j ++ )
            for (int i = 0; i + (1 << j) - 1 < n; i ++ ) {
                if (!j) f[i][j] = i;
                else {
                    int l = f[i][j - 1], r = f[i + (1 << j - 1)][j - 1];
                    if (nums[l] > nums[r]) f[i][j] = l;
                    else f[i][j] = r;
                }
            }

        return build(0, n - 1);
    }

    int query(int l, int r) {
        int len = r - l + 1;
        int k = log(len) / log(2);
        int a = f[l][k], b = f[r - (1 << k) + 1][k];
        if (nums[a] > nums[b]) return a;
        return b;
    }

    TreeNode* build(int l, int r) {
        if (l > r) return NULL;
        int k = query(l, r);
        auto root = new TreeNode(nums[k]);
        root->left = build(l, k - 1);
        root->right = build(k + 1, r);
        return root;
    }
};

LeetCode 655. 输出二叉树

/*
BFS + 模拟 timeO(h∗2^h)
先求高度,
然后再找规律填表。
 */
class Solution {
public:
    vector<vector<string>> printTree(TreeNode* root) {
        if (!root)
            return {};

        int h = 0;
        queue<TreeNode *> Q; Q.push(root);

        while (Q.size()){
            int size = Q.size();
            for (int i = 0; i < size; ++i){
                TreeNode *node = Q.front(); Q.pop();
                if (node){//没有判断左右子节点是否存在就插入
                    Q.push(node->left);
                    Q.push(node->right);
                } 
            }
            ++h;
        }

        --h;//减去最后一层全为空的
        vector<vector<string>> ans(h, vector<string>((1 << h) - 1, ""));
        int w = 1 << h;

        Q.push(root);
        for (int i = 0; i < h; ++i){
            int size = Q.size();
            for (int j = 0; j < size; ++j){
                TreeNode *node = Q.front(); Q.pop();
                if (node){
                    Q.push(node->left);
                    Q.push(node->right);
                    ans[i][(1 << h - i - 1) - 1 + j * (1 << h - i)] = to_string(node->val);
                } else {
                    Q.push(nullptr);
                    Q.push(nullptr);
                }
            }
        }

        return ans;
    }
};
 /*
题解:题目已经很清楚需要使用递归,首先我们需要知道整个打印空间的高度和宽度。因为根节点需要打印在中间。所以我们能够得到:
H=max(Hleft,Hright)+1
W=2∗max(Wleft,Wright)+1
打印的时候,先打印根节点,然后递归打印左右子树。
 */
class Solution {
public:
    vector<vector<string>> ans;

    vector<int> dfs(TreeNode* root) {
        if (!root) return {0, 0};
        auto l = dfs(root->left), r = dfs(root->right);
        return {max(l[0], r[0]) + 1, max(l[1], r[1]) * 2 + 1};;
    }

    void print(TreeNode* root, int h, int l, int r) {
        if (!root) return;
        int mid = (l + r) / 2;
        ans[h][mid] = to_string(root->val);
        print(root->left, h + 1, l, mid - 1);
        print(root->right, h + 1, mid + 1, r);
    }

    vector<vector<string>> printTree(TreeNode* root) {
        auto t = dfs(root);//求高宽
        int h = t[0], w = t[1];
        ans = vector<vector<string>>(h, vector<string>(w));//对已定义的答案数组重置宽高
        print(root, 0, 0, w - 1);//从第0行开始,从第0列到第w-1列
        return ans;
    }
};

LeetCode 657. 机器人能否返回原点

class Solution {
public:
    bool judgeCircle(string moves) {
        int x = 0, y = 0;//坐标模拟
        for (auto c: moves) {
            if (c == 'U') x -- ;
            else if (c == 'R') y ++ ;
            else if (c == 'D') x ++ ;
            else y -- ;
        }
        return !x && !y;
    }
};

LeetCode 658. 找到 K 个最接近的元素


LeetCode 659. 分割数组为连续子序列

class Solution {
public:
    bool isPossible(vector<int>& nums) {
        unordered_map<int, int> cnt1, cnt2;
        //前者存每个字符出现的次数(未被使用),后者存是否存在以这个数结尾的合法子序列
        for (auto x: nums) cnt1[x] ++ ;
        for (auto x: nums) {
            if (!cnt1[x]) continue;
            if (cnt2[x - 1]) {//x放在某个合法子序列后面
                cnt2[x - 1] -- ;
                cnt2[x] ++ ;
                cnt1[x] -- ;//当前的x已经被使用
            } else if (cnt1[x + 1] && cnt1[x + 2]) {//另立门户
                cnt2[x + 2] ++ ;
                cnt1[x] --, cnt1[x + 1] --, cnt1[x + 2] -- ;
            } else return false;//当前这个数无解
        }
        return true;
    }
};

LeetCode 661. 图片平滑器

class Solution {
public:
//(模拟) O(nm)
    vector<vector<int>> imageSmoother(vector<vector<int>>& M) {
        int n = M.size(), m = M[0].size();
        vector<vector<int>> res = M;
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ ) {
                int s = 0, c = 0;//s表示周围格子的总和,c表示周围格子的个数
                for (int x = i - 1; x <= i + 1; x ++ )//枚举当前遍历元素的周围八个格子
                    for (int y = j - 1; y <= j + 1; y ++ )
                        if (x >= 0 && x < n && y >= 0 && y < m)
                            s += M[x][y], c ++ ;
                res[i][j] = s / c;
            }
        return res;
    }
};
class Solution {
public:
/*
原地修改,位运算
1,由于给定矩阵中的整数范围为 [0, 255]。因此可以用最后的8个bit位来存储像素原始值
2,由于周围的矩阵个数最多为8,因此可以用用倒数第8位到倒数第12位中间的4个bit位来存储周围点个数
3,由于255 * 8 < 2^12,因此可以用倒数第12位之后的12个bit来存储周围点的加和
以上一共可以用到24个bit,int类型完全满足需求。
*/
    vector<vector<int>> imageSmoother(vector<vector<int>>& M) {
        int n = M.size(), m = M[0].size();
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ ) {
                for (int x = i - 1; x <= i + 1; x ++ )//枚举当前遍历元素的周围八个格子
                    for (int y = j - 1; y <= j + 1; y ++ )
                        if (x >= 0 && x < n && y >= 0 && y < m){
                            // 用12位以上bit位记录周围像素数的和,&255是因为只关心最后表示原数值的八位
                            M[i][j] += (M[x][y] & 255) << 12;
                            M[i][j] += 1 << 8; // 用倒数第8位到倒数第12位中间的四位记录周围点数
                            //最后的8位表示原数值
                        }
            }

        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {//>>8位之后,最后的四位表示周围点数,&15是只关心最后的四位
                int count = (M[i][j] >> 8) & 15;
                M[i][j] = (M[i][j] >> 12)/ count;
            }
        }    
        return M;
    }
};

LeetCode 662. 二叉树最大宽度

 /*
(层级遍历) O(n)
对整个树进行层级遍历。遍历时,记录每一层所有结点的编号。
编号规则如下,根结点编号为 0。某个结点的左儿子结点编号为当前结点的编号乘 2;右儿子结点的编号为当前结点的编号乘 2 再加 1。
遍历完一层后,我们进行编号收缩,以最小编号作为偏移量,让当前层所有编号都减去这个偏移量,然后再进行遍历编号。
通过编号收缩,保证编号的范围不会超过答案
 */
class Solution {
public:
    int widthOfBinaryTree(TreeNode* root) {
        if (!root) return 0;
        queue<pair<TreeNode*, int>> q;
        q.push({root, 1});
        int res = 1;
        while (q.size()) {
            int sz = q.size();
            int l = q.front().second, r;

            for (int i = 0; i < sz; i ++ ) {
                auto t = q.front();
                q.pop();
                auto v = t.first;
                auto p = t.second - l + 1;
                r = t.second;
                if (v->left) q.push({v->left, p * 2});
                if (v->right) q.push({v->right, p * 2 + 1});
            }
            res = max(res, r - l + 1);
        }
        return res;
    }
};

LeetCode 665. 非递减数列

class Solution {
public:
    bool check(vector<int>& nums) {
        for (int i = 1; i < nums.size(); i ++ )
            if (nums[i] < nums[i - 1])
                return false;
        return true;
    }

    bool checkPossibility(vector<int>& nums) {
        for (int i = 1; i < nums.size(); i ++ )
            if (nums[i] < nums[i - 1]) {//出现逆序对
                int a = nums[i - 1], b = nums[i];//系修改,查看改了之后是否还有逆序对
                nums[i - 1] = nums[i] = a;
                if (check(nums)) return true;
                nums[i - 1] = nums[i] = b;
                if (check(nums)) return true;
                return false;
            }
        return true;
    }
};
class Solution {
public:
/*
出现 nums[i -1] > nums[i] 只要两种解决办法,一种是降低 nums[i - 1],一种是增大 nums[i],所以只需要通过构造出反例使得这两种都不可行就可以了。考虑 3412 这四个数字,当前 nums[i] 是 1,nums[i - 1] 是 4,此时没有办法只改变 1 或只改变 4 来做到整体递增。
*/
    bool checkPossibility(vector<int>& nums) {
        int n = nums.size();
        int chance = 0;//记录出现递减的次数
        for (int i = 1; i < n; i++)
            if (nums[i] < nums[i - 1]) {
                if (chance==1)//当前出现逆序对,且前面已经出现过了一次
                    return false;
                if (i > 1 && i < n - 1 && nums[i - 2] > nums[i] && nums[i - 1] > nums[i + 1])
                    return false;//此时无法通过只改变 nums[i] 或者 nums[i - 1] 来保证递增关系
                chance = 1;//到达这一步说明当前遍历到的第一个逆序对还找不到反例,继续往后判断,但注意次数加一
            }
        return true;
    }
};

LeetCode 669. 修剪二叉搜索树

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if (!root) return nullptr;
        if (root->val < low) return trimBST(root->right, low, high);
        if (root->val > high) return trimBST(root->left, low, high);
        root->left = trimBST(root->left, low, high);
        root->right = trimBST(root->right, low, high);
        return root;
    }
};

LeetCode 670. 最大交换

class Solution {
public:
/*
(贪心) O(logn)
尽可能让更高位变大,从最高位开始枚举
首先找到整个数字中,从最高位到最低位出现严格递增的第一个位置 t,即 s[t - 1] < s[t]。若不存在这样的 t,则不需要交换。
在 t 到最低位中找到最大的数字所在的位置 maxi,如果有多个,取最小的位。我们取出这个的这个 maxi是希望能与 t 前边的一位数位进行交换,期望获得更大的数字;而出现多个最大数字的情况下,需要保证交换后的数字尽可能大,所以取低位上的大数字会更优。
从高位开始,如果出现 s[i] < s[maxi],则交换位置即可。
*/
    int maximumSwap(int num) {
        string str = to_string(num);
        for (int i = 0; i + 1 < str.size(); i ++ ) {
            if (str[i] < str[i + 1]) {//出现第一个严格递增
                int k = i + 1;
                for (int j = k; j < str.size(); j ++ )
                    if (str[j] >= str[k])
                        k = j;//大于时更新,等于时也更新,尽可能取最小的位
                for (int j = 0; ; j ++ )//找到第一个更小的数
                    if (str[j] < str[k]) {
                        swap(str[j], str[k]);
                        return stoi(str);
                    }
            }
        }
        return num;
    }
};
class Solution {
public:
    int maximumSwap(int num) {
        string s = to_string(num);
        int l = s.length(), t = -1;
        for (int i = 0; i < l - 1; i++)
            if (s[i] < s[i + 1]) {
                t = i + 1;
                break;
            }
        if (t == -1)
            return num;
        int maxi = t;
        for (int i = t + 1; i < l; i++)
            if (s[maxi] <= s[i])
                maxi = i;
        for (int i = 0; i < l; i++)
            if (s[i] < s[maxi]) {
                swap(s[i], s[maxi]);
                break;
            }
        int ans = 0;
        for (int i = 0; i < l; i++)
            ans = ans * 10 + s[i] - '0';
        return ans;
    }
};

LeetCode 671. 二叉树中第二小的节点

class Solution {
public:
    long long d1, d2;

    void dfs(TreeNode* root) {
        if (!root) return;
        int x = root->val;
        if (x < d1) d2 = d1, d1 = x;
        else if (x > d1 && x < d2) d2 = x;
        dfs(root->left), dfs(root->right);
    }

    int findSecondMinimumValue(TreeNode* root) {
        d1 = d2 = 1e18;
        dfs(root);
        if (d2 == 1e18) d2 = -1;
        return d2;
    }
};

LeetCode 674. 最长连续递增序列

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int res = 0;
        for (int i = 0; i < nums.size(); i ++ ) {
            int j = i + 1;
            while (j < nums.size() && nums[j] > nums[j - 1]) j ++ ;
            res = max(res, j - i);
            i = j - 1;
        }
        return res;
    }
};

LeetCode 678. 有效的括号字符串

class Solution {
public:
/*
(贪心) O(n)
单一种类的括号,通常不需要真的用到栈,而是用一个变量进行记录,节省空间。

我们使用两个标记low,high分别当前已经读入的字符中所有可能的情况中最少有多少个左括号,最多有多少个左括号。对于读入的字符c

c = '('说明low = low + 1,high = high + 1
c = ')'说明low = low - 1,high = high - 1
c = '*'说明low = low - 1,high = high + 1这是因为,当前这个字符既可以看作是左括号也可以看作是右括号。
在匹配过程中,如果当前high < 0需要返回false。如果当前low < 0需要把low重置为0,这是因为不可以在已经不合法的序列中继续添加左括号。

最后只需要判断一下,low= 0说明完全匹配。时间复杂度O(N)
*/
    bool checkValidString(string s) {
        int low = 0, high = 0;
        for (auto c: s) {
            if (c == '(') low ++, high ++ ;
            else if (c == ')') low -- , high -- ;
            else low --, high ++ ;
            low = max(low, 0);
            if (low > high) return false;
        }
        return low==0;
    }
};

LeetCode 680. 验证回文字符串 Ⅱ

class Solution {
public:
    bool check(string &s, int l, int r){
        for (int i = l, j = r; i < j; ++i, --j)
            if (s[i] != s[j])
                return false;

        return true;
    }


    bool validPalindrome(string s) {
        if (s.empty())
            return true;

        for (int i = 0, j = s.size() - 1; i < j; ++i, --j)
            if (s[i] != s[j])
                return check(s, i + 1, j) || check(s, i, j - 1);

        return true;
    }

};

LeetCode 682. 棒球比赛

class Solution {
public:
    int calPoints(vector<string>& ops) {
        vector<int> stk;
        for (auto& s: ops) {
            int p = stk.size() - 1;
            if (s == "+") stk.push_back(stk[p - 1] + stk[p]);
            else if (s == "D") stk.push_back(stk[p] * 2);
            else if (s == "C") stk.pop_back();
            else stk.push_back(stoi(s));
        }
        return accumulate(stk.begin(), stk.end(), 0);
    }
};
class Solution {
public:
    int calPoints(vector<string>& ops) {
        vector<int> nums;

        for (string op: ops){
            if (op == "+"){
                int n = nums.size();
                nums.push_back(nums[n - 1] + nums[n - 2]);
            } else if (op == "D"){
                nums.push_back(nums.back() * 2);
            } else if (op == "C"){
                nums.pop_back();
            } else {
                nums.push_back(stoi(op));
            }
        }

        int sum = 0;
        for (int num: nums)
            sum += num;
        return sum;
    }
};

LeetCode 686. 重复叠加字符串匹配

class Solution {
public:
    int repeatedStringMatch(string a, string p) {
        string s;
        while (s.size() < p.size()) s += a;//一直复制直到长度大于p
        s += a;//跳出循环时s刚好大于等于b,需要枚举的最终起点只会在第一个a中的任意位置,所以再加上一个a即可
        //判断子序列有一个模板,判断子串也有模板kmp
        int n = s.size(), m = p.size();
        s = ' ' + s, p = ' ' + p;//下标从1开始

        vector<int> next(m + 1);
        for (int i = 2, j = 0; i <= m; i ++ ) {
            while (j && p[i] != p[j + 1]) j = next[j];
            if (p[i] == p[j + 1]) j ++ ;
            next[i] = j;
        }
        for (int i = 1, j = 0; i <= n; i ++ ) {
            while (j && s[i] != p[j + 1]) j = next[j];
            if (s[i] == p[j + 1]) j ++ ;
            //找到第一个匹配的位置,要找到1到i包含了多少个a,也就是i/a.size()上取整
            //a/b的上取整==(a+b-1)/b的下取整
            if (j == m) return (i + (int)a.size() - 1) / a.size();
        }
        return -1;
    }
};

LeetCode 687. 最长同值路径

 /*
枚举最高点
 */
class Solution {
public:
    int ans = 0;
    int longestUnivaluePath(TreeNode* root) {
        arrowLength(root);
        return ans;
    }
    int arrowLength(TreeNode* root){//arrowLength返回以root为根节点的单侧最大同值路径的长度
        //官方答案的思路非常巧妙 可以学习一下
        if(!root) return 0;//
        int left = arrowLength(root->left);//left存储以root的左孩子为根节点的最大同值路径的长度
        int right = arrowLength(root->right);//存储以root的右孩子为根节点的最大同值路径的长度
        int arrowLeft = 0, arrowRight = 0;
        if(root->left && root->left->val == root->val){//若root的左孩子不为空且左孩子值与root值相等
            arrowLeft += left + 1;
        }
        if(root->right && root->right->val == root->val){//若root的右孩子不为空且右孩子值与root值相等
            arrowRight += right + 1;
        }
        ans = max(ans, arrowLeft + arrowRight);
        return max(arrowLeft, arrowRight);//保证路径不分叉且取到最大
    }
};

LeetCode 690. 员工的重要性

class Solution {
public:
    unordered_map<int, Employee*> hash;

    int getImportance(vector<Employee*> employees, int id) {
        for (auto e: employees) hash[e->id] = e;
        return dfs(id);
    }
    int dfs(int id) {
        auto p = hash[id];
        int res = p->importance;
        for (auto x: p->subordinates)
            res += dfs(x);
        return res;
    }
};

LeetCode 693. 交替位二进制数

class Solution {
public:
    bool hasAlternatingBits(int n) {
        int cur = n & 1;
        int pre = 1 - cur;

        while (n){
            if (cur + pre != 1)
                return false;
            n >>= 1;
            pre = cur;
            cur = n & 1;
        }

        return true;
    }
};

LeetCode 696. 计数二进制子串

class Solution {
public:
/*
注意看清楚题目,给定一个01字符串,计算具有相同数量连续的0和连续的1的非空子字符串的数量
先将整个字符串分成若干段,每一段要么全0,要么全1
*/
    int countBinarySubstrings(string s) {
        int res = 0, last = 0;//记录上一段连续相同字符的个数
        for (int i = 0; i < s.size(); i ++ ) {
            int j = i + 1;
            //当前的i要么是第一个0,要么是第一个1,判断从i开始的连续相同字符有多少个
            while (j < s.size() && s[j] == s[i]) j ++ ;
            int cur = j - i;//从i开始的连续相同字符有cur个
            i = j - 1;
            res += min(last, cur);
            //比如1111000,last为4,cur为3,以两者分界线向左右两边扩展,满足条件的子串有3个
            last = cur;
        }
        return res;
    }
};

LeetCode 700. 二叉搜索树中的搜索

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if (!root) return NULL;
        if (root->val == val) return root;
        if (root->val > val) return searchBST(root->left, val);
        else return searchBST(root->right, val);
    }
};
class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        while (root){
            if (root->val == val)
                return root;
            else if (root->val > val)
                root = root->left;
            else 
                root = root->right;
        }

        return root;
    }
};

LeetCode 703. 数据流中的第 K 大元素

class KthLargest {
public:
/*
用堆动态维护第k大的数
*/
    priority_queue<int, vector<int>, greater<int>> heap;//小根堆加额外参数,默认大根堆
    int k;

    KthLargest(int _k, vector<int>& nums) {
        k = _k;
        for (auto x: nums) {
            heap.push(x);
            if (heap.size() > k) heap.pop();
        }
    }

    int add(int val) {
        heap.push(val);
        if (heap.size() > k) heap.pop();
        return heap.top();
    }
};

LeetCode 704. 二分查找

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        if (nums[r] != target) return -1;//注意二分一定能得到结果,但不一定是你要的
        return r;
    }
};

LeetCode 705. 设计哈希集合

const int N = 19997;
/*
基础课中讲过,设计哈希表两个方式:开放寻址法和拉链法(后者很像邻接表)
有删除操作的话,拉链法更容易写一点
*/
class MyHashSet {
public:
    vector<int> h[N];//定义邻接表表头

    /** Initialize your data structure here. */
    MyHashSet() {

    }

    int find(vector<int>& h, int key) {
        for (int i = 0; i < h.size(); i ++ )
            if (h[i] == key)
                return i;
        return -1;
    }

    void add(int key) {
        int t = key % N;
        int k = find(h[t], key);
        if (k == -1) h[t].push_back(key);
    }

    void remove(int key) {
        int t = key % N;
        int k = find(h[t], key);
        if (k != -1) h[t].erase(h[t].begin() + k);
    }

    /** Returns true if this set contains the specified element */
    bool contains(int key) {
        int t = key % N;
        int k = find(h[t], key);
        return k != -1;
    }
};

LeetCode 706. 设计哈希映射

const int N = 19997;
typedef pair<int, int> PII;
/*
上道题实现unordered_set 这道题实现unordered_map
*/
class MyHashMap {
public:
    vector<PII> h[N];

    /** Initialize your data structure here. */
    MyHashMap() {

    }

    int find(vector<PII>& h, int key) {
        for (int i = 0; i < h.size(); i ++ )
            if (h[i].first == key)
                return i;
        return -1;
    }

    /** value will always be non-negative. */
    void put(int key, int value) {
        int t = key % N;
        int k = find(h[t], key);
        if (k == -1) h[t].push_back({key, value});
        else h[t][k].second = value;
    }

    /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
    int get(int key) {
        int t = key % N;
        int k = find(h[t], key);
        if (k == -1) return -1;
        return h[t][k].second;
    }

    /** Removes the mapping of the specified value key if this map contains a mapping for the key */
    void remove(int key) {
        int t = key % N;
        int k = find(h[t], key);
        if (k != -1) h[t].erase(h[t].begin() + k);
    }
};

LeetCode 707. 设计链表

class MyLinkedList {
public:
    struct Node {
        int val;
        Node* next;
        Node(int _val): val(_val), next(NULL) {}
    }*head;

    /** Initialize your data structure here. */
    MyLinkedList() {
        head = NULL;
    }

    /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    int get(int index) {
        if (index < 0) return -1;
        auto p = head;
        for (int i = 0; i < index && p; i ++ ) p = p->next;
        if (!p) return -1;
        return p->val;
    }

    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    void addAtHead(int val) {
        auto cur = new Node(val);
        cur->next = head;
        head = cur;
    }

    /** Append a node of value val to the last element of the linked list. */
    void addAtTail(int val) {
        if (!head) head = new Node(val);
        else {
            auto p = head;
            while (p->next) p = p->next;
            p->next = new Node(val);
        }
    }

    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    void addAtIndex(int index, int val) {
        if (index <= 0) addAtHead(val);
        else {
            int len = 0;
            for (auto p = head; p; p = p->next) len ++ ;
            if (index == len) addAtTail(val);
            else {
                auto p = head;
                for (int i = 0; i < index - 1; i ++ ) p = p->next;//找到第index-1个节点
                auto cur = new Node(val);
                cur->next = p->next;
                p->next = cur;
            }
        }
    }

    /** Delete the index-th node in the linked list, if the index is valid. */
    void deleteAtIndex(int index) {
        int len = 0;
        for (auto p = head; p; p = p->next) len ++ ;
        if (index >= 0 && index < len) {
            if (!index) head = head->next;
            else {
                auto p = head;
                for (int i = 0; i < index - 1; i ++ ) p = p->next;
                p->next = p->next->next;
            }
        }
    }
};

LeetCode 709. 转换成小写字母

class Solution {
public:
    string toLowerCase(string str) {
        for (int i = 0; i < str.size(); ++i) {
            char a = str[i];
            if (a >='A' && a <= 'Z') {
                str[i] = a - 'A' + 'a';
            }
        }
        return str;
    }
};

LeetCode 710. 黑名单中的随机数

class Solution {
public:
/*
我们考虑这样一个事实,总共有N个数字,黑名单中有n个数。那么白名单中有N-n个数字,那么我们只需要生成[0,N-n)中的一个随机数就可以了,如果这个数字在黑名单当中,我们就把它映射到一个[n,N)中一个白名单中的数字。
每次随机生成一个[0,N-n)之间的数字,如果这个数字在黑名单当中,那么返回哈希表中映射的数字,否则直接返回原来的数字就可以了。
*/
    int n, len;//总长度和黑名单的长度
    unordered_map<int, int> hash;//把黑名单中的数映射到没有在黑名单的数

    Solution(int N, vector<int>& blacklist) {
        n = N, len = blacklist.size();
        unordered_set<int> S;//存储后半部分不在黑名单中的元素
        for (int i = n - len; i < n; i ++ ) S.insert(i);
        for (auto x: blacklist) S.erase(x);
        auto it = S.begin();
        for (auto x: blacklist)
            if (x < n - len)
                hash[x] = *it ++ ;
    }

    int pick() {
        int k = rand() % (n - len);//尽量少调用,只调用一次
        if (hash.count(k)) return hash[k];
        return k;
    }
};

LeetCode 712. 两个字符串的最小ASCII删除和

class Solution {
public:
/*
和72题编辑距离很像
f[i][j]的定义为考虑要使得两个字符串的前i个和前j个字母相等要删除ASCII码之和的最小值
根据s1的第i个字母和s2的第j个字母是否删除分类
f[i - 1][j] + s1[i - 1]
f[i][j - 1] + s2[j - 1]
f[i - 1][j - 1] + s1[i - 1] + s2[j - 1]
当s1[i - 1] 和 s2[j - 1]相等时,f[i][j] 可转化为 f[i - 1][j - 1]
*/
    int minimumDeleteSum(string s1, string s2) {
        int n = s1.size(), m = s2.size(), INF = 1e8;
        s1 = ' ' + s1, s2 = ' ' + s2;//下标变为从1开始
        vector<vector<int>> f(n + 1, vector<int>(m + 1, INF));
        f[0][0] = 0;
        for (int i = 1; i <= n; i ++ ) f[i][0] = f[i - 1][0] + s1[i];
        for (int i = 1; i <= m; i ++ ) f[0][i] = f[0][i - 1] + s2[i];
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= m; j ++ ) {
                f[i][j] = min(f[i - 1][j] + s1[i], f[i][j - 1] + s2[j]);
                f[i][j] = min(f[i][j], f[i - 1][j - 1] + s1[i] + s2[j]);
                if (s1[i] == s2[j])
                    f[i][j] = min(f[i][j], f[i - 1][j - 1]);
            }
        return f[n][m];
    }
};

LeetCode 713. 乘积小于K的子数组

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        int res = 0, p = 1;
        for (int i = 0, j = 0; i < nums.size(); i ++ ) {//固定终点i,找到最前面的j使得从j一直乘到i小于等于k
            p *= nums[i];
            while (j <= i && p >= k) p /= nums[j ++ ];
            res += i - j + 1;
        }
        return res;
    }
};

LeetCode 714. 买卖股票的最佳时机含手续费

/*
f[i][0]表示第i天手里没有股票
f[i][1]表示第i天是手里有股票
状态计算:
    f[i][0] = max(f[i - 1][0], f[i - 1][1] + prices[i] - fee);
    f[i][1] = max(f[i - 1][1], f[i - 1][0] - prices[i]);
    
*/
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        if (prices.empty()) return 0;
        int n = prices.size(), INF = 1e8;
        vector<vector<int>> f(n+1, vector<int>(2, -INF));
        f[0][0] = 0;
        for (int i = 1; i <= n; i ++ )
        {
        f[i][0] = max(f[i - 1][0], f[i - 1][1] + prices[i-1] - fee);
        f[i][1] = max(f[i - 1][1], f[i - 1][0] - prices[i-1]);
        }
        return f[n][0];
    }
};
/*
滚动数组
*/
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        if (prices.empty()) return 0;
        int n = prices.size(), INF = 1e8;
        vector<vector<int>> f(2, vector<int>(2, -INF));
        f[0][0] = 0;
        for (int i = 1; i <= n; i ++ )
        {
        f[i&1][0] = max(f[i - 1&1][0], f[i - 1&1][1] + prices[i-1] - fee);
        f[i&1][1] = max(f[i - 1&1][1], f[i - 1&1][0] - prices[i-1]);
        }
        return f[n&1][0];
    }
};

LeetCode 715. Range 模块

typedef pair<int, int> PII;//习惯用pair存区间
const int INF = 2e9;

#define x first
#define y second

class RangeModule {
public:
    set<PII> S;//set平衡树维护所有相互之间不能合并的区间

    RangeModule() {
        S.insert({-INF, -INF});//加入两个哨兵,这样查询时候不需要判断无解情况
        S.insert({INF, INF});
    }
//插入区间时可能发生合并
    void addRange(int left, int right) {
//要判断当前待插入的区间是否会发生合并的情况,往前找到第一个左端点比当前区间左端点小的区间
        auto i = S.lower_bound({left, -INF});//此时区间i是左端点大于等于left的第一个区间
        i -- ;
        if (i->y < left) i ++ ;
        if (i->x > right) {
            S.insert({left, right});
        } else {//有交集的情况
            auto j = i;
            while (j->x <= right) j ++ ;
            j -- ;//此时j是最后一个有交集的区间
            PII t(min(i->x, left), max(j->y, right));//要插入整个大的区间
            while (i != j) {//删掉i到j的部分
                auto k = i;
                k ++ ;
                S.erase(i);
                i = k;
            }
            S.erase(i);//此时删掉的是j
            S.insert(t);
        }
    }

    bool queryRange(int left, int right) {
        auto i = S.upper_bound({left, INF});//左端点大于left最小的一个区间
        i -- ;//左端点小于等于left的最大的一个区间
        return i->y >= right;
    }

    vector<PII> get(PII a, PII b) {//返回a中一系列剩余区间
        vector<PII> res;
        if (a.x < b.x) {
            if (a.y > b.y) {
                res.push_back({a.x, b.x});
                res.push_back({b.y, a.y});
            } else {
                res.push_back({a.x, b.x});
            }
        } else {
            if (a.y > b.y) res.push_back({b.y, a.y});
        }
        return res;
    }
//删掉一个区间可能会产生两个新的区间
    void removeRange(int left, int right) {
        auto i = S.lower_bound({left, -INF});
        i -- ;
        if (i->y < left) i ++ ;
        if (i->x <= right) {
            auto j = i;
            while (j->x <= right) j ++ ;
            j -- ;//ij是第一个和左后一个有交集的区间

            auto a = get(*i, {left, right});//ij剩下的区间
            auto b = get(*j, {left, right});
            while (i != j) {
                auto k = i;
                k ++ ;
                S.erase(i);
                i = k;
            }
            S.erase(i);
            for (auto t: a) S.insert(t);//可能有重复,但set会去重
            for (auto t: b) S.insert(t);
        }
    }
};

LeetCode 717. 1比特与2比特字符

class Solution {
public:
    bool isOneBitCharacter(vector<int>& bits) {
        for (int i = 0; i < bits.size(); i ++ ) {
            if (i == bits.size() - 1 && !bits[i]) return true;
            if (bits[i]) i ++ ;//遇到1跳两位,这里+,循环也+
        }
        return false;
    }
};

LeetCode 718. 最长重复子数组

typedef unsigned long long ULL;
const int P = 131;
/*
注意这里是连续的子数组,如果是子序列的话就是dp问题 AcWing 897. 最长公共子序列
*/ 
class Solution {
public:
    int n, m;
    vector<ULL> ha, hb, p;

    ULL get(vector<ULL>& h, int l, int r) {
        return h[r] - h[l - 1] * p[r - l + 1];
    }

    bool check(int mid) {//如何快速判断两个序列中某两段是否相等:字符串哈希 AcWing 841. 字符串哈希
        unordered_set<ULL> hash;
        //枚举长度mid的区间的尾端点
        for (int i = mid; i <= n; i ++ ) hash.insert(get(ha, i - mid + 1, i));
        for (int i = mid; i <= m; i ++ )
            if (hash.count(get(hb, i - mid + 1, i)))
                return true;
        return false;
    }

    int findLength(vector<int>& A, vector<int>& B) {
        n = A.size(), m = B.size();
        ha.resize(n + 1), hb.resize(m + 1), p.resize(n + 1);
        for (int i = 1; i <= n; i ++ ) ha[i] = ha[i - 1] * P + A[i - 1];//预处理
        for (int i = 1; i <= m; i ++ ) hb[i] = hb[i - 1] * P + B[i - 1];
        p[0] = 1;
        for (int i = 1; i <= n; i ++ ) p[i] = p[i - 1] * P;

        int l = 0, r = n;
        while (l < r) {//二分区间长度,查看第一个区间长度是mid的子数组是否与第二个区间长度是mid的子数组相同
            int mid = l + r + 1 >> 1;
            if (check(mid)) l = mid;//扩大
            else r = mid - 1;
        }
        return r;
    }
};

LeetCode 719. 找出第 k 小的距离对

class Solution {
public:
    int get(vector<int>& nums, int mid) {
        int res = 0;
        //求出数组中差值小于等于mid的个数 经典双指针 对于每个i找到最小的一个j使得两者差值刚好小于等于mid
        for (int i = 0, j = 0; i < nums.size(); i ++ ) {
            while (nums[i] - nums[j] > mid) j ++ ;
            res += i - j;
        }
        return res;
    }

    int smallestDistancePair(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int l = 0, r = 1e6;
        while (l < r) {//二分出大于等于k的第一个绝对差值  O(nlogn)
            int mid = l + r >> 1;//差值小于等于mid的距离对的个数应该是差值大于等于k的第一个
            if (get(nums, mid) >= k) r = mid;
            else l = mid + 1;
        }
        return r;
    }
};

LeetCode 720. 词典中最长的单词

const int N = 30010;//一千个单词,每个单词最多30个字母,最多3万个节点

int son[N][26], idx;
int id[N];//每个点结尾的单词编号
/*
考虑到前缀相关的题目,我们考虑使用trie树数据结构来解题。首先将词典中的词都插入Trie树中,然后我们从根节点开始,DFS遍历所有是单词的节点(不仅仅是有这个节点,而且这个节点必须是单词节点,因为我们需要一次添加一个字符)。每次遍历到一个节点时,当前路径上的字符就是一个合法的单词,我们将当前单词和当前答案进行比较,如果长度大于当前答案,我们就更新答案。因为我们DFS遍历是就是先遍历字典序较小的那个,如果后面遇到了长度相等的那个一定字典序更大,不是我们想要的答案。
*/
class Solution {
public:
    void insert(string& str, int k) {
        int p = 0;
        for (auto c: str) {
            int u = c - 'a';
            if (!son[p][u]) son[p][u] = ++ idx;
            p = son[p][u];
        }
        id[p] = k;
    }

    vector<int> dfs(int p, int len) {//返回长度和id   
        vector<int> res{len, id[p]};
        for (int i = 0; i < 26; i ++ ) {//枚举当前点的所有儿子
            int j = son[p][i];
            if (j && id[j] != -1) {//这个点可以走
                auto t = dfs(j, len + 1);
                if (res[0] < t[0]) res = t;//严格小于,等于是字典序更大的情况
            }
        }
        return res;
    }

    string longestWord(vector<string>& words) {
        memset(id, -1, sizeof id);//最开始都没有单词编号
        memset(son, 0, sizeof son);
        idx = 0;
        for (int i = 0; i < words.size(); i ++ ) insert(words[i], i);
        auto t = dfs(0, 0);//从根节点dfs
        if (t[1] != -1) return words[t[1]];
        return "";
    }
};

LeetCode 721. 账户合并

class Solution {
/*
用email判断人是否相同,相同就合并
集合合并==并查集
*/
public:
    vector<int> p;

    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
        int n = accounts.size();
        for (int i = 0; i < n; i ++ ) p.push_back(i);//初始化并查集数组
        unordered_map<string, vector<int>> hash;//对于同一个邮件来说,存包含这个邮件的id
        for (int i = 0; i < n; i ++ ) {
            for (int j = 1; j < accounts[i].size(); j ++ )//0是名字,从1开始
                hash[accounts[i][j]].push_back(i);
        }
        for (auto& [k, v]: hash)
            for (int i = 1; i < v.size(); i ++ )
                p[find(v[i])] = find(v[0]);//把同一个邮箱的所有id合并到一起
        vector<set<string>> res(n);//判重
        for (int i = 0; i < n; i ++ )
            for (int j = 1; j < accounts[i].size(); j ++ )
                res[find(i)].insert(accounts[i][j]);//把在同一集合的所有邮箱合并到根节点
        vector<vector<string>> ans;
        for (int i = 0; i < n; i ++ )
            if (res[i].size()) {//把这个根节点的姓名和邮箱全部插入
                vector<string> t;
                t.push_back(accounts[i][0]);
                for (auto& e: res[i]) t.push_back(e);
                ans.push_back(t);
            }
        return ans;
    }
};

LeetCode 722. 删除注释

class Solution {
public:
    vector<string> removeComments(vector<string>& source) {
        string str;
        for (auto& s: source) str += s + '\n';//把所有行放到一个字符串中
        vector<string> res;
        string line;

        for (int i = 0; i < str.size(); ) {//枚举每个字符
            if (i + 1 < str.size() && str[i] == '/' && str[i + 1] == '/') {
                while (str[i] != '\n') i ++ ;//遇到//,过滤掉回车前面所有
            } else if (i + 1 < str.size() && str[i] == '/' && str[i + 1] == '*') {
                i += 2;
                while (str[i] != '*' || str[i + 1] != '/') i ++ ;
                i += 2;
            } else if (str[i] == '\n') {
                if (line.size()) {
                    res.push_back(line);
                    line.clear();
                }
                i ++ ;
            } else {
                line += str[i];
                i ++ ;
            }
        }
        return res;
    }
};

LeetCode 724. 寻找数组的中心索引

class Solution {
public:
    int sum,temp;
    int pivotIndex(vector<int>& nums) {
        int n=nums.size();
        if(n<1) return -1;
        for(auto x:nums) sum+=x;
        for(int i=0;i<n;i++)
        {
            if(temp*2==sum-nums[i]) return i;   //以下标i为边界,总和要减去nums[i]符合条件输出结果
            temp+=nums[i];
        }
        return -1;      //没符合标准的,直接退出
    }
};

LeetCode 725. 分隔链表

class Solution {
public:
    vector<ListNode*> splitListToParts(ListNode* root, int k) {
        int n = 0;
        for (auto p = root; p; p = p->next) n ++ ;//求链表总长度

        vector<ListNode*> res;
        auto p = root;
        for (int i = 0; i < k; i ++ ) {//枚举k部分
            int len = n / k;
            if (i + 1 <= n % k) len ++ ;//不能整除的部分往前面放一个
            res.push_back(p);
            for (int j = 0; j < len - 1; j ++ ) p = p->next;//遍历len-1次到结尾
            if (p) {
                auto q = p->next;
                p->next = NULL;
                p = q;
            }
        }
        return res;
    }
};

LeetCode 726. 原子的数量

typedef map<string, int> MPSI;//因为要求按照字典序输出,所以就用map来存

class Solution {
public:
    MPSI dfs(string& str, int& u) {
        MPSI res;
        while (u < str.size()) {
            if (str[u] == '(') {
                u ++ ;//跳过左括号
                auto t = dfs(str, u);
                u ++ ;//跳过右括号
                int cnt = 1, k = u;
                while (k < str.size() && isdigit(str[k])) k ++ ;//得到系数
                if (k > u) {
                    cnt = stoi(str.substr(u, k - u));
                    u = k;
                }
                for (auto& [x, y]: t) res[x] += y * cnt;
            } 
            else if (str[u] == ')') break;
            else {
                int k = u + 1;//跳过大写字母
                while (k < str.size() && str[k] >= 'a' && str[k] <= 'z') k ++ ;
                auto key = str.substr(u, k - u);
                u = k;
                int cnt = 1;
                while (k < str.size() && isdigit(str[k])) k ++ ;
                if (k > u) {
                    cnt = stoi(str.substr(u, k - u));
                    u = k;
                }
                res[key] += cnt;
            }
        }
        return res;
    }

    string countOfAtoms(string formula) {
        int k = 0;
        auto t = dfs(formula, k);
        string res;
        for (auto& [x, y]: t) {
            res += x;
            if (y > 1) res += to_string(y);
        }
        return res;
    }
};

LeetCode 728. 自除数

class Solution {
public:
    vector<int> selfDividingNumbers(int left, int right) {
        vector<int> res;
        for(int i=left;i<=right;i++){
            bool isflag=true;
            int n = i;
            while(n){
                int k = n%10;
                if(!k||i%k!=0){
                    isflag = false;
                    break;
                }
                n=n/10;
            }
            if(isflag) res.push_back(i);
        }
        return res;
    }
};

LeetCode 729. 我的日程安排表 I

typedef pair<int, int> PII;
const int INF = 2e9;

class MyCalendar {
public:
/*
给了很多左闭右开的区间,插入区间时查看是否与已有区间有交集
这道题与LeetCode 715. Range 模块很像
如何判断是否有交集,将所有区间分为两大块,根据待插区间的左端点分类
只需判断左侧第一个和右侧第一个区间是否有交集即可
*/
    set<PII> S;//把区间以pair的形式存入set中

    MyCalendar() {
        S.insert({-INF, -INF});//存两个哨兵区间,这样每次插入区间时,左右两端至少存在一个区间,不用特判空
        S.insert({INF, INF});
    }

    bool check(PII a, PII b) {
        if (a.second <= b.first || b.second <= a.first) return false;
        return true;
    }

    bool book(int start, int end) {
        auto i = S.lower_bound({start, -INF});//左端点大于等于当前左端点,右端点在任意位置,大于等于负无穷即可
        //i是右边第一个区间
        auto j = i;
        j -- ;//左边第一个区间 这是迭代器
        PII t(start, end);//变成区间
        if (check(*i, t) || check(*j, t)) return false;
        S.insert(t);
        return true;
    }
};

LeetCode 730. 统计不同回文子序列

class Solution {
public:
/*
区间dp
注意这里的不同不仅仅是位置不同,结果相同也不行
(动态规划) O(n^2)
定义 f(i,j) 表示闭区间 [i, j] 包括空串的不同回文子序列的数量。
初始时,f(i,i)=2。转移时,对于区间 [i, j],首先累加不同字符的个数。
根据f(i,j)中所有合法方案中的边界的第l个和第r个字符是什么来划分(注意只有四种字符)
四类:a...a, b...b, c...c, d...d 这四类没有交集,累加即可
然后对于每个字符,找到区间内最靠左的位置 l 和最靠右的位置 r,若 l<r,则转移 f(i,j)=f(i,j)+f(l+1,r−1)
最后再加上空串的 1。
快速找出整个区间中lr的位置的过程可以通过双端队列来优化。对于每个字符,维护一个双端队列,每次取队头和队尾作为 l 和 r。
*/
    int countPalindromicSubsequences(string s) {
        int n = s.size(), MOD = 1e9 + 7;
        vector<vector<int>> f(n + 2, vector<int>(n + 2, 1));
        //下标从1开始,0和n+1都会用到,初始化1是因为都包含空串
        for (int i = 1; i <= n; i ++ ) f[i][i] ++ ;//所有长度为1的区间都是一个回文子序列
        for (int len = 2; len <= n; len ++ ) {//区间长度从2开始
            deque<int> q[4];//数组q中用四个队列
            for (int i = 1; i <= n; i ++ ) {//枚举区间右端点
                q[s[i - 1] - 'a'].push_back(i);//当前元素的下标插入对应的队列中 注意s下标从0开始
                int j = i - len + 1;//左端点
                if (j >= 1) {
                    for (int k = 0; k < 4; k ++ ) {//分为四种情况
                        while (q[k].size() && q[k].front() < j) q[k].pop_front();//队头元素滑出队列
                        if (q[k].size()) {
                            f[j][i] ++ ;//从j到i中至少包含一个长度为1的回文子序列
                            int l = q[k].front(), r = q[k].back();
                            if (l < r)
                                f[j][i] = (f[j][i] + f[l + 1][r - 1]) % MOD;
                        }
                    }
                }
            }
        }
        return (f[1][n] + MOD - 1) % MOD;//注意减去空串 先加mod再减一是为了避免负数情况
    }
};

LeetCode 731. 我的日程安排表 II

class MyCalendarTwo {
public:
    map<int, int> S;

    MyCalendarTwo() {

    }
/*
这道题允许最多重叠两次,这道题与LeetCode 699. 掉落的方块一模一样
这道题可以用算法基础课中的差分来做
每插入一个区间将区间内全部+1
*/
    bool book(int start, int end) {
        S[start] ++ , S[end] -- ;//差分数组起点++,终点--
        int sum = 0;
        for (auto [k, v]: S) {
            sum += v;
            if (sum >= 3) {//说明有三个区间的起点++还没有被自己区间的终点--抵消掉,也就是三个日常安排都未结束
                S[start] --, S[end] ++ ;
                return false;
            }
        }
        return true;
    }
};

LeetCode 732. 我的日程安排表 III

class MyCalendarThree {
public:
    map<int, int> S;

    MyCalendarThree() {

    }

    int book(int start, int end) {
        S[start] ++ , S[end] -- ;
        int sum = 0, res = 0;
        for (auto [k, v]: S) {//时间复杂度O(n^2)
            sum += v;
            res = max(res, sum);
        }
        return res;
    }
};

LeetCode 733. 图像渲染

class Solution {
public:
/*
典型flood fill算法
把与给定坐标颜色相同的连通块找出来,全部染色成给定颜色
*/
    vector<vector<int>> g;//定义全局数组
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    void dfs(int x, int y, int color, int newColor) {
        g[x][y] = newColor;
        for (int i = 0; i < 4; i ++ ) {
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < g.size() && b >= 0 && b < g[0].size() && g[a][b] == color)
                dfs(a, b, color, newColor);
        }
    }

    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        g = image;
        int color = g[sr][sc];
        if (color == newColor) return g;//颜色相同不需要染色
        dfs(sr, sc, color, newColor);
        return g;
    }
};

LeetCode 735. 行星碰撞

class Solution {
public:
//分类讨论 栈
    vector<int> asteroidCollision(vector<int>& asteroids) {
        vector<int> res;
        for (auto x: asteroids) {
            if (x > 0) res.push_back(x);
            else {
                while (res.size() && res.back() > 0 && res.back() < -x) res.pop_back();
                if (res.size() && res.back() == -x) res.pop_back();
                else if (res.empty() || res.back() < 0) res.push_back(x);
            }
        }
        return res;
    }
};

LeetCode 736. Lisp 语法解析

typedef unordered_map<string, int> MPSI;//维护变量名与值的关系
/*
这道题与LeetCode 726. 原子的数量代码很像
函数式语言本质上是递归过程
*/
class Solution {
public:
    int evaluate(string expression) {
        int k = 0;
        return dfs(expression, k, MPSI());
    }

    int get_value(string& str, int& k, MPSI vars) {//最后不要传引用,以免被修改
        int value;
        if (str[k] == '-' || isdigit(str[k])) {//数字或负数
            int i = k + 1;
            while (isdigit(str[i])) i ++ ;
            value = stoi(str.substr(k, i - k));
            k = i;
        } else if (str[k] != '(') {//已经出现过的变量
            string name;
            while (str[k] != ' ' && str[k] != ')') name += str[k ++ ];
            value = vars[name];
        } else {
            value = dfs(str, k, vars);
        }
        return value;
    }

    int dfs(string& str, int& k, MPSI vars) {
        int value;//定义当前dfs的这个表达式最终对应的值
        k ++ ;  // 跳过 '('
        auto type = str.substr(k, 3);
        if (type == "let") {
            k += 4;  // 跳过 "let "
            while (str[k] != ')') {//判断时变量名还是值
                if (str[k] == '(' || str[k] == '-' || isdigit(str[k])) {
                //值可能是左括号开头的表达式,可能是数字,可能是负数,也有可能是一个已经出现过的变量
                    value = get_value(str, k, vars);
                    break;
                }
                string name;
                while (str[k] != ' ' && str[k] != ')') name += str[k ++ ];
                //前面的变量名以空格结尾,后面的变量名以右括号结尾
                if (str[k] == ')') {//是一个已经出现过的变量
                    value = vars[name];
                    break;
                }
                k ++ ;  // 跳过 ' ' 值的情况处理完了,接下来是名与值的关系
                vars[name] = get_value(str, k, vars);
                k ++ ;  // 跳过 ' '
            }
        } else if (type == "add") {
            k += 4;  // 跳过 "add "
            int a = get_value(str, k, vars);
            k ++ ;  // 跳过 ' '
            int b = get_value(str, k, vars);
            value = a + b;
        } else {
            k += 5;  // 跳过 "mult "
            int a = get_value(str, k, vars);
            k ++ ;  // 跳过 ' '
            int b = get_value(str, k, vars);
            value = a * b;
        }
        k ++ ;  // 跳过 ')'
        return value;
    }
};

LeetCode 738. 单调递增的数字

class Solution {
public:
/*
找到第一个逆序问题
贪心
1.假设当前数字本事就是这样一个单调递增的数字,那么ans就是N,否则,一定是从左开始的某一位数字比前一位数字小,
那对于前面的低位来说都是满足单调递增条件了,因为题目要求我们找到小于等于N的最大的整数,那么显然核心就是我们尽量不要修改前面的高位数字,因为高位数字对一个数的大小的贡献更大
2.那么这题我们可以从不满足条件的那一位往前找,将与前一位数字相等的位数最靠前的那一位数字减1,后面的数字全变成9即可
O(n),n为数字位数
*/
    int monotoneIncreasingDigits(int N) {
        auto str = to_string(N);
        int k = 0;
        while (k + 1 < str.size() && str[k] <= str[k + 1]) k ++ ;//找到第一个单调递减的位置
        if (k == str.size() - 1) return N;
        while (k && str[k - 1] == str[k]) k -- ;
        str[k] -- ;
        for (int i = k + 1; i < str.size(); i ++ ) str[i] = '9';
        return stoi(str);
    }
};

LeetCode 739. 每日温度

class Solution {
public:
/*
经典单调栈模板题,找到每个数右边第一个比它大的数
*/
    vector<int> dailyTemperatures(vector<int>& T) {
        stack<int> stk;
        vector<int> res(T.size());
        for (int i = T.size() - 1; i >= 0; i -- ) {
            while (stk.size() && T[i] >= T[stk.top()]) stk.pop();
            if (stk.size()) res[i] = stk.top() - i;
            stk.push(i);
        }
        return res;
    }
};

LeetCode 740. 删除与获得点数

const int N = 10010;
int cnt[N], f[N][2];//二维状态

/*
选了nums[i],就不能选nums[i] + 1,这道题特别像198. 打家劫舍,状态机那道题
一般这种有限制的选择问题都可以用dp来做
f[i][0]表示考虑前i个数,第i个数不选  f[i][0] = max(f[i - 1][0], f[i - 1][1]);
f[i][1]表示考虑前i个数,第i个数选 f[i][1] = f[i - 1][0] + i * cnt[i];
注意这里的选与不选是指完全不选和全部都选,因为选了一个i不影响选其他的i
以及这里的i不是下标,就是所选的数,cnt[i]是数i出现的次数
*/
class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        memset(cnt, 0, sizeof cnt);//清空
        memset(f, 0, sizeof f);
        for (auto x: nums) cnt[x] ++ ;//长度10000的数组,记录每个数出现多少次
        int res = 0;
        for (int i = 1; i < N; i ++ ) {
            f[i][0] = max(f[i - 1][0], f[i - 1][1]);
            f[i][1] = f[i - 1][0] + i * cnt[i];
            res = max(res, max(f[i][0], f[i][1]));
        }
        return res;
    }
};

LeetCode 741. 摘樱桃

/*
这道题和提高课中数字三角形模型中的AcWing 1027. 方格取数 AcWing 275. 传纸条一模一样,只是多了某些格子不能走的限制
等价于从左上到右下走两次
如果某个格子被两条路线同时经过,只能取一次樱桃数
*/
const int N = 55;
int f[N][N][N * 2];//三维状态数组定义

class Solution {
public:
    int cherryPickup(vector<vector<int>>& grid) {
        int n = grid.size();
        memset(f, -0x3f, sizeof f);//清空
        if (grid[0][0] != -1) f[1][1][2] = grid[0][0];//初始化
        for (int k = 3; k <= n * 2; k ++ )
/*
1<=i<=n  
1<=k-i<=n->  k - n <= i <= k - 1
*/
            for (int i = max(1, k - n); i <= min(n, k - 1); i ++ )
                for (int j = max(1, k - n); j <= min(n, k - 1); j ++ ) {
                    if (grid[i - 1][k - i - 1] == -1 || grid[j - 1][k - j - 1] == -1) continue;
                    int t = grid[i - 1][k - i - 1];
                    if (i != j) t += grid[j - 1][k - j - 1];//只取一次
                    for (int a = i - 1; a <= i; a ++ )
                        for (int b = j - 1; b <= j; b ++ )
                            f[i][j][k] = max(f[i][j][k], f[a][b][k - 1] + t);
                }
        return max(0, f[n][n][n * 2]);
    }
};

LeetCode 743. 网络延迟时间

const int N = 110, M = 6010, INF = 0x3f3f3f3f;//点数与边数
int h[N], e[M], w[M], ne[M], idx;//邻接表的定义
int dist[N];
bool st[N];
/*
给定一个有向图和起点,求从起点到其他所有点距离的最大值,经典的单源最短路问题
spfa
*/
class Solution {
public:
    void add(int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    }

    void spfa(int start) {
        queue<int> q;
        q.push(start);//把起点加入队列
        memset(dist, 0x3f, sizeof dist);//清空距离
        dist[start] = 0;//起点距离一开始是0
        while (q.size()) {
            int t = q.front();
            q.pop();
            st[t] = false;
            for (int i = h[t]; ~i; i = ne[i]) {//枚举邻边
                int j = e[i];//邻点编号
                if (dist[j] > dist[t] + w[i]) {
                    dist[j] = dist[t] + w[i];
                    if (!st[j]) {
                        q.push(j);
                        st[j] = true;
                    }
                }
            }
        }
    }

    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
        memset(h, -1, sizeof h);
        idx = 0;//清空邻接表和指针
        for (auto& e: times) {//建图
            int a = e[0], b = e[1], c = e[2];
            add(a, b, c);
        }
        spfa(k);
        int res = 1;
        for (int i = 1; i <= n; i ++ ) res = max(res, dist[i]);
        if (res == INF) res = -1;
        return res;
    }
};

LeetCode 744. 寻找比目标字母大的最小字母

class Solution {
public:
/*
给定一个递增的字母列表和目标字母
二分
*/
    char nextGreatestLetter(vector<char>& letters, char target) {
        int l = 0, r = letters.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (letters[mid] > target) r = mid;
            else l = mid + 1;
        }
        if (letters[r] > target) return letters[r];
        return letters[0];
    }
};

LeetCode 745. 前缀和后缀搜索

const int N = 2000000;
int son[N][27], w[N], idx;
/*
Trie树
我们对每个单词的所有后缀包括空串以及自己,拼接上#,在拼接上原单词.把整个字符串插入到Trie中,以apple为例,我们会插入 #apple, e#apple, le#apple, ple#apple, pple#apple, apple#apple 到Trie树中. 如果查询前缀是 ap,后缀是le的单词,我们可以够着这样的查询语句 le#ap。插入的时候,路径上每个节点都更新为当前的单词的权值。

时间复杂度:每个长度为L单词我们插入了L+1个单词,总共N个单词,那么插入的时间复杂度为O(N∗L^2),对于每次查询的时间复杂度为O(L),总共Q次查询,那么总的时间复杂度为O(N∗L^2+QL)。
*/
class WordFilter {
public:
    void insert(string& s, int id) {
        int p = 0;
        for (auto c: s) {
            int t = c == '#' ? 26 : c - 'a';
            if (!son[p][t]) son[p][t] = ++ idx;
            p = son[p][t];
            w[p] = id;
        }
    }

    int query(string s) {
        int p = 0;
        for (auto c: s) {
            int t = c == '#' ? 26 : c - 'a';
            if (!son[p][t]) return -1;
            p = son[p][t];
        }
        return w[p];
    }

    WordFilter(vector<string>& words) {
        memset(son, 0, sizeof son);
        idx = 0;
        for (int i = 0; i < words.size(); i ++ ) {
            string s = '#' + words[i];
            insert(s, i);
            for (int j = words[i].size() - 1; j >= 0; j -- ) {
                s = words[i][j] + s;
                insert(s, i);
            }
        }
    }

    int f(string prefix, string suffix) {
        return query(suffix + '#' + prefix);
    }
};

/**
 * Your WordFilter object will be instantiated and called as such:
 * WordFilter* obj = new WordFilter(words);
 * int param_1 = obj->f(prefix,suffix);
 */

LeetCode 746. 使用最小花费爬楼梯

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        // 状态表示:f[i]表示到达第i个台阶的最小花费
        // 状态计算:f[i] = min(f[i - 1] + cost[i - 1], f[i - 2] + cost[i - 2]);
        // 边界设置:
        int n = cost.size();
        vector<int> f(n + 1);
        f[0] = 0, f[1] = 0;
        for (int i = 2; i <= cost.size(); i ++) {
            f[i] = min(f[i - 1] + cost[i - 1], f[i - 2] + cost[i - 2]);
        }
        return f[n];
    }
};
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        // 状态表示:f[i]表示到达第i个台阶的最小花费
        // 状态计算:f[i] = min(f[i - 1] + cost[i - 1], f[i - 2] + cost[i - 2]);
        // 边界设置:
        int n = cost.size();
        int a = 0, b = 0, c;
        for (int i = 2; i <= cost.size(); i ++) {
            c = min(b + cost[i - 1], a + cost[i - 2]);
            a = b;
            b = c;
        }
        return c;
    }
};
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        // 状态表示:f[i]表示到达第i个台阶的最小花费
        // 状态计算:f[i] = min(f[i - 1] + cost[i - 1], f[i - 2] + cost[i - 2]);
        // 边界设置:
        int n = cost.size();
        vector<int> f(n + 1);
        vector<int> path(n + 1);
        path[0] = -1, path[1] = -1;
        f[0] = 0, f[1] = 0;
        for (int i = 2; i <= cost.size(); i ++) {
            if (f[i - 1] + cost[i - 1] <= f[i - 2] + cost[i - 2]) {
                path[i] = i - 1;
            } else {
                path[i] = i - 2;
            }
            f[i] = min(f[i - 1] + cost[i - 1], f[i - 2] + cost[i - 2]);
        }

        // cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
        // 输出路径:0 2 4 6 7 9 
        int t = path[n]; stack<int> stk; 
        while (t != -1) stk.push(t), t = path[t];

        while(stk.size()) {
            cout << stk.top() << " ";
            stk.pop();
        }
        cout << endl;

        return f[n];
    }

LeetCode 747. 至少是其他数字两倍的最大数

class Solution {
public:
    int dominantIndex(vector<int>& nums) {
        int k = 0;
        for (int i = 1; i < nums.size(); i ++ )
            if (nums[i] > nums[k])
                k = i;//找到数组中最大值下标
        for (int i = 0; i < nums.size(); i ++ )
            if (i != k && nums[k] < nums[i] * 2)
                return -1;
        return k;
    }
};

LeetCode 748. 最短补全词

class Solution {
public:
    string shortestCompletingWord(string licensePlate, vector<string>& words) {
        int count[26] = {0};
        for (auto c: licensePlate) {
            c = tolower(c);
            if (c >= 'a' && c <='z') count[c-'a']++;
        }
        string res = "";
        for (auto w: words) {
            int count1[26] = {0};
            for (auto c: w) count1[c-'a']++;
            bool match = true;
            for (int i=0; i<26; i++) {
                if (count[i] > count1[i]) {
                    match = false;
                    break;
                }
            }
            if (match) {
                if (res == "" || res.size() > w.size()) res = w;
            }
        }
        return res;
    }
};

LeetCode 749. 隔离病毒

typedef pair<int, int> PII;

#define x first
#define y second
/*
这道题是模拟题
可以用Flood Fill算法统计出每一块所有的1和会影响的0(放入set中,以便判重),统计要放的墙
遍历所有的感染区域,对于每个感染区域求出他的威胁的区域以及其面积(与当前连通分量相邻的0),如果需要隔离这个区域需要多少个防火墙。
在所有连通区域中选择威胁面积最大的那个连通分量进行隔离,将剩余的连通分量威胁的区域进行感染。
重复上述操作,直至病毒无法扩散或者已经扩散完了。
*/
class Solution {
public:
    int n, m;//整个矩阵长宽
    vector<vector<int>> g;//整个矩阵存一个全局变量
    vector<vector<bool>> st;//判重数组
    vector<PII> path;//flood fill中所有被遍历到的点
    set<PII> S;//当前flood fill中所有被影响到的点
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    int dfs(int x, int y) {
        st[x][y] = true;
        path.push_back({x, y});
        int res = 0;
        for (int i = 0; i < 4; i ++ ) {
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < n && b >= 0 && b < m) {
                if (!g[a][b]) S.insert({a, b}), res ++ ;//这块区域会被感染,并且还要放隔板
                else if (g[a][b] == 1 && !st[a][b])
                    res += dfs(a, b);
            }
        }
        return res;
    }

    int find() {
        st = vector<vector<bool>>(n, vector<bool>(m));//清空判重数组
        int cnt = 0, res = 0;//前者是感染区域数量,后者是防火墙数量
        vector<PII> ps;//被选出区域的所有点
        vector<set<PII>> ss;//所有明天会被感染的区域
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ ) 
                if (g[i][j] == 1 && !st[i][j]){
                    path.clear(), S.clear();
                    int t = dfs(i, j);//需要建立的防火墙的数量
                    if (S.size() > cnt) {//如果当前遍历的区域感染数量更大的话
                        cnt = S.size();
                        res = t;
                        ps = path;
                    }
                    ss.push_back(S);//把当前这片区域感染的存入总的明天被感染的
                }
        for (auto& p: ps) g[p.x][p.y] = -1;
        for (auto& s: ss)//枚举所有区域
            if (s.size() != cnt)
                for (auto& p: s)//把没有防护的区域感染掉
                    g[p.x][p.y] = 1;
        return res;
    }

    int containVirus(vector<vector<int>>& grid) {
        g = grid;//先存下来
        n = g.size(), m = g[0].size();
        int res = 0;
        while (true) {
            auto cnt = find();//每次需要放多少防火墙
            if (!cnt) break;
            res += cnt;
        }
        return res;
    }
};

LeetCode 752. 打开转盘锁

class Solution {
public:
/*
经典宽搜 是提高课中的最小步数模型
给定初始状态0000,目标状态比如0202
每次选择初始状态中的某一位,加1或减1,(注意在0到9循环)
总共10^4种状态相互转化(两个状态之间如果可以相互转化的话,就连一条边,某些节点不能走到)
求从起点走到终点的最小值,就是一个无向图求最短路的模型,每条边权值为1,所以bfs就行
*/
    int openLock(vector<string>& deadends, string target) {
        string start = "0000";
        if (start == target) return 0;
        unordered_set<string> S;
        for (auto& s: deadends) S.insert(s);
        if (S.count(start)) return -1;//题目给定终止状态不在,只需判断初始不在即可
        queue<string> q;//宽搜定义队列
        q.push(start);
        unordered_map<string, int> dist;
        dist[start] = 0;
        while (q.size()) {
            auto t = q.front();
            q.pop();
            for (int i = 0; i < 4; i ++ )
                for (int j = -1; j <= 1; j += 2) {//每一位两种选择
                    auto state = t;
                    state[i] = (state[i] - '0' + j + 10) % 10 + '0';
                    if (!dist.count(state) && !S.count(state)) {
                        //转换的状态没有被走过并且可以走
                        dist[state] = dist[t] + 1;
                        if (state == target) return dist[state];
                        q.push(state);
                    }
                }
        }
        return -1;
    }
};

LeetCode 772. 基本计算器 III

class Solution {
public:
	int calculate(string s) {
		char sign = '+';
		long num = 0;
		long res = 0;
		stack<long>x;
		int i = 0;
		while (i < s.size()) {
			if (s[i] >= '0'&&s[i] <= '9') {
				num *= 10;
				num += s[i] - '0';
			}
			else if (s[i] == '(') {
				int cnt = 0;
				int j = i;
				for (; i < s.size(); i++) {
					if (s[i] == '(')cnt++;
					if (s[i] == ')')cnt--;
					if (cnt == 0)break;
				}
				num = calculate(s.substr(j + 1, i-j - 1));
			}
			if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/' || i == s.size() - 1) {
				if (sign == '+')x.push(num);
				else if (sign == '-')x.push(-num);
				else if (sign == '*') {
					int temp = x.top(); x.pop();
					x.push(temp*num);
				}
				else if (sign == '/') {
					int temp2 = x.top(); x.pop();
					x.push(temp2 / num);
				}
				sign = s[i];
				num = 0;
			}
			i++;
		}
		//记录了所有的值,最后在栈中进行相加
		while (!x.empty()) {
			res += x.top(); x.pop();
		}
		return res;
	}
};

LeetCode 876. 链表的中间结点

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        auto p = head, q = head;
        while (q && q->next) {
            p = p->next;
            q = q->next->next;
        }
        return p;
    }
};

LeetCode 1099:小于 K 的两数之和

class Solution 
{
public:
    int twoSumLessThanK(vector<int>& nums, int k) 
    {
        int n=nums.size();
        sort(nums.begin(), nums.end());
        int res=INT_MIN;              
        for(int i=0,j=n-1;i<j;)
        {
            if (nums[i] + nums[j] >= k)
                j--;
            else
            {
                res=max(res, nums[i] + nums[j]);
                i++;
            }
        }
        return (res==INT_MIN ? -1 : res);
    }
};

LeetCode 1143. 最长公共子序列

/*
状态表示:
f(i,j)表示所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现
的子序列(公共子序列)中最长子序列的长度(max)
状态计算:
集合划分最主要的是不漏,如果属性是求最值,划分是否重复不重要,如果求数量,
不能重复
f(i,j)划分成四个部分
包含i和j f(i-1,j-1)+1(这种情况要考虑到需要a[i]和b[j]相等)
只包含i  f(i,j-1)一定不包含j,可能包含i,可能不包含i
只包含j  f(i-1,j)一定不包含i,可能包含j,可能不包含j
均不包含 f(i-1,j-1)
最后一种情况f(i-1,j-1)被f(i-1,j)或f(i,j-1)包含,所以不用单独考虑
中间两种情况的状态表示互有重复,且与只包含i(j)的情况不完全等价,而是包含关系
但因为是求最值,所以不遗漏才最重要
综上所述:
f(i,j)=max( f(i-1,j) , f(i,j-1) , f(i-1,j-1)+1)
*/
#include <iostream>
using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N];//注意定义时候别出错,y总这里最开始写成int,很久都没找到错误
int f[N][N];

int main()
{
    cin>>n>>m>>a+1>>b+1;

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
        }

    cout<<f[n][m]<<endl;

    return 0;
}
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.length(), n = text2.length();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        for (int i = 1; i <= m; i++) {
            char c1 = text1[i-1];
            for (int j = 1; j <= n; j++) {
                char c2 = text2[j-1];
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                if (c1 == c2)  dp[i][j] = dp[i - 1][j - 1] + 1;
            }
        }
        return dp[m][n];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值