力扣hot100分类整理及解题方法

hot100 题单

二叉树 16道

层序遍历,左孩子下标 i*2;右孩子下标 i*2+1 (下标从1开始

二叉树的中序遍历 94 简单

左根右

一直向左一直进栈,取栈输出,再向右

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> sk;
        while (root || !sk.empty()) {
            while (root) {
                sk.push(root);
                root = root->left;
            }
            root = sk.top();
            sk.pop();
            res.push_back(root->val);
            root = root->right;
        }
        return res;
    }
};

BFS

二叉树的层序遍历 102
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(!root)   return {};
        vector<vector<int>> res;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()){
            int n=que.size(); //这层的大小
            vector<int> level;//这层的数据
            for(int i=0;i<n;i++){ //遍历这一层
                TreeNode* node=que.front();
                que.pop();
                level.push_back(node->val);
                //将下一层结点放入队列
                if(node->left)  que.push(node->left);
                if(node->right) que.push(node->right);
            }
            res.push_back(level);
        }
        return res;
    }
};

递归

1. 翻转二叉树 226 简单
class Solution {
public:
    void dfs(TreeNode* t){
        if(!t)  return;
        dfs(t->left);
        dfs(t->right);
        TreeNode* l=t->left;
        TreeNode* r=t->right;
        t->left=r;
        t->right=l;
    }
    TreeNode* invertTree(TreeNode* root) {
        dfs(root);
        return root;
    }
};
2. 合并二叉树 617 简单
class Solution {
public:
    TreeNode* dfs(TreeNode* t1,TreeNode* t2){
        TreeNode* t=new TreeNode();
        if(t1 && t2){
            t->val=t1->val+t2->val;
            t->left=dfs(t1->left,t2->left);
            t->right=dfs(t1->right,t2->right);
            return t;
        }else if(t1){
            return t1;
        }else if(t2){
            return t2;
        }else{
            return nullptr;
        }
    }
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        return dfs(root1,root2);
    }
};
3. 二叉树的最大深度 104 简单 略

4. 对称二叉树 101 简单

root是轴对称的:root的左子树和右子树是对称的

树 a 和树 b 是对称的:根相等 && a 的左子树和b右子树是对称的 && a的右子树和b的左子树是对称的

class Solution {
public:
    bool dfs(TreeNode* l, TreeNode* r) {
        if (!l && !r)
            return true;
        if (!l && r || l && !r)
            return false;
        if (l->val != r->val) {
            return false;
        }
        return dfs(l->left, r->right) && dfs(l->right, r->left);
    }
    bool isSymmetric(TreeNode* root) {
        if (!root)
            return true;
        return dfs(root->left, root->right);
    }
};
5. 把二叉搜索树转换为累加树 538

右根左

全局变量sum

class Solution {
public:
    int sum=0;
    void dfs(TreeNode* t){
        if(!t){
            return;
        }
        dfs(t->right);
        sum+=t->val;
        t->val=sum;
        dfs(t->left);
    }
    TreeNode* convertBST(TreeNode* root) {
        dfs(root);
        return root;
    }
};
6. 验证二叉搜索树 98 √

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

保证结点t的值在(low,up)之间

class Solution {
public:
    bool dfs(TreeNode* t, long long low, long long up) {
        if (!t)		return true;
        long long x = t->val;
        return x < up && x > low && dfs(t->left, low, x)&&dfs(t->right, x, up);
    }
    bool isValidBST(TreeNode* root) { 
        return dfs(root, LLONG_MIN, LLONG_MAX); 
    }
};
7. 二叉树的最近公共祖先 236

先序遍历,目标:找到 p 和 q

class Solution {
public:
    TreeNode* dfs(TreeNode* t, TreeNode* p, TreeNode* q) {
        if (!t || t == p || t == q) {
            return t;
        }
        TreeNode* l = dfs(t->left, p, q);
        TreeNode* r = dfs(t->right, p, q);
        if (l && r) { // p,q 在t左右两侧
            return t;
        }
        if (l) { // p q都在左侧
            return l;
        }
        if (r) { // 都在右侧
            return r;
        }
        return nullptr; // 左右都没有
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return dfs(root, p, q);
    }
};
8. 二叉树的直径 543 简单

一条路径的长度 = 该路径经过的节点数 - 1

而任意一条路径均可以被看作由某个节点为起点,从其左儿子和右儿子向下遍历的路径拼接得到。

class Solution {
    int ans;//答案经过的结点数
    int depth(TreeNode* t){ //求以t为根的树的深度(深度意思是最大深度)
        if (t == NULL) {
            return 0; // 访问到空节点了,返回0
        }
        int L = depth(t->left); // 左儿子为根的子树的深度
        int R = depth(t->right); // 右儿子为根的子树的深度
        ans = max(ans, L + R + 1); // 经过t的结点数量为 左深度+右深度+t本身=L+R+1
        return max(L, R) + 1; // 返回t为根的子树的深度
    }
public:
    int diameterOfBinaryTree(TreeNode* root) {
        ans = 0;
        depth(root);
        return ans - 1;
    }
};
9. 路径总和 III 437

根左右 前缀和

class Solution {
public:
    unordered_map<long long,int> mp;//前缀和数组,mp[s]表示从根到某结点和为s的路径数量
    //sum为从根节点到t的路径value和。需要作为参数传递,返回时sum回退。
    int dfs(TreeNode* t,long long sum,int targetSum){
        if(!t)  return 0;
        int res=0; //t子树包含的答案数
        sum+=t->val;
        if(mp.count(sum - targetSum)){//sum-mp=target,大前缀和-小前缀和 = 一段的和
            res += mp[sum-targetSum];
        }
        mp[sum]++;
        res+=dfs(t->left,sum,targetSum);
        res+=dfs(t->right,sum,targetSum);
        mp[sum]--;//需要回退
        return res;
    }
    int pathSum(TreeNode* root, int targetSum) {
        mp[0]=1; //考虑根节点value=tagert的情况
        return dfs(root,0,targetSum);
    }
};
10. 二叉树展开为链表 114

采用头插法构建链表,也就是从节点 6 开始,在 6 的前面插入 5,在 5 的前面插入 4,依此类推。

为此,要按照 6→5→4→3→2→1 的顺序访问节点,也就是按照右子树 - 左子树 - 根的顺序 DFS 这棵树。

DFS 的同时,记录当前链表的头节点为 head。一开始 head 是空节点。

class Solution {
public:
    TreeNode* head=nullptr;
    void dfs(TreeNode* t){
        if(!t)  return;
        dfs(t->right);
        dfs(t->left);
        //头插法
        t->right=head;
        t->left=nullptr;
        head=t;
        
    }
    void flatten(TreeNode* root) {
        dfs(root);
    }
};
11. 从前序与中序遍历序列构造二叉树 105

左右

根据前序的根,去中序中定位根的位置,从而能确定左子树的范围。

map: <中序遍历中的数值,在中序遍历中的位置下标> 方便“去中序中定位根的位置”

int preorder_left, int preorder_right, int inorder_left, int inorder_right : 边界位置下标

class Solution {
public:
    unordered_map<int, int> mp;
    TreeNode* dfs(vector<int>& preorder, vector<int>& inorder,
                  int preorder_left, int preorder_right, 
                  int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right)	return nullptr;
        int preorder_root = preorder_left; // 根节点在前序遍历中的位置下标
        int inorder_root = mp[preorder[preorder_root]]; // 求出根节点在中序遍历中的位置下标
        TreeNode* root = new TreeNode(preorder[preorder_root]); // 构建根节点
        int size_left_subtree = inorder_root - inorder_left; // 左子树的节点数目
        // 算出前序、中序的左、右子树的边界下标
        root->left = dfs(preorder, inorder, 
                         preorder_left + 1, preorder_left + size_left_subtree, 							 inorder_left, inorder_root - 1);
        root->right = dfs(preorder, inorder, 
                          preorder_left + size_left_subtree + 1,preorder_right, 						  inorder_root + 1, inorder_right);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 建立map
        for (int i = 0; i < n; i++) {
            mp[inorder[i]] = i;
        }
        return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};

