我在代码随想录|写代码总结篇| 数组 |链表|哈希表|字符串|双指针法|栈与队列|二叉树|回溯算法|贪心算法|动态规划|单调栈

机缘

因为想要成为代码大神所以开始了我代码随想录的刷题过程


收获

  1. 对于算法的基础软件就是了解算法的基础,建立了自己的知识体系
  2. 发现算法还是一个积累的过程,对于很多知识点不是只刷往往一个总结的时间收获是以前的几倍
    在这里插入图片描述

日常

  1. 创作已经是你生活的一部分了,有限的精力下平衡创作和工作学习
  2. 刷题 + 文章 + 总结知识点

成就

  1. 对与算法进入小白阶段, 积累ing
  2. 养成了文章习惯
  3. 准备自己的想法

数组

在这里插入图片描述

在这里插入图片描述

二分查找模版

左开右闭

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0,right=nums.size();
        while(left<right){
            int mid = (right-left)/2+left;
            if(target>nums[mid]){
                left=mid+1;
            }else if(target<nums[mid]){
                right=mid;
            }else{
                return mid;
            }
        }
        return -1;
    }
};

闭区间

class Solution {
public:
    int search(vector<int>& nums, int target) {//二分查找找不到这么办?
        int i=0,j=nums.size()-1;
        int mid=0; 
        while(i<=j){
            mid = (j-i)/2+i;
            if(target>nums[mid]){
                i=mid+1;
            }else if(target<nums[mid]){
                j=mid-1;
            }else{
                return mid;
            }
        }
        return -1;
    }
};

另类:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(!binary_search(nums.begin(),nums.end(),target)) {//二分搜索元素是否存在
            return -1;
        } else {
            return lower_bound(nums.begin(),nums.end(),target) - nums.begin();//得到的地址减去首地址 等于下标
        }
    }
};

在二分查找中学会了查找区间 左开右闭 右闭左开 闭区间

移除元素: 原地移除用类哈希表的方法

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int cnt=0;
        for(int i=0;i<nums.size();i++){
            if(nums[i]!=val) nums[cnt++]=nums[i];
        }
        return cnt;
    }
};

有序数组的平方: 可能有数组中有负数

长度最小的子数组: 本质就是找数组中长度最小的组合

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int cnt=0,result=INT_MAX;//这里我设置了计数器
        for(int j=0,i=0;j<nums.size();j++){ //这里的j是终点也就是快指针
            cnt+=nums[j];//这里统计和用于与target进行比较
           while(cnt>=target){ //当我们的cnt>=target时也就代表我们的快指针到达终点
               result=min(result,j-i+1); //然后每次移动取最小的哪个
               cnt-=nums[i++]; //先删除慢指针所指的元素然后慢指针向前移动
           }
       }
       return result == INT_MAX ? 0 : result; //返回值与INT_MAX进行比较
       //如果相同者表示没有进while也就没有值
    }
};

螺旋矩阵II : 数组的处理对每个数组的处理规则要相同,遍历区间的问题
经典

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
       vector<vector<int>> arr(n, vector<int>(n,0));
       int j,k,o,p,m=1;
       if(n&1){//判断不是偶数是偶数返回0不是返回正数
           arr[n/2][n/2]=n*n;//奇数判断我们要将
       }
       for(int i=0;i<n/2;i++){//圈数大小,如果大于则会越界
       //就是起始点大于终点
           for(j=i;j<=n-i-2;j++){//向左遍历
               arr[i][j]=m++;
           }
           for(k=i;k<=n-i-2;k++){//向下遍历
               arr[k][n-i-1]=m++;
           }
           for(o=n-i-1;o>=i+1;o--){//o是大于等于n+1
               arr[n-i-1][o]=m++;//向左遍历
           }
           for(p=n-i-1;p>=i+1;p--){//向上遍历
               arr[p][i]=m++;
           }
       }
       return arr;
    }
};

链表

  1. 移除链表元素 : 学会了双指针法虚拟头结点 的方法, 不断遍历,链表就是想法会更简单但是实现起来会更难, 要注意结点是否为空,我们是否访问了空结点之类的

  2. 设计链表: 这个主要完成链表的 增删查改
    重要 最好将模版背一背