字典树

实现 Trie (前缀树) 208

Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。

请你实现 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false

解:

class Trie {
public:
    vector<Trie*> next;
    bool isEnd;
    Trie() {
        next=vector<Trie*>(26);
        isEnd=false;
    }
    
    void insert(string word) {
        Trie* node=this; // 谁调用谁就是node
        for(char c:word){
            if(node->next[c-'a']==NULL){//新建一个“叉”
                node->next[c-'a']=new Trie();
            }
            node=node->next[c-'a'];
        }
        node->isEnd=true;
    }
    
    bool search(string word) {
        Trie* node=this;
        for(char c:word){
            if(node->next[c-'a']==NULL){
                return false;
            }
            node=node->next[c-'a'];
        }
        if(node->isEnd){
            return true;
        }
        return false;
    }
    
    bool startsWith(string prefix) {
        Trie* node=this;
        for(char c:prefix){
            if(node->next[c-'a']==NULL){
                return false;
            }
            node=node->next[c-'a'];
        }
        return true;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

位运算 3道

1 汉明距离 461 简单

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

给你两个整数 xy,计算并返回它们之间的汉明距离。

示例 1:

输入:x = 1, y = 4
输出:2
解释:
1   (0 0 0 1)
4   (0 1 0 0)
       ↑   ↑
上面的箭头指出了对应二进制位不同的位置。

异或^

%2:二进制的最后一位

/2 :二进制右移一位

class Solution {
public:
    int hammingDistance(int x, int y) {
        int ans=0;
        int z=x^y;
        while(z>0){
            if(z%2==1){
                ans++;
            }
            z=z/2;
        }
        return ans;
    }
};
2 比特位计数 338 动态规划

找规律 背诵

1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

6 - ->110

奇数的个位一定是1,偶数是0

​ 奇数:比前一个数多一个 个位的 1

​ 偶数:比它除以2的数多一个0(因为/2相当于右移一位)

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> ans;
        vector<int> dp(n+1); //dp[i]表示数字i的1的个数
        dp[0]=0;
        ans.push_back({0});
        for(int i=1;i<=n;i++){
            if(i%2==1){//奇数
                dp[i]=dp[i-1]+1;
            }
            else{//偶数
                dp[i]=dp[i/2];
            }
            ans.push_back(dp[i]);
        }
        return ans;
    }
};
3 只出现一次的数字 136 简单

背诵

  • 任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a。
  • 任何数和其自身做异或运算,结果是 0,即 a⊕a=0。
  • 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。

出现两次的数⊕ 得0,0⊕出现一次的数,得到出现一次的数

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(int num:nums){
            res=res^num;
        }
        return res;
    }
};

字符串 4道

1 字母异位词分组 49 √

map<排序后的string,原string>

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> ans;
        unordered_map<string,vector<string>> mp;
        for(string str:strs){
            string x=str;
            sort(x.begin(),x.end());
            mp[x].push_back(str);
        }
        for(auto e:mp){
            ans.push_back(e.second);
        }
        return ans;
    }
};
2 找到字符串中所有字母异位词 438 滑动窗口

滑动窗口大小=p.size(),在s中滑动

p中每个字符出现次数 == s的滑动窗口中 每个字符出现次数

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> ans;
        vector<int> count_s(26);
        vector<int> count_p(26);
        int n=s.size(),m=p.size();
        if(n<m) return ans;
        //计算p中每个字符出现次数。并初始滑动窗口
        for(int i=0;i<m;i++){
            count_p[p[i]-'a']++;
            count_s[s[i]-'a']++;
        }
        if(count_p==count_s){
            ans.push_back(0);
        }
        //在s中进行窗口滑动
        for(int i=0;i<n-m;i++){
            count_s[s[i]-'a']--;  //左侧滑走
            count_s[s[i+m]-'a']++;  //右侧滑入
            if(count_s==count_p){
                ans.push_back(i+1);
            }
        }
        return ans;
    }
};
3 无重复字符的最长子串 3 滑动窗口
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> mp;//滑动窗口中字母出现次数
        int ans=0;
        int n=s.size();
        int start,end=0; //滑动窗口边界
        while(end<n){
            mp[s[end]]++;
            while(mp[s[end]]>=2){//左边界向右滑,直至没有重复字母
                mp[s[start]]--;
                start++;
            }
            ans=max(ans,end-start+1);
            end++;//右边界向左滑
        }
        return ans;
    }
};
4 字符串解码 394 递归

每层dfs返回一个string,是这个括号内 解码后 的字符串

入口:遇到 [ 进入下一层递归

出口:遇到 ] 返回当前层递归处理后的字符串

class Solution {
public:
    int idx=0;//注意是全局变量,每层递归共享
    string dfs(string s){
        int n=s.size();
        string res;//当前层递归要返回的解码后字符串
        while(idx<n){
            if(s[idx]>='0'&&s[idx]<='9'){//数字
                int num=0;//多位数字组合成的倍数
                while(s[idx]>='0'&&s[idx]<='9'){
                    num=num*10+(s[idx]-'0');
                    idx++;
                }
                //数字后一定跟的是左括号,现在idx是左括号
                idx++;
                string ress= dfs(s); //ress是下层递归处理后的字符串,需要乘以当前倍数
                for(int i=1;i<=num;i++){
                    res=res+ress;
                }
            }else if(s[idx]>='a'&&s[idx]<='z'){//字母拼入res
                res=res+s[idx];
            }else{//右括号
                return res;
            }
            idx++;
        }//while
        return res;
    }
    
    string decodeString(string s) {
        return dfs(s);
    }
};

栈 3道

1 有效的括号 20 简单 √

( [ ) ] 是false

左括号进栈,右括号和栈顶匹配

class Solution {
public:
    bool isValid(string s) {
        stack<char> sk;
        unordered_map<char,char> mp={
            {')','('},
            {']','['},
            {'}','{'}
        };
        for(char x:s){
            if(mp.count(x)>0){//右括号
                if(sk.empty() || sk.top()!=mp[x]){
                    return false;
                }
                sk.pop();//匹配了
            }else{ //左括号
                sk.push(x);
            }
        }
        return sk.empty();
    }
};
2 每日温度 739

单调栈。从底向顶递减。从右往左遍历数组。向右看,看到第一个比自己高的。

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n=temperatures.size();
        vector<int> ans(n);
        stack<int> sk;
        for(int i=n-1;i>=0;i--){
            while(!sk.empty()){
                if(temperatures[i]<temperatures[sk.top()]){//找到第一个比自己大的
                    ans[i]=sk.top()-i;
                    break;
                }
                sk.pop();//栈里比自己小的不要,出栈
            }
            if(sk.empty()){
                ans[i]=0;
            }
            sk.push(i);
        }
        return ans;
    }
};
3 最小栈 155

辅助栈

class MinStack {
public:
    stack<int> sk; //主栈,正常入栈出栈
    stack<int> sk_min; //栈顶保存主栈当前的最小值
    MinStack() {
         sk_min.push(INT_MAX);
    }
    
    void push(int val) {
        sk.push(val);
        if(val<=sk_min.top()){
            sk_min.push(val);
        }
    }
    
    void pop() { 
        if(sk.top()==sk_min.top()){
            sk_min.pop();
        }
        sk.pop();
    }
    
    int top() {
        return sk.top();
    }
    
    int getMin() {
        return sk_min.top();
    }

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

链表 11道

1 反转链表 206 简单 √

反转单链表head

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //if(!head)   return nullptr;
        ListNode* cur=head,*pre=nullptr;
        while(cur){
            ListNode* nxt=cur->next;
            cur->next=pre;
            pre=cur;
            cur=nxt;
        }
        return pre;
    }
};
哈希:
2 相交链表 160 简单√

遍历A,将每个结点加入set

遍历B,返回第一个在set中的结点

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> set_a;
        ListNode* a=headA;
        while(a){
            set_a.insert(a);
            a=a->next;
        }
        ListNode* b=headB;
        while(b){
            if(set_a.count(b)>0){
                return b;
            }
            b=b->next;
        }
        return NULL;
    }
};
3 环形链表 141 简单√

与上一题类似

class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> set;
        ListNode* p=head;
        while(p){
            if(set.count(p)>0){
                return true;
            }
            set.insert(p);
            p=p->next;
        }
        return false;
    }
};
4 合并两个有序链表 21 简单 √
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* h=new ListNode();
        ListNode *m=h,*a=list1,*b=list2;
        while(a&&b){
            if(a->val<b->val){
                m->next=a;
                a=a->next;
            }else{
                m->next=b;
                b=b->next;
            }
            m=m->next;
        }
        if(a){
            m->next=a;
        }else{
            m->next=b;
        }
        return h->next;
    }
};

扩展:

双指针:
5 排序链表 148

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

解:分成左右两个子链表,分别排序 —–> 合并两个升序链表

class Solution {
public:
    //找中间节点函数。偶数返回前一半的最后一个节点,奇数返回中间结点
    ListNode* findmid(ListNode* head){
        ListNode* slow=head,*fast=head;
        while(fast->next&&fast->next->next){
            slow=slow->next;
            fast=fast->next->next;
        }
        return slow;
    }
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* h=new ListNode();
        ListNode *m=h,*a=list1,*b=list2;
        while(a&&b){
            if(a->val<b->val){
                m->next=a;
                a=a->next;
            }else{
                m->next=b;
                b=b->next;
            }
            m=m->next;
        }
        if(a){
            m->next=a;
        }else{
            m->next=b;
        }
        return h->next;
    }
    ListNode* sortList(ListNode* head) {
        //递归出口:空结点或只有一个节点,不用排序了
        if(!head|| !head->next){
            return head;
        }
        
        ListNode * mid=findmid(head);
        ListNode* head2=mid->next;
        mid->next=nullptr;
        return mergeTwoLists(sortList(head),sortList(head2));
    }
};
6 回文链表 简单 234 (1+5)

拆成两半(用快慢指针找到中间结点),第二半反转,再比较

class Solution {
public:
    //找中间节点函数。偶数返回前一半的最后一个节点,奇数返回中间结点
    ListNode* findmid(ListNode* head){
        ListNode* slow=head,*fast=head;
        while(fast&&fast->next&&fast->next->next){
            slow=slow->next;
            fast=fast->next->next;
        }
        return slow;
    }
    //反转函数
    ListNode* reverse(ListNode* head){
        ListNode* cur=head;//当前遍历结点
        ListNode* ne;//下一个要处理的结点
        ListNode* pre=nullptr;//哨兵
        while(cur){
            ne=cur->next;
            cur->next=pre;
            pre=cur;
            cur=ne;
        }
        return pre;
    }
    //主函数
    bool isPalindrome(ListNode* head) {
        ListNode* tail=findmid(head);
        ListNode* right=tail->next;
        tail->next=nullptr;
        ListNode* head2=reverse(right);
        //如果是奇数,head2比head短
        while(head2){
            if(head->val!=head2->val)   return false;
            head=head->next;
            head2=head2->next;
        }
        return true;
    }
};
7 环形链表 II 142

快慢指针。

背诵方法:快慢指针相等时,慢指针=head,两指针同时移动一步,直到重合

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow=head,*fast=head;
        while(true){
            if(fast==NULL|| fast->next==NULL)  return NULL;
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast)  break;
        }
        slow=head;
        while(slow!=fast){
            slow=slow->next;
            fast=fast->next;
        }
        return fast;
    }
};

类似思想:

8 寻找重复数 287 位运算

与上一题一样:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow=0,fast=0;
        while(true){
            slow=nums[slow];//移动一步
            fast=nums[nums[fast]];//移动两步
            if(slow==fast)  break;
        }
        slow=0;
        while(slow!=fast){
            slow=nums[slow];
            fast=nums[fast];
        }
        return slow;
    }
};
9 删除链表的倒数第 N 个结点 19 √ 注意哨兵

fast 移动n个。slow在头。

fast和slow同时移动。fast移动到尾,要移动len-n个; 此时slow也移动len-n个,则slow到尾就是len-(len-n)=n个

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *a=new ListNode(0,head);//哨兵结点
        ListNode *slow=a,*fast=head;
        //q移动n个
        for(int i=1;i<=n;i++){
            fast=fast->next;
        }
        //同时移动
        while(fast){
            slow=slow->next;
            fast=fast->next;
        }
        //pre->next就是倒数第n个结点
        slow->next=slow->next->next; //删除操作
        return a->next;
    }
};
10 两数相加 2

模拟。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* l=new ListNode();
        ListNode* head=l;//保存头结点,以便最后返回
        int carry=0;
        while(l1 || l2){
            int n1=l1?l1->val:0; //不存在则补0
            int n2=l2?l2->val:0;
            int sum=n1+n2+carry;
            carry=sum/10;
            
            ListNode* t=new ListNode();
            t->val=sum%10;
            l->next=t;
            l=t;
            if(l1)  l1=l1->next;
            if(l2)  l2=l2->next;
        }
        if(carry>0){
            l->next=new ListNode(carry);
        }
        return head->next;
    }
};
11 LRU 缓存 146

双链表。dummy->pre即为队尾

O(1),需要用map

struct Node {
    int key;
    int value;
    Node* pre;
    Node* next;
    Node() {
        key = 0;
        value = 0;
    }
};
class LRUCache {
public:
    int capacity;
    Node* dummy;
    unordered_map<int,Node*> key_to_node;

    LRUCache(int c) {
        capacity=c;
        dummy=new Node();
        dummy->pre=dummy;
        dummy->next=dummy;
    }
    
    void remove_node(Node* x){
        x->pre->next=x->next;
        x->next->pre=x->pre;
    }
    void add_to_head(Node* x){
        x->next=dummy->next;
        x->pre=dummy;
        dummy->next->pre=x;
        dummy->next=x;
    }
    void move_to_head(Node* x){
        remove_node(x);
        add_to_head(x);
    }
    

    int get(int key) {
        if(key_to_node.count(key)==0){
            return -1;
        }
        Node* a=key_to_node[key];
        move_to_head(a);
        return a->value;
    }

    void put(int key, int value) {
        //有这个key
        if(key_to_node.count(key)>0){
            key_to_node[key]->value=value;
            move_to_head(key_to_node[key]);
            return;
        }
        //没有
        Node* node=new Node();
        node->key=key;
        node->value=value;
        add_to_head(node);
        key_to_node[key]=node;
        if(key_to_node.size()>capacity){
            Node* a=dummy->pre;
            remove_node(a);
            key_to_node.erase(a->key);
        }
    }
};

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

数组 18道