class MyLinkedList{
public:
    //初始化链表
    MyLinkedList(){
        this->size = 0;
        this->head=new ListNode(0);//这个是虚拟结点
    }
    //获取到第index个结点数值,如果index是非法的数值直接返回-1,注意index是从0开始
    int get(int index){
        if(index<0||index>=size) return -1;
        ListNode*cur = head;
        index++;
        while(index--) cur = cur->next;
        return cur->val;
    }
    void addAtHead(int val){
        addAtIndex(0,val);//既然都是增加结点那么调用我们增加结点的函数就可以
    }
    void addAtTail(int val){
        addAtIndex(size,val);//同理
    }
    //增加结点
    //如果index个节点之前插入新节点,那么新插入的节点为链表的新头节点
    //如果index等于链表的长度,则说明是新插入的节点为链表的尾结点
     //如果index大于链表的长度,则返回为空
    void addAtIndex(int index, int val){
        if(index>size) return ;
        index = max(0,index);
        size++;
        ListNode*cur = head;
        while(index--) cur = cur->next;
        ListNode*pred = new ListNode(val);
        pred->next = cur->next;
        cur->next = pred;
    }
    
    void deleteAtIndex(int index){//删除结点
        if(index<0||index>=size) return ;
        size--;//长度记得改变!!!!!
        ListNode*cur = head;
        while(index--) cur = cur -> next;
        ListNode*pred = cur->next;
        cur->next = pred->next;
        delete pred;
    }
    private://定义一个私有属性为类使用
        int size;
        ListNode*head;
};

  1. 反转链表: 理解起来有点难度主要是模拟,还是要记住相关的情况就好了
    1
    通过图可以知道pre主要储存的是上一个结点然后将 cur -> next 指向上一个结点即可完成反转
    这个思路是告诉完美储存的是上一个结点和双指针的方法,双指针主要还是链表使用最多

  2. 两两交换链表中的节点: 涉及链表交换,这道题是真的难理解,无论是交换链表还是循环操作(螺旋矩阵) 等等都要操作规则一样, 前面是操作俩结点但是整个时候他是操作3个结点, 题目是储存cur -> next 和 cur -> next -> next;然后对三个结点进行操作
    如图:
    在这里插入图片描述

  3. 删除链表的倒数第N个节点: 这个只要循环到第n个结点然后删除第n个结点即可,但是注意: 这个N可能为头结点, 使用虚拟头结点会更好

  4. 链表相交: 这题和循环链表一样,如同诗歌般绚丽,也如同命运般琢磨不透,正如<<我与地坛>>的书评般: 年少时捡到一把枪,因为年少无知,扣动扳机,谁都没有受伤,多年以后,背后隐隐约约有风声,回头,子弹正中眉心, 下面给出代码

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* you= headA;
        ListNode* me= headB;
        // 在遇到你之前,我们是俩条平行线,以外不会相遇
        if (you == nullptr || me == nullptr) 
            return nullptr;
        // 兜兜转转,我们的生命轨迹终将出现交点
        while(me!=you){
            // 我们携手与共,即使一个人掉队,也会拉上彼此继续前行
            you = you == nullptr ? headB : you->next;
            me = me == nullptr ? headA : me->next;r
            // 往后余生,你就是我的世界
        }
        return you;
        //即使身处两个世界,但只要以相同的速度双向奔赴,就一定会相遇。
        //两人同时以相同的速度走自己的路,走完自己的路之后走对方的路,两者有缘则相遇,
        //相遇则结束长跑,若无缘则同时走到“空”,跳出感情的死循环。
    }
};
  1. 环形链表II : 只要有环就不会遍历到NULL, 入口确定: 快慢指针.一个移动一个结点,一个移动两个结点,最后会在入口处相遇,
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode*fast = head;
        ListNode*slow = head;
        //我们彼此相识共同进步
        while(fast!=nullptr&&fast->next!=nullptr){
            //即使身处两个世界,以不相同的速度双向奔赴,但只要在同一恋爱循环中,就一定会相遇。
            slow = slow->next;
            fast = fast->next->next;
            //在命运的某一时刻我们登入的婚姻的殿堂
            if(slow==fast){
                //在我们相遇的哪个时刻脑中回忆着过往
                ListNode*cnt = slow; 
                ListNode*cur = head;
                while(cur!=cnt){
                    //从相识到相知彼此断靠近
                    cur=cur->next;
                    cnt = cnt->next;
                }
                //直到回忆道我们的青涩告白返回我们当初最真挚的情感
                return cur;
            }
        }
        //尽管我们没有进入恋爱的循环我们都有着彼此的故事
        return nullptr;
    }
};