1 旋转图像 48

背诵

先对矩阵求对称,再对每一行左右翻转

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n=matrix.size();
        for(int i=0;i<n;i++){
            for(int j=0;j<i;j++){
                swap(matrix[i][j],matrix[j][i]);
            }
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<n/2;j++){
                swap(matrix[i][j],matrix[i][n-j-1]);
            }
        }
    }
};

二分查找 3道

模板:

  1. 返回第一个出现的位置

左侧是<它的,右侧是>=它的

int find(vector<int> nums,int target){
    int n=nums.size();
    int l=0,r=n-1;
    while(l<=r){
        int mid=l+(r-l)/2;
        if(target>nums[mid])    l=mid+1;
        else    r=mid-1;
    }
    return l;
}
  1. 简单的找
int find(vector<int> nums,int target){
    int n=nums.size();
    int l=0,r=n-1;
    while(l<=r){
        int mid=l+(r-l)/2;
        if(nums[mid]==targer)	return mid;
        if(target>nums[mid])    l=mid+1;
        else    r=mid-1;
    }
    return -1;
}
1 在排序数组中查找元素的第一个和最后一个位置 34

第一个:用模板1.

最后一个:用模板1查target+1,得到的位置再减一

class Solution {
public:
    //模板
    int find(vector<int> nums,int target){
        int n=nums.size();
        int l=0,r=n-1;
        while(l<=r){
            int mid=l+(r-l)/2;
            if(target>nums[mid])    l=mid+1;
            else    r=mid-1;
        }
        return l;
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> ans;
        int first=find(nums,target);
        if(first>=nums.size()||nums[first]!=target){
            return {{-1,-1}};
        }
        int last=find(nums,target+1);
        ans.push_back(first);
        ans.push_back(last-1);
        return ans;
    }
};
2 搜索旋转排序数组 33

nums是无序的,不能直接用二分

nums一定是这样的左右两段有序数组,称为左段、右段。

左段都是>=nums[0]的,右段都是<nums[0]的。

先判断mid在左段还是右段,再判断target在mid左侧还是右侧

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n=nums.size();
        int l=0,r=n-1;
        while(l<=r){
            int mid=(l+(r-l)/2);
            if(nums[mid]==target)   return mid;
            if(nums[0]<=nums[mid]){//说明mid在左段
                //tagert在mid左侧还是右侧?
                if(target>=nums[0]&&target<nums[mid]){//在左侧
                    r=mid-1;  
                }else{//在右侧
                    l=mid+1;
                }
            }else{//mid在右段
                //同理
                if(target>nums[mid]&&target<=nums[n-1]){
                    l=mid+1;
                }else{
                    r=mid-1;
                }
            }
        }
        return -1;
    }
};
3 搜索二维矩阵 II 240 矩阵 √

遍历每行,对每一行用二分查找。

class Solution {
public:
    bool find(vector<int> &nums,int target){//注意要写&号,不然会超出内存限制
        int n=nums.size();
        int l=0,r=n-1;
        while(l<=r){
            int mid=l-(l-r)/2;
            if(nums[mid]==target)   return true;
            if(target<nums[mid])    r=mid-1;
            else    l=mid+1;
        }
        return false;
    }
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m=matrix.size();
        int n=matrix[0].size();
        for(int i=0;i<m;i++){
            if(find(matrix[i],target)){
                return true;
            }
        }
        return false;
    }
};

快排 2道

模板

void quickSort1(vector<int>& nums, int l, int r) {
    // 1. 递归终止条件:当子数组长度为1或空时,无需排序
    if (l >= r) return;

    // 2. 初始化分区参数:
    // - i 初始在左边界左侧(l-1)
    // - j 初始在右边界右侧(r+1)
    // - 基准值 x 选中间元素(避免极端情况如全排序数组导致最坏时间复杂度)
    int i = l - 1, j = r + 1;
    int x = nums[l+(r-l)/2]; 

    // 3. Hoare 分区循环:双指针从两端向中间移动
    while (i < j) {
        // 3.1 移动左指针 i:跳过所有小于 x 的元素,直到找到 >=x 的元素
        do i++; while (nums[i] < x);

        // 3.2 移动右指针 j:跳过所有大于 x 的元素,直到找到 <=x 的元素
        do j--; while (nums[j] > x);

        // 3.3 如果指针未交错,交换左右指针的元素(确保左边 <=x,右边 >=x)
        if (i < j) swap(nums[i], nums[j]);
    }

    // 4. 递归排序左右子数组:
    // - 分区点为 j(因为当 i >= j 时,j 是左半部分的最右端)
    // - 左子数组:[l, j](所有元素 <=x)
    // - 右子数组:[j+1, r](所有元素 >=x)
    quickSort1(nums, l, j);
    quickSort1(nums, j + 1, r);
}
1 数组中的第K个最大元素 215
class Solution {
public:
    //从大到小排序。 k是数组下标
    int helper(vector<int>& nums,int l,int r,int k){
        if(l>=r)    return nums[k];//递归出口.排好序了
        int n=nums.size();
        int p=l+(r-l)/2;
        int x=nums[p]; //枢轴
        int i=l-1,j=r+1;
        while(i<j){
            do{i++;}while(nums[i]>x);
            do{j--;}while(nums[j]<x);
            if(i<j){
                swap(nums[i],nums[j]);
            }
        }
        //现在[l,j]中的元素均>[j+1,r]中的元素
        if(k<=j) return helper(nums,l,j,k);//不用管右边的具体顺序
        else    return helper(nums,j+1,r,k);//不用管左边
    }
    int findKthLargest(vector<int>& nums, int k) {
        int n=nums.size();
        return helper(nums,0,n-1,k-1);
    }
};
2 前 K 个高频元素 347
class Solution {
public:
    vector<int> ans;
    //按频率由大到小排序。k:还要找出k个高频数
    void helper(vector<int> nums,int l,int r,int k,unordered_map<int,int> v_f){
        if(k==0) return;
        int p=l+(r-l)/2;
        int fre=v_f[nums[p]];
        int i=l-1,j=r+1;
        while(i<j){
            do{i++;} while(v_f[nums[i]]>fre);
            do{j--;} while(v_f[nums[j]]<fre);
            if(i<j){
                swap(nums[i],nums[j]);
            }
        }
        int len=j-l+1;
        if(k>=len){
            //[l,j]的数全加进ans
            for(int i=l;i<=j;i++){
                ans.push_back(nums[i]);
            }
            helper(nums,j+1,r,k-len,v_f);//还要找k-len个
        }else{
            helper(nums,l,j,k,v_f);
        }
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> v_f;//值--频次
        unordered_set<int> v; //set用于数组去重
        //遍历nums,构造map
        for(int x:nums){
            v_f[x]++;
            v.insert(x);//去重
        }
        //构造去重后的数组a
        vector<int> a;
        for(int x:v){
            a.push_back(x);
        }
        helper(a,0,a.size()-1,k,v_f);
        return ans;
    }
};

双指针 6道

1 移动零 283 数组 简单

时刻保证 p1左边全是非0

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int n=nums.size();
        int p1=0,p2=0;
        while(p2<n){
            if(nums[p2]!=0){
                swap(nums[p1],nums[p2]);
                p1++;
            }
            p2++;
        }
    }
};

类似:

2 颜色分类 75