哈希表

主要作用我认为是用来去重, C++代表哈希表的主要是 unordered_map<int,int> mp;unordered_set<int,int> st; 主要去记录元素也可以通过一些方法查找记录的元素

字符串

我认为字符串是重要的 常考

  1. 反转字符串: 最简单的方法是 reverse 也可以用 swap() 同样可以用双指针的方法
class Solution {
public:
    void reverseString(vector<char>& s) {
        int i = 0;  
        int k = s.size() - 1;  
        while (i < k) {  
            s[k],s[i]=s[k],s[i];
            i++;  
            k--;  
        }  
    }
};
  1. 反转字符串II : 特定区域反转字符串, 这个要对字符串的反转要有一定的理解,还有就是区间控制,比如大于2k时取什么,小于2k时取什么,遇到这个特定区间变化的时候一般会用 min() 或者 max()
class Solution {
public:
    string reverseStr(string s, int k) {
        int n = s.size();
        for(int i=0;i<n;i+=2*k) {
            reverse(s.begin()+i,s.begin()+min(i+k,n));
        }
        return s;
    }
};
  1. 替换数字: 遇到特定字符转换为指定字符的情况, 如图
    在这里插入图片描述
    核心代码
void solve() {
    string s; cin>>s;
    for(int i=0;i<s.size();i++){
        if(s[i]>='1'&&s[i]<='9'){
            s.replace(i,1,"number");
        }
    }
    std::cout<<s;
}

代码函数讲解: replace() 用于交换字符,代码中只要遇到 if(s[i]>='1'&&s[i]<='9') 将这个字符 交换为 number 其中 1 代表被交换字符的大小

  1. 翻转字符串里的单词: 这个便是区域交换, 交换时要注意空格

在这里插入图片描述

class Solution {
public:
    void reverse(string& s, int start, int end){ //翻转,区间写法:左闭右闭 []
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }

    void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
        int slow = 0;   
        for (int i = 0; i < s.size(); ++i) { //
            if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
                if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
                while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
                    s[slow++] = s[i++];
                }
            }
        }
        s.resize(slow); //slow的大小即为去除多余空格后的大小。
    }

    string reverseWords(string s) {
        removeExtraSpaces(s); //去除多余空格,保证单词之间之只有一个空格,且字符串首尾没空格。
        reverse(s, 0, s.size() - 1);
        int start = 0; //removeExtraSpaces后保证第一个单词的开始下标一定是0。
        for (int i = 0; i <= s.size(); ++i) {
            if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,说明一个单词结束。进行翻转。
                reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
                start = i + 1; //更新下一个单词的开始下标start
            }
        }
        return s;
    }
};

在这里插入图片描述
遇到空格怎么样将空格反转?

反转容易 reverse 但是读入

读入可以用

std::scanf(“%[^\n]s”, &s); //可以将空格读入

assign 是std::string类的一个成员函数,用于将一个字符串或字符数组赋值给另一个字符串

// 函数重载
string& assign (const string& str);
string& assign (const string& str, size_t subpos, size_t sublen);
string& assign (const char* s, size_t n);
string& assign (const char* s);
string& assign (size_t n, char c);

C++字符串常用函数
C字符串总结

  1. 右旋字符串: 左右旋转要学会复制字符串,然后将字符串相加减去复制后的字符串
void solve() {
    int n;
    std::string s;
    std::cin >> n >> s;
    int len = s.size();
    // Ensure n is within the bounds of the string length
    n = n % len;
    // Create a substring of the last n characters
    std::string t = s.substr(len - n, n);
    // Erase the last n characters from s
    s.erase(len - n, n);
    // Prepend the substring t to s
    s = t + s;
    // Output the modified string
    std::cout << s;
}

strstr()函数的使用说明

1strstr() 函数搜索一个字符串在另一个字符串中的第一次出现。
2、找到所搜索的字符串,则该函数返回第一次匹配的字符串的地址;
3、如果未找到所搜索的字符串,则返回NULL

KMP算法

class Solution {
public:
//KMP
    int strStr(string s, string t) {
        int n = s.size(),m=t.size();
        if(m==0) return 0;
        s.insert(s.begin(),' ');
        t.insert(t.begin(),' ');
        vector<int> next(m+1);
        //处理next数组
        for(int i=2,j=0;i<=m;i++){
            while(j&&t[i]!=t[j+1]) j = next[j];
            if(t[i] == t[j+1]) j++;
            next[i] = j;
        }
        //匹配过程
        for(int i=1,j=0;i<=n;i++){
            while(j&&s[i] != t[j+1]) j = next[j];
            if(s[i]==t[j+1]) j++;
            if(j==m) return i-m;
        }
        return -1;
    }
};