保证:p0左面全是0,p2右面全是2

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n=nums.size();
        int p0=0,p1=0,p2=n-1;
        while(p1<=p2){
            if(nums[p1]==0){
                swap(nums[p0],nums[p1]);
                p0++;
                p1++;
            }else if(nums[p1]==2){
                swap(nums[p1],nums[p2]);
                p2--;
                //不能p1++,因为i的位置可能是0,要和p0换
            }else{
                p1++;
            }
        }
    }
};
3 盛最多水的容器 11 √
class Solution {
public:
    int maxArea(vector<int>& height) {
        int n=height.size();
        int i=0,j=n-1;
        int ans=0;
        while(i<j){
            ans=max(ans,(j-i)*min(height[i],height[j]));
            if(height[i]<height[j]){
                i++;
            }else{
                j--;
            }
        }
        return ans;
    }
};
4 最短无序连续子数组 581

左段的最大值小于右边,右段的最小值大于左边

找中段的左右边界begin end

end:从左到右,最后一个<左面的max的

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int n=nums.size();
        int begin=0,end=-1;
        int max=nums[0],min=nums[n-1];
        for(int i=0;i<n;i++){
            //从左到右确定end 、更新max
            //end最终停在 最后一个<max的地方。所以end右边都是>max的
            if(nums[i]>=max){
                max=nums[i];
            }else{
                end=i;
            }
            //从右到左确定begin 、更新min
            //begin最终停在 最后一个>min的地方。所以begin左边都是<min的
            int j=n-i-1;
            if(nums[j]<=min){
                min=nums[j];
            }else{
                begin=j;
            }
        }
        return end-begin+1;
        //初始化begin=0,end=-1,这样end-begin+1=0
    }
};
5 三数之和 15

先排序,再枚举。这样枚举的[a,b,c] 满足 a<=b<=c ,时间复杂度低

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n=nums.size();
        sort(nums.begin(),nums.end());
        vector<vector<int>> ans;
        for(int i=0;i<n-2;i++){
            if(i>0&&nums[i]==nums[i-1]) continue;// 跳过重复元素
            // 双指针,目标是找到 nums[l] + nums[r] = -nums[i]
            int l=i+1,r=n-1;
            int target=-nums[i];
            while(l<r){
                int sum=nums[l]+nums[r];
                if(sum==target){
                    ans.push_back({nums[i],nums[l],nums[r]});
                    l++;
                    r--;
                    // 跳过重复元素
                    while(l<r&&nums[l]==nums[l-1])  l++;
                    while(l<r&&nums[r]==nums[r+1])  r--;
                }else if(sum<target){
                    l++;
                }else{
                    r--;
                }
            }
        }
        return ans;
    }
};
6 下一个排列 31

升序是最小的排列,降序是最大的排列。变大需要升序变降序

改变的地方越靠后,改变越小

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n=nums.size();
        //从后往前找 第一个升序对nums[i]< nums[j]
        int i=n-2,j=n-1;
        while(i>=0&&nums[i]>=nums[j]){
            i--;
            j--;
        }
        if(i>=0){
            //找满足nums[k]>nums[i]的最小的nums[k]
            int k=n-1;
            while(nums[k]<=nums[i]) k--;
            swap(nums[i],nums[k]);
        }
        //逆置 [j,n-1],使其升序。升序是最小的排列
        int l=j,r=n-1;
        while(l<r){
            swap(nums[l],nums[r]);
            l++;
            r--;
        } 
    }
};

蓝色的下一个排列为橙色:

回溯 6道

  1. 确定回溯函数参数

  2. 确定终止条件(叶子

  3. 确定单层遍历逻辑(处理、递归、回溯)

  4. 组合

1 组合总和 39 搜索
class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracking(vector<int>& candidates,int target,int sum,int idx){
        if(sum==target){
            ans.push_back(path);
            return;
        }
        if(sum>target){
            return;
        }
        for(int i=idx;i<candidates.size();i++){
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum+candidates[i],i);
            //可重复选,所以是i,不是i+1
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates,target,0,0);
        return ans;
    }
};
2 电话号码的字母组合 17 搜索
class Solution {
public:
    string map[10]={
        "","","abc","def",//0,1,2,3
        "ghi","jkl","mno",//4,5,6
        "pqrs","tuv","wxyz"//7,8,9
    };
    vector<string> ans;
    string path;
    void backtracking(string digits,int idx){
        if(idx==digits.size()){
            ans.push_back(path);
            return;
        }
        string letters=map[digits[idx]-'0'];
        for(int i=0;i<letters.size();i++){//遍历数字对应的字母集合
            path.push_back(letters[i]);
            backtracking(digits,idx+1);
            path.pop_back();
        }

    }
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0){
            return {};
        }
        backtracking(digits,0);
        return ans;
    }
};

子集:

要获取所有节点

3 子集 78
class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracking(vector<int>& nums,int idx){
        if(idx>nums.size()){
            return;
        }
        ans.push_back(path);
        for(int i=idx;i<nums.size();i++){
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums,0);
        return ans;
    }
};
  1. 排列
  • 每层都是从0开始搜索而不是idx
  • 需要used数组记录path里都放了哪些元素了
4 全排列 46 搜索
class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracking(vector<int> &nums,vector<bool> used){
        if(path.size()==nums.size()){
            ans.push_back(path);
            return;
        }
        for(int i=0;i<nums.size();i++){
            if(used[i]==true)   continue;
            path.push_back(nums[i]);
            used[i]=true;
            backtracking(nums,used);
            path.pop_back();
            used[i]=false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return ans;
    }
};
  1. 其他
5 括号生成 22 搜索
class Solution {
public:
    vector<string> ans;
    string path;
    void dfs(int l,int r,int n){//左括号数量、右括号数量
        if(l==n&&r==n){
            ans.push_back(path);
            return;
        }
        if(l<n){//如果还能加入左括号 就加入
            path.push_back('(');
            dfs(l+1,r,n);
            path.pop_back();
        }
        if(r<l&&r<n){//只有还有左括号的时候才能压入右括号
            path.push_back(')');
            dfs(l,r+1,n);
            path.pop_back();
        }
    }
    vector<string> generateParenthesis(int n) {
        dfs(0,0,n);
        return ans;
    }
};
6 单词搜索 79 矩阵
class Solution {
public:
    bool dfs(vector<vector<char>>& board,string word,int i,int j,int idx){
        int m=board.size();
        int n=board[0].size();
        //过界 或 不是要找的字母
        if(i<0||i>=m||j<0||j>=n||board[i][j]!=word[idx]){
            return false;
        }
        //找完了
        if(idx==word.size()-1){
            return true;
        }
        //符合board[i][j]=word[idx],继续找下一个字母:
        board[i][j]='\0';//标记,防止重复
        bool res=dfs(board,word,i+1,j,idx+1)||dfs(board,word,i-1,j,idx+1)||
            dfs(board,word,i,j+1,idx+1)||dfs(board,word,i,j-1,idx+1);
            //上下左右继续
        board[i][j]=word[idx];//回溯
        return res;
    }
    bool exist(vector<vector<char>>& board, string word) {
        int m=board.size();
        int n=board[0].size();
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(dfs(board,word,i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }
};

动态规划 20道

动规五部曲分别为:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

基础

1 爬楼梯 70 简单 √
class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(50);
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};
2 不同路径 62 √
class Solution {
public:
    int uniquePaths(int m, int n) {
        //dp[i][j]:到这格有多少条不同路径
        vector<vector<int>> dp(m,vector<int>(n));
        //最左一列
        for(int i=0;i<m;i++){
            dp[i][0]=1;
        }
        //最上一行
        for(int j=0;j<n;j++){
            dp[0][j]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];//从左边来+从上边来
            }
        }
        return dp[m-1][n-1];
    }
};
3 最小路径和 64 动态规划 √

dp[i][j]:走到grid[i][j]处的最小路径和

分从左走过来、从上走过来 两种情况

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m=grid.size();
        int n=grid[0].size();
        vector<vector<int>> dp(m,vector<int>(n)); 
        dp[0][0]=grid[0][0];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(i==0&&j==0)  continue;
                if(i==0){ //第一行 边界
                    dp[0][j]=dp[0][j-1]+grid[0][j];
                }else if(j==0){ //第一列边界
                    dp[i][0]=dp[i-1][0]+grid[i][0];
                }else{
                    dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
                }
            }
        }
        return dp[m-1][n-1];
    }
};
4 最大正方形 221 动态规划

左、上、左上,三者取最小

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int m=matrix.size();
        int n=matrix[0].size();
        vector<vector<int>> dp(m,vector<int>(n));
        int max_side=0;//最大边长
        //处理第一行和第一列边界
        for(int i=0;i<m;i++){
            if(matrix[i][0]=='1'){
                dp[i][0]=1;
                max_side=1;
            }   
        }
        for(int j=0;j<n;j++){
            if(matrix[0][j]=='1'){
                dp[0][j]=1;
                max_side=1;
            }
        }
        
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                if(matrix[i][j]=='1'){
                    dp[i][j]=min(dp[i-1][j-1],min(dp[i][j-1],dp[i-1][j]))+1;
                    max_side=max(max_side,dp[i][j]);
                }
            }
        }
        return max_side*max_side;
    }
};
5 不同的二叉搜索树 96 树

dp[i] :总结点数为i的数,有dp[i]种

一棵二叉搜索树,总结点数为num,根节点的值为value,则左子树的结点个数为value-1,右子树的结点个数为num-value

种数=左子树种数*右子树种数

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n+1);
        dp[0]=1; //空结点也算1种
        dp[1]=1;
        for(int num=2;num<=n;num++){//num是总结点数
            for(int value=1;value<=num;value++){//value是根结点的值,范围肯定是在[1,num]之间
                dp[num]+=dp[value-1]*dp[num-value];
            }
        }
        return dp[n];
    }
};

子序列

1 回文子串 647 字符串

dp[i][j]:s[i]到s[j]的子串是否是回文串

注意:i和j遍历的顺序,要先遍历更短的子串。(动态规划的依赖性)

class Solution {
public:
    int countSubstrings(string s) {
        int ans=0;
        int n=s.size();
        vector<vector<bool>> dp(n,vector<bool>(n));
        for(int j=0;j<n;j++){
            for(int i=0;i<=j;i++){
                if(i==j){
                    dp[i][j]=true;
                }else if(j==i+1){
                    dp[i][j]=s[i]==s[j];
                }else{
                    dp[i][j]=s[i]==s[j]&&dp[i+1][j-1];
                }
                if(dp[i][j])    ans++;
            }
        }
        return ans;
    }
};
2 最长回文子串 5 字符串

和上一题几乎一样,只要记录、更新长度就好

class Solution {
public:
    string longestPalindrome(string s) {
        int maxlen=0;
        int begin=0;
        int n=s.size();
        vector<vector<bool>> dp(n,vector<bool>(n));
        for(int j=0;j<n;j++){
            for(int i=0;i<=j;i++){
                if(i==j){
                    dp[i][j]=true;
                }else if(j==i+1){
                    dp[i][j]=s[i]==s[j];
                }else{
                    dp[i][j]=s[i]==s[j]&&dp[i+1][j-1];
                }
                if(dp[i][j]){
                    if(j-i+1>maxlen){
                        maxlen=j-i+1;
                        begin=i;
                    }
                }
            }
        }
        return s.substr(begin,maxlen);
    }
};
3 编辑距离 72 字符串

dp[i][j]:word1的前i个字符 转变成 word2的前j个字符,最少的操作次数

遍历两个字符串

  • 字符相等。转化为:word1-1转变为word2-1
  • 字符不等
    • 替换这个字符。转化为:word1-1转变为word2-1 +1次操作
    • word1插入这个字符。word1转变为word2 -1 +1次操作
    • word2删除这个字符。word1-1转变为word2 +1次操作

dp[i][j]为以上情况的最小的那个

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size();
        int len2 = word2.size();
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0x3f3f3f3f));
        // 初始化dp
        for (int i = 0; i <= len1; i++) {
            dp[i][0] = i; // word1删除i次变成空
        }
        for (int j = 0; j <= len2; j++) {
            dp[0][j] = j; // 空串插入n次变成word2
        }
        // 遍历两个字符串
        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                if (word1[i - 1] == word2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
                int replace = dp[i - 1][j - 1] + 1;
                int insert = dp[i][j - 1] + 1;
                int deletee = dp[i - 1][j] + 1;

                dp[i][j] = min(dp[i][j], min(replace, min(insert, deletee)));
            }
        }
        return dp[len1][len2];
    }
};
4 最大子数组和 53 数组

dp[i]:以第i个数结尾的子数组(必须选上),最大和为dp[i]

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n+1);
        int ans=-0x3f3f3f3f;
        dp[0]=0;
        for(int i=1;i<=n;i++){
            if(dp[i-1]<=0){
                dp[i]=nums[i-1];
            }else{
                dp[i]=dp[i-1]+nums[i-1];
            }
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};
5 乘积最大子数组 152 数组

类似上一题

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n=nums.size();
        int dp_max[n+1];//以第i个数为结尾的子数组的乘积最大值
        int dp_min[n+1];
        memset(dp_max,0,n+1);
        memset(dp_min,0,n+1);
        dp_max[0]=1;
        dp_min[0]=1;
        int ans=nums[0];
        for(int i=1;i<=n;i++){
            dp_max[i]=max(nums[i-1],
                          max(dp_max[i-1]*nums[i-1],dp_min[i-1]*nums[i-1]));
            dp_min[i]=min(nums[i-1],
                          min(dp_max[i-1]*nums[i-1],dp_min[i-1]*nums[i-1]));
            ans=max(ans,dp_max[i]);
        }
        return ans;
    }
};
6 最长递增子序列 300 数组

dp[i]表示以nums的第i个元素结尾的最长递增子序列长度

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        int dp[n+1];
        memset(dp,0,n+1);
        int ans=0;
        for(int i=1;i<=n;i++){
            dp[i]=1;
            for(int j=1;j<i;j++){
                if(nums[j-1]<nums[i-1]){
                    dp[i]=max(dp[i],dp[j]+1);
                }else{
                    dp[i]=dp[i];
                }
            }
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};
7 最长连续序列 128 数组 不是动规
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        int ans=0;
        unordered_set<int> st(nums.begin(),nums.end());//nums加入set去重
        //遍历set
        for(int num:st){
            if(st.count(num-1))   continue;
            //以num-1开头的序列肯定比以num开头的序列长,已经处理过了,跳过
            
            int x=num+1;
            //不断找num+1,num+2.....把此序列全找完,断了就结束
            while(st.count(x)){
                x++;
            }
            //此序列以num开头,x-1结尾
            ans=max(ans,x-num);
        }
        return ans;
    }
};

其他

1 打家劫舍 198 数组
class Solution {
public:
    int rob(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n+1);//dp[i]:偷前i间房子的最高金额
        dp[0]=0;
        dp[1]=nums[0];
        for(int i=2;i<=n;i++){
            dp[i]=max(nums[i-1]+dp[i-2],dp[i-1]);//偷,不偷
        }
        return dp[n];
    }
};
2 打家劫舍 III 337 树

递归。左右根,因为要先知道左右子树,才能知道根

两个dp数组:

​ yes[t] : 以t为根的树,选择偷t,获得的最高金额