KMP 可以用C++的 find()

class Solution {
public:
    int strStr(string haystack, string needle) {
        int x = haystack.find(needle);
        return x;
    }
};
  1. 重复的子字符串: 将字符串复制后在新字符串中查找 s 如果找到 就返回 true 否则 false
class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        string t =s+s;//俩相同字符串相加
        t = t.substr(1,t.size()-2);//suberstr用于提取字符串和copy类似
        //这个将首尾第一个字符串剔除
        int index = t.find(s);//在我们新的字符串中查找s
        if(index != string::npos){
            return 1;
        }else{
            return 0;
        }
    }
};

双指针法

双指针返回即标记俩处位置,用双指针可以减少一定的时间

栈与队列

  1. 用栈实现队列
class MyQueue {
public:
    MyQueue() {

    }
    
    void push(int x) {
        s.push(x);//将元素入队列
    }
    
    int pop() {
        if(t.empty()&&s.empty()) return -1;//
        if(!t.empty()){//这里s改为t
            int result = t.top();
            t.pop();
            return result;
        }else{
            while(!s.empty()){
                t.push(s.top());
                s.pop();
            }
            int result = t.top();
            t.pop();
            return result;
        }
    }
    
    int peek() {
        if(t.empty()&&s.empty()) return -1;
        if(!t.empty()){
           return t.top();
        }else{
            while(!s.empty()){
                t.push(s.top());
                s.pop();
            }
            return t.top();
        }
    }
    
    bool empty() {
        if(t.empty()&&s.empty()) return -1;
        else return 0;
    }
    private:
        stack<int> s;
        stack<int> t;
};
  1. 用队列实现栈
class MyStack {
public:
    MyStack(){

    }
    void push(int x){
        s.push(x);
    }
    //问题是怎么样在第一个队列放一个元素
    //让元素始终在一个队列中
    int pop(){
        int size = s.size();//通过队列长度确定->可以让队列剩下一个元素
        size--;//留出一个元素空位
        while(size--){
            t.push(s.front());//这里将元素放在t队列中
            s.pop();
        }
        int result = s.front();
        s.pop();//这个时候s为空格
        s = t;//为什么要讲t赋值给s=>让元素始终在一个栈中?
        //这里让元素位置复原
        while(!t.empty()) {
            t.pop();
        }
        return result;
    }
    
    int top(){
        return s.back();//返回队尾元素
    }
    bool empty(){
        return s.empty();
    }
    private:
        queue<int>s;
        queue<int>t;
};
  1. 有效的括号
class Solution {
public:
    bool isValid(string s) {
        if(s.size() % 2 != 0) { return false; }
        stack<char> t;
        for(int i = 0;i < s.size();i++) { 
            if(s[i] == '(') { t.push(')'); }
            else if(s[i] == '[') { t.push(']'); }
            else if(s[i] == '{') { t.push('}'); }
            else if(t.empty() || s[i] != t.top()) { return 0; }
            else { t.pop(); }
        }
        return t.empty();
    }
};
  1. 删除字符串中的所有相邻重复项