​ no[t] : 以t为根的树,选择不偷t,获得的最高金额

class Solution {
public:
    unordered_map<TreeNode*,int> yes,no;
    void dfs(TreeNode* t) {
        if (!t) return;
        dfs(t->left);
        dfs(t->right);
        yes[t] = no[t->left] + no[t->right] + t->val;
        no[t] = max(yes[t->left], no[t->left]) + max(yes[t->right], no[t->right]);
    }
    int rob(TreeNode* root) {
        dfs(root);
        return max(yes[root], no[root]);
    }
};
3 最佳买卖股票时机含冷冻期 309 数组

今天结束后,有四种状态:

  • 不持有(今天肯定没买)
    • 今天卖了——昨天是持有状态
    • 没买没卖——昨天不持有
  • 持有(今天肯定没卖)
    • 今天买了——昨天不持有、没卖(冷冻)
    • 没买没卖——昨天持有
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        int dp[n][4]; //dp[i][j]:第i天结束,状态为j,目前的收益。
        //prices从第0天开始
        memset(dp,0,sizeof dp);
        //4种状态:
        dp[0][0]=0;//不持有,今天卖出
        dp[0][1]=0;//不持有,今天没卖(也没买)
        dp[0][2]=-prices[0];//持有,今天买的(也没卖)
        dp[0][3]=-prices[0];//持有,今天没买,之前买的(今天没买没卖)

        for(int i=1;i<n;i++){
            dp[i][0]=prices[i]+max(dp[i-1][2],dp[i-1][3]);//昨天是持有状态
            dp[i][1]=max(dp[i-1][0],dp[i-1][1]);//昨天不持有
            dp[i][2]=-prices[i]+dp[i-1][1];//今天买,说明昨天不持有且没卖
            dp[i][3]=max(dp[i-1][2],dp[i-1][3]);//昨天持有
        }
        return max(dp[n-1][0],dp[n-1][1]);//最后肯定全卖了,不持有

    }
};

背包 5道

01 背包:
物品不可重复使用。

外循环遍历物品,内循环遍历背包 target,且内循环倒序

完全背包:

元素可重复使用。内循环正序。

(1)组合:不考虑物品之间顺序:物品放在外循环,背包target在内循环。
(2)排列:需考虑物品之间的顺序:背包 target 放在外循环,物品放在内循环。

01背包:

1 分割等和子集 416 数组

物品:nums 物品重量:nums[i] 背包容量:sum/2

//bool dp[i][j]:从前i个数选,和为j,可以选出来吗
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=0;
        for(int x:nums){
            sum+=x;
        }
        if(sum%2==1){//不能是奇数
            return false;
        }
        int n=nums.size();
        int target=sum/2;
        vector<vector<bool>> dp(n+1,vector<bool>(target+1));
        dp[0][0]=true;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=target;j++){
                if(nums[i-1]<=j){//可选可不选
                    dp[i][j]=dp[i-1][j]||dp[i-1][j-nums[i-1]];//不选 or 选
                }else{//不能选
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[n][target];
    }
};
//公式版:
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n=nums.size();
        int sum=0;
        for(int num:nums){
            sum+=num;
        }
        if(sum%2==1)    return false; 
        int target=sum/2;
        vector<bool> dp(target+1); //dp[j]:和为j能不能选出
        dp[0]=true;
        for(int i=0;i<n;i++){
            for(int j=target;j>=0;j--){
                if(j>=nums[i]){
                    dp[j]=dp[j]||dp[j-nums[i]];  //不选 or 选
                }else{ 
                    dp[j]=dp[j]; //只能不选 //这行可以没有
                }
            }
        }
        return dp[target];
    }
};
2 目标和 494

充当正数的num和 - 充当负数的num和=要求的target

充当正数的num和 + 充当负数的num和 =sum 是固定的

综合得,2*充当正数的num和=target+sum

target_new=(target+sum)/2

//dp[i][j]:从前i个数中选,和为j的种数
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n=nums.size();
        int sum=0;
        for(int x:nums){
            sum+=x;
        }
        if((sum+target)%2==1)   return 0;
        int t=(sum+target)/2;
        if(t<0)    return 0;
        vector<vector<int>> dp(n+1,vector<int>(t+1));
        dp[0][0]=1;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=t;j++){         
                if(nums[i-1]<=j){//可选可不选
                    dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];//不选+选
                }else{//不能选
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[n][t];
    }
};
//公式法:
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n=nums.size();
        int sum=0;
        for(int num:nums){
            sum+=num;
        }
        if((sum+target)%2==1)   return 0;
        int target_new=(sum+target)/2;
        if(target_new<0) return 0;
        vector<int> dp(target_new+1);//dp[j] 表示和为 j 的 num 组合有 dp[j] 种。
        dp[0]=1;
        for(int i=0;i<n;i++){
            for(int j=target_new;j>=0;j--){
                if(j>=nums[i]){
                    dp[j]=dp[j]+dp[j-nums[i]];//不选的组合种数+选的组合种数
                }else{//不选
                    dp[j]=dp[j];
                }
            }
        }
        return dp[target_new];
    }
};

完全背包:

1 零钱兑换 322

组合。

dp[j]:j元最少需要的硬币数

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n=coins.size();
        vector<long long> dp(amount+1,INT_MAX);
        dp[0]=0;
        for(int i=0;i<n;i++){
            for(int j=1;j<=amount;j++){
                if(coins[i]<=j){             
                    dp[j]=min(dp[j],dp[j-coins[i]]+1);//不选或选
                }
            }
        }
        if(dp[amount]==INT_MAX) return -1;//没找到
        return dp[amount];
    }
};
2 完全平方数 279

**组合。**物品:1,4,16,25…;背包:n

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0]=0;
        for(int i=1;i<=sqrt(n);i++){
            for(int j=1;j<=n;j++){
                if(j<i*i){
                    dp[j]=dp[j];
                }else{
                    dp[j]=min(dp[j],dp[j-i*i]+1);
                }
            }
        }
        return dp[n];
    }
};
3 单词拆分 139

排列。物品:wordDict;背包:s

dp[i]:s的以第i个字母结尾的字符串是否能由 wordDict 中组合而成

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        int n=s.size();
        vector<bool> dp(n+1);
        dp[0]=true;
        for(int i=1;i<=n;i++){
            for(int j=0;j<wordDict.size();j++){
                string word=wordDict[j];
                int len=word.size();
                if(len>i){//不能选
                    dp[i]=dp[i];
                }else{
                    dp[i]=dp[i]|| (dp[i-len]&&word==s.substr(i-len,len));
                    //不选或选
                }
            }
        }
        return dp[n];
    }
};

贪心 10道

1 多数元素 169 简单√

1

2 买卖股票的最佳时机 121 简单

1

3 两数之和 1 简单

map

4 找到所有数组中消失的数字 448

奇技淫巧

num范围是1到n,可以映射数组的[0,n),n个位置。

当前元素是 nums[i],那么我们把第 nums[i]−1 位置的元素 乘以 -1,表示这个该位置出现过

class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        int n=nums.size();
        vector<int> ans;
        for(int i=0;i<n;i++){
            int j=abs(nums[i]);
            if(nums[j-1]>0){
                nums[j-1]=-nums[j-1];
            }
        }
        for(int i=0;i<n;i++){
            if(nums[i]>0){
                ans.push_back(i+1);
            }
        }
        return ans;
    }
};
5 根据身高重建队列 406

h从大到小,k从小到大 排序。依次插入ans中

class Solution {
public:
    bool static compare_design(vector<int> a,vector<int> b){
        if(a[0]!=b[0])  return a[0]>b[0];
        return a[1]<b[1];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        int n=people.size();
        vector<vector<int>> ans;
        sort(people.begin(),people.end(),compare_design);
        for(auto p:people){
            if(p[1]>=ans.size()){
                ans.push_back(p);
            }else{
                ans.insert(ans.begin()+p[1],p);
            }
        }
        return ans;
    }
};
6 除自身以外数组的乘积 238 前缀和

前缀和数组left:左面的元素乘积

right:右面的元素乘积

ans=left*right

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n=nums.size();
        vector<int> left(n);
        vector<int> right(n);
        left[0]=1;
        for(int i=1;i<n;i++){
            left[i]=left[i-1]*nums[i-1];
        }
        right[n-1]=1;
        for(int i=n-2;i>=0;i--){
            right[i]=right[i+1]*nums[i+1];
        }
        vector<int> ans(n);
        for(int i=0;i<n;i++){
            ans[i]=left[i]*right[i];
        }
        return ans;
    }
};
7 和为 K 的子数组 560 前缀和

想找[i , j]数组和为k

前缀和数组sum

sum[j]-sum[i]=k

想找前面有没有sum[i]

即有没有sum[i]=sum[j]-k

前面都保存到map里,去map中找 sum[j]-k

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int> sum(n+1);
        sum[0]=0;
        for(int i=1;i<=n;i++){
            sum[i]=nums[i-1]+sum[i-1];
        }
        unordered_map<int,int> mp;//前缀和为sum的数量是mp[sum]
        int ans=0;
        //遍历sum
        for(int i=0;i<=n;i++){
            int x=sum[i]-k;
            if(mp.count(x)>0){
                ans+=mp[x];
            }
            mp[sum[i]]++;
        }
        return ans;
    }
};
8 任务调度器 621

建立大小为 n+1 的桶子,个数为任务数量最多的那个任务

答案 = (桶个数 - 1) * (n + 1) + 最后一桶的任务数

情况:如果被桶全被填满了,没有空闲时间,答案就是任务的数量

class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        unordered_map<char,int> mp;
        int tong=0;//桶个数
        for(char task:tasks){
            mp[task]++;
            tong=max(tong,mp[task]);
        }
        int count=0;//最后一桶的任务数
        for(auto x:mp){
            if(x.second==tong){
                count++;
            }
        }
        int ans=(tong-1)*(n+1)+count;
        int len=tasks.size();
        return max(ans,len);
    }
};
9 跳跃游戏 55
  • 如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点
  • 可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新
  • 如果可以一直跳到最后,就成功了
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n=nums.size();
        int reach=0;
        for(int i=0;i<n;i++){
            if(i>reach) return false;//前面没有能跳到i的
            reach=max(reach,i+nums[i]);
            if(reach>=n-1){
                return true;
            }
        }
        return false;
    }
};
10 合并区间 56 双指针

j,起点比i起点小的都合并

class Solution {
public:
	//按起点由小到大排序
    static bool cmp(vector<int> a,vector<int> b){
        if(a[0]!=b[0])  return a[0]<b[0];
        return a[1]<b[1];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end(),cmp);
        int n=intervals.size();
        vector<vector<int>> ans;
        int i=0;
        while(i<n){
            int t=intervals[i][1];//目前最右的
            int j=i+1;
            //起点<t的合并
            while(j<n&&intervals[j][0]<=t){
                t=max(t,intervals[j][1]);//更新最右
                j++;
            }
            ans.push_back({intervals[i][0],t});
            i=j;
        }
        return ans;
    }
};

图 3道

1 岛屿数量 200 搜索(联通块)dfs
  • 0 —— 海洋格子
  • 1 —— 陆地格子(未遍历过)
  • 2 —— 陆地格子(已遍历过) 避免重复遍历

遍历。是1则DFS它相邻的。

class Solution {
public:
    void dfs(vector<vector<char>> &grid,int i,int j){//注意&,否则不对
        if(!isValid(grid,i,j)){
            return;
        }
        if(grid[i][j]!='1'){
            return;
        }
        grid[i][j]='2';
        // 访问上、下、左、右四个相邻结点
        dfs(grid,i-1,j);
        dfs(grid,i+1,j);
        dfs(grid,i,j-1);
        dfs(grid,i,j+1);
    }
    //判断是否出界
    bool isValid(vector<vector<char>> &grid,int i,int j){////注意&,否则超时
        int m=grid.size();
        int n=grid[0].size();
        if(i<0||j<0||i>=m||j>=n) return false;
        return true;
    }
    int numIslands(vector<vector<char>>& grid) {
        int m=grid.size();
        int n=grid[0].size();
        int ans=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]=='1'){//b
                    ans++;
                    dfs(grid,i,j);
                }
            }
        }
        return ans;
    }
};
2 课程表 207 搜索(拓扑排序)

拓扑排序的过程,其实就两步:

  1. 找到入度为0 的节点,加入结果集
  2. 将该节点从图中移除

循环以上两步,直到 所有节点都在图中被移除了。

结果集的顺序,就是我们想要的拓扑排序顺序

入度为0的课程可以学。

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> indegree(numCourses);//记录每个课程的入度(它的前置课程数)
        unordered_map<int,vector<int>> mp; //课程号-它的后置课程集合
        //构造indegree和mp:
        for(int i=0;i<prerequisites.size();i++){
            indegree[prerequisites[i][0]]++;
            mp[prerequisites[i][1]].push_back(prerequisites[i][0]);
        }

        queue<int> q;//存放入度为0的课程号
        //构造q:
        for(int i=0;i<numCourses;i++){
            if(indegree[i]==0){
                q.push(i);
            }
        }
        
        int count=0;
        while(!q.empty()){ //入度为0的课程出队,从图中去掉
            int x=q.front();
            q.pop();
            count++;
            //x从图中去掉,那么x的后置课程入度要减1:
            for(int i=0;i<mp[x].size();i++){
                int num=mp[x][i];//x的后置课程
                if(indegree[num]>0){
                    indegree[num]--;
                    if(indegree[num]==0){
                        q.push(num);
                    }
                }
            }
        }
        if(count==numCourses)   return true;
        return false;
    }
};
3 除法求值 399 搜索(图,dfs)

那么我们求解 a / c 相当于计算从节点 a 到 节点 c 的路径的权重乘积

class Solution {
public:
    vector<double> ans;
    unordered_map<string,vector<pair<string,double>>> graph;
    unordered_map<string,bool> visited;
    bool found;
    
    void dfs(string start,string end,double path){
        //path是一路走来的乘积和
        if(found){
            return;
        }
        if(start==end){
            found=true;
            ans.push_back(path);
            return;
        }
        // 枚举该节点的所有邻节点:
        for(pair<string,double> neighbor:graph[start]){
            string node=neighbor.first;
            double weight=neighbor.second;
            if(visited[node]==0){
                visited[node]=1;
                dfs(node,end,path*weight);//递归处理邻节点
                visited[node]=0;
            }
        }
    }
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        //构造双向图:
        for(int i=0;i<equations.size();i++){
            graph[equations[i][0]].push_back(make_pair(equations[i][1],values[i]));
            graph[equations[i][1]].push_back(make_pair(equations[i][0],1.0/values[i]));
        }
        // 对于每个query,寻找从起点start到终点end的最短路径,并计算权重积
        for(int i=0;i<queries.size();i++){
            if(graph.count(queries[i][0])==0){//没有这个字母
                ans.push_back(-1.0);
                continue;
            }
            found=false;
            dfs(queries[i][0],queries[i][1],1);
            if(found==false){
                ans.push_back(-1.0);
            }
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值