class Solution {
public:
    string removeDuplicates(string S) {
        stack<char>st;
        for(char s : S){
            if(st.empty() || s != st.top()) {
                st.push(s);
            }else{
                st.pop();
            }
        }
        string ans = "";
        while(!st.empty()) {
            ans += st.top();
            st.pop();
        }
        reverse(ans.begin(),ans.end());
        return ans;`
    }
};
  1. 逆波兰表达式求值
class Solution {
public:
    int evalRPN(vector<string>& s) {
        stack<int>st;
        for(int i=0;i < s.size();i++) {
            if(st.empty() || (s[i].compare("+")  && s[i].compare("-") && s[i].compare("*") && s[i].compare("/"))) {
                int num = atoi(s[i].c_str());
                st.push(num);
            } else {
                int x = st.top();
                st.pop();
                int y = st.top();
                st.pop();
                switch(s[i][0]) {
                    case '+' : st.push(x+y);break;
                    case '-' : st.push(y-x);break;
                    case '*' : st.push(x*y);break;
                    case '/' : st.push(y/x); break;
                }
            }
        }
        return st.top();
    }
};

/*错误汇总:
*不能如下:
 if(st.empty() || (s[i] != '+' && s[i] != '-' && s[i] != '*' && s[i] != '/')) {
                int num = atio(&s[i]);
*也不可以如下:
   if(st.empty() || (s[i].compare('+')  && s[i].compare('-') && s[i].compare('*') && s[i].compare('/'))) {
                int num = atio(&s[i]);
*正确如下:
if(st.empty() || (s[i].compare("+")  && s[i].compare("-") && s[i].compare("*") && s[i].compare("/"))) {
                int num = atoi(s[i].c_str());
原因: compare 比较的是字符串 ''这样代表字符
atoi()转化的是字符串而且是c语言风格所以要用.c_str()格式化
还有为什么要s[i][0]我也不太清楚
*/

二叉树

二叉树的遍历区别 前序,中序,后序 遍历, 还有就是 层序遍历 ,还有就是二叉树的 深度 和 高度

  1. 层序遍历模版
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        vector<vector<int>> ans;
        if(root != nullptr) que.push(root);
        while(!que.empty()) {
            int size = que.size();
            vector<int>vec;
            for(int i = 0;i < size;i++) {
                TreeNode*node = que.front();
                que.pop();
                vec.push_back(node->val);
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            ans.push_back(vec);
        }
        return ans;
    }
};

只要去更改根结点转换即可

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        invertTree(root->left);         // 左
        swap(root->left, root->right);  // 中
        invertTree(root->left);         // 注意 这里依然要遍历左孩子,因为中间节点已经翻转了
        return root;
    }
};
  1. 二叉树的所有路径
class Solution {
public:
    void traversal(TreeNode*cur,string path,vector<string>&result) {
        path += to_string(cur->val);
        if(cur->left == NULL && cur->right == NULL) {
            result.push_back(path);
            return;
        }
        if(cur->left) traversal(cur->left,path +"->",result);//path+= "->"会影响整层循环
        if(cur->right) traversal(cur->right,path +"->",result);
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;string path;
        if(root == nullptr) return result;
        traversal(root,path,result);
        return result;
    }
};

二叉树主要是查找,删除,插入 等操作

回溯算法

回溯算法模版

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

回溯算法可以通过 剪枝优化 让算法效率更高

  1. 组合
class Solution {
private:
    vector<int>vec;
    vector<vector<int>>v;
    void backtracking(int n,int k,int startIndex) {
        if(vec.size()==k) {
            v.push_back(vec);
            return;
        }
        for(auto i = startIndex;i<=n;i++) {
            vec.push_back(i);
            backtracking(n,k,i+1);
            vec.pop_back();
        }
    }
    
public:
    vector<vector<int>> combine(int n, int k) {
        v.clear();
        vec.clear();
        backtracking(n,k,1);
        return v;
    }
};

全排列模版

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>>result;
        do {
            result.push_back(nums);
        } while(next_permutation(nums.begin(),nums.end()));
        return result;
    }
};

贪心算法

寻找最优解的过程,局部最优到全局最优, 比如我要把饼干分个N个孩子,每个饼干重量不同,孩子的胃口不同,我局部最优是不是要用最少重量的饼干去满足最小胃口的孩子,这样孩子的胃口也能饱
在这里插入图片描述
贪心基础题目:

  1. 分发饼干: 贪心基础题
  2. 摆动序列: 会有点难度, 最主要是下面的细节问题的处理

在这里插入图片描述

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int curDiff = 0,preDiff = 0,result = 1;
        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i+1] - nums[i];
            //出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                curDiff = nums[i + 1] - nums[i];
                
                if ((preDiff <= 0 && curDiff > 0) || preDiff >= 0 && curDiff < 0) {
                    result++;
                    preDiff = curDiff; //更新摆动变化
                }
            }
        }
        return result;
    }
};
  1. 最大子数组和: 应该比较简单,但是对于当前的我来写还是要消耗很多时间的
    最主要是其中含有负数的原因, 如果相加和成为了负数,直接清零,这样才能让数据相加最大化
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        int result = dp[0];
        for (int i = 1; i < nums.size(); i++) {
            dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
            if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
        }
        return result;
    }
};
  1. 跳跃游戏: 和我之前写过的一道ACM的题差不多,都是从当前起点通过步数跳道最后一个位置
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int k = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (i > k) return false;
            k = max(k, i + nums[i]);
        }
        return true;
    }
};

如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点
可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新
如果可以一直跳到最后,就成功了

官方题解:

在这里插入图片描述

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        int rightmost = 0;
        for (int i = 0; i < n; ++i) {
            if (i <= rightmost) {
                rightmost = max(rightmost, i + nums[i]);
                if (rightmost >= n - 1) {
                    return true;
                }
            }
        }
        return false;
    }
};
  1. 跳跃游戏 II : 这个除了能跳道对面还要找到最小的步数
class Solution {
public:
    int jump(vector<int>& nums) {
        int cnt = 0;
        int ans = 0;
        int next = 0;
        if (nums.size() == 1) return 0;
        for (int i = 0; i < nums.size() - 1; i++) {
            cnt = cnt > i + nums[i] ? cnt : i + nums[i];
            if(i == next) {
                next = cnt;
                ans++;
            }
        }
        return ans;
    }
};
  1. K次取反后最大化的数组和 : 只要取反后将最小的取反即可

动态规划

动态规划是有规律的和题型的我对于动态规划的理解目前可能不深,但是只要我们不断总结总会理解
下面是当前我对动态规划的总结

  1. 斐波那契数: dp 入门基础题, 有就是动态规划数组常常为dp 或者 f 命名
class Solution {
public:
    int fib(int n) {
        int a[n+5];
        a[0]=0;
        a[1]=1;
        for(int i=2;i<=n;i++){
            a[i]=a[i-1]+a[i-2];
        }
        return a[n];
    }
};
  1. 爬楼梯: 和斐波那契数列一样爬楼梯是当前位置是由上一个文章的次数 和 上上个位置和组合而成的
class Solution {
public:
    int climbStairs(int n) {
        long long int a[n+5];
        a[0]=1;
        a[1]=1;
        a[2]=2;
        for(int i=2;i<n+2;i++){
            a[i]=a[i-1]+a[i-2];
        }
        return a[n];
    }
};
  1. 使用最小花费爬楼梯: 与爬楼梯一样 ,可是加了条件 便是要最小花费, 可以用贪心 + 动规 的写法
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size() + 1);
        dp[0] = 0; // 默认第一步都是不花费体力的
        dp[1] = 0;
        for (int i = 2; i <= cost.size(); i++) {
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[cost.size()];
    }
};

贪心

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        // i = 0, 所站的平地,还没有迈步往上
        int min0 = 0;
        // i = 1, 往上爬一个台阶,因为题目可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯,所以这里也是0
        int min1 = 0;
        int minTop = 0;
        // i 表示阶梯顶部
        for(int i = 2; i <= n ; i++){
            // 要么一次迈两阶,要么先迈一阶再迈一阶
            minTop = min(min0 + cost[i - 2], min1 + cost[i - 1]);
            // 等同于min1变成了平地min0,minNext变成了min1,继而往上爬
            min0 = min1;
            min1 = minTop;
        }
        return minTop;
    }
};

动规解决路径问题

  1. 不同路径: 总结数学规律直接算的
class Solution {
public:
    int uniquePaths(int m, int n) {
        long long numerator = 1; // 分子
        int denominator = m - 1; // 分母
        int count = m - 1;
        int t = m + n - 2;
        while (count--) {
            numerator *= (t--);
            while (denominator != 0 && numerator % denominator == 0) {
                numerator /= denominator;
                denominator--;
            }
        }
        return numerator;
    }
};

易理解版

class Solution {
public:
    int uniquePaths(int m, int n) {
        int dp[m][n];
        dp[0][0] = 0;
        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];
    }
};
  1. 不同路径 II : 路径上有障碍物,直接跳过
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int l,r;
        int m =obstacleGrid.size();
        int n = obstacleGrid[0].size();
        int dp[m][n];
        if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1){
            return 0;
        }
        memset(dp,0,sizeof(dp));
        for (int i = 0; i < m && !obstacleGrid[i][0]; i++) dp[i][0] = 1;
        for (int j = 0; j < n && !obstacleGrid[0][j]; j++) dp[0][j] = 1;
        for (int i = 1;i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (obstacleGrid[i][j] == 1) {continue;}
                dp[i][j] = dp[i-1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};
  1. 背包问题 : 每种背包类型都要记下模板,没有模板很难做
  • 01背包: 物品只有一个要求最大背包价值
    基础模版
#include<bits/stdc++.h>
#define Run 0
#define endl "\n"
#define n 105
#define m 1010
using ll = long long;
using namespace  std;
ll dp[n][m];


void slove() {
    ll N,V;cin >> N >> V; //这个是物品数 N 背包体积V
    for (int i = 1 ; i <= N; i++) {
        ll w,v;cin >> w >> v; //每个物品的 重量 和 价值
        for (int j = 0; j <= V; j++) {
            if (j >= w) dp[i][j] = max(dp[i-1][j],dp[i - 1][j - w] + v); //递推公式(动态转移方程)
            else dp[i][j] = dp[i - 1][j];
        }
    }
    cout << dp[N][V] << endl;
}


int main() {
    cin.tie(0) -> ios::sync_with_stdio(0);
    cout.tie(0) -> ios::sync_with_stdio(0);
    #if Run
        int _;cin>>_;while(_--) slove();
    #else 
        slove();
    #endif
    return 0;
}

优化版本

include<bits/stdc++.h>
#define Run 0
#define endl "\n"
#define n 105
#define m 1010
using ll = long long;
using namespace  std;
ll dp[n][m];


void slove() {
    ll N,V;cin >> N >> V; //这个是物品数 N 背包体积V
    for (int i = 1 ; i <= N; i++) {
        ll w,v;cin >> w >> v; //每个物品的 重量 和 价值
        for (int j = 0; j <= V; j++) {
            if (j >= w) dp[i][j] = max(dp[i-1][j],dp[i - 1][j - w] + v); //递推公式(动态转移方程)
            else dp[i][j] = dp[i - 1][j];
        }
    }
    cout << dp[N][V] << endl;
}


int main() {
    cin.tie(0) -> ios::sync_with_stdio(0);
    cout.tie(0) -> ios::sync_with_stdio(0);
    #if Run
        int _;cin>>_;while(_--) slove();
    #else 
        slove();
    #endif
    return 0;
}
  • 完全背包
    模版
#include<bits/stdc++.h>
#define Run 0
#define endl "\n"
#define n 1010
#define m 1010
using ll = long long;
using namespace  std;
ll dp[n];


void slove() {
    ll N,V;cin >> N >> V; //这个是物品数 N 背包体积V
    memset(dp,0,sizeof dp);
    for (int i = 0; i < N; i++) {
        ll w,v; cin >> w >> v;
        for (int j = w; j <= V; j++) {
            dp[j] = dp[j] > dp[j - w] + v ? dp[j] : dp[j - w] + v;
        }
    }
    cout << dp[V] << endl;
}


int main() {
    cin.tie(0) -> ios::sync_with_stdio(0);
    cout.tie(0) -> ios::sync_with_stdio(0);
    #if Run
        int _;cin>>_;while(_--) slove();
    #else 
        slove();
    #endif
    return 0;
}
  1. 打家劫舍类问题: 都是举一反三的问题,通过一个问题然后加条件让解题代码完全不同,目前掌握不熟练,因为涉及到我当前没有掌握的区间变换问题,后续还得加油
class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < nums.size(); i++) {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[nums.size() - 1];
    }
};
  1. 买卖股票的最佳时机类问题: 完全没有搞懂,只是将代码抄了一遍,这个也要加油,我认为这个应该算是一个模块,要以段时间去学习,可能我是赶的着急,这个还得要花时间
// 版本二
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
            dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
        }
        return dp[(len - 1) % 2][1];
    }
};
  1. 序列模块
    在这里插入图片描述
  2. 其他模块
    11`

单调栈

单调栈对栈的运用,单调即栈内元素单调,如果不单调则进行一系列操作

1.每日温度

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        //单调增栈
        stack<int> st;
        vector<int> result(T.size(),0);
        st.push(0);
        for (int i = 1; i < T.size(); i++) {
            if (T[i] < T[st.top()]) {
                st.push(i);
            } else if (T[i] == T[st.top()]) {
                st.push(i);
            } else {
                while(!st.empty() && T[i] > T[st.top()]) {
                    result[st.top()] = i - st.top();
                    st.pop();
                }
                st.push(i);
            }
        }
        return result;
    }
};

遇到不相同的便弹出并遍历后面的直到下一个大于他的元素


憧憬

  1. 成为大佬
  2. 拿下理想offer
  3. 技术之上,拿下计划软件

Tips

努力方得积累, 有想法才有希望,有希望就得更加拼

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值