[leetcode]第一遍+第二遍复习

第一遍复习时间:08-04
第二遍复习时间:09-20

文章目录

  • 58 最后一个单词的长度
  • 771 宝石和石头
  • 387 字符串中第一个唯一字符
  • 8 字符串中的整数
  • 14最长公共前缀
  • 344 反转字符串
  • 541 反转字符串2
  • 151 翻转字符串的单词
  • 242 有效的字母异位词
  • 438 找到字符串中所有字母异位词
  • 125验证回文串
  • 680 验证回文串2
  • 后面开始 dp动态规划问题了
  • 5 最长回文子串
  • 647 回文子串
  • 517 最长回文子序列
  • 1143 最长公共子序列
  • 72 编辑距离
  • 10正则表达式(这个耶待补充 好吧)
  • 115不同子序列(困难)
  • 44 通配符
  • 746 使用最小花费爬楼梯
  • 53 最大子序和
  • 917 仅仅翻转字母
  • 205 同构字符串
  • 300最长递增子序列
  • 91 解码方法
  • 下面开始是排序问题-------------------------
  • 242 有效的字母异位词
  • 1122 数组的相对排序
  • 56 合并区间
  • 493 翻转对(跳过了)
  • 146 LRU缓存机制(中等)
  • 191 位1的个数
  • 231 2的幂
  • 190 颠倒二进制位
  • 338比特位计数
  • 51 N皇后
  • 先看二分查找了 刚做的dp再做好嘞
  • 69 x的平方根
  • 367 有效的完全平方根
  • 33 搜索旋转排序数组
  • 74 搜索二维矩阵(中等)
  • 153搜寻旋转排序数组中的最小值
  • 62不同路径
  • 63 不同路径2
  • 120 三角形最小路径和
  • 198 打家劫舍
  • 121 买卖股票的最佳时机
  • 122 买卖股票的最佳时机2(可以多次买入买出)
  • 123买卖股票的最佳时机3
  • 188买卖股票的最佳时机4
  • 714 买卖股票的最佳时机含手续费
  • 309 最佳买卖股票含冷冻器(待补充)
  • 字典树
  • 208 实现trie(前缀树)
  • 并查集和字典其他两题好像举的例子不是很好 回头看看
  • 下面开始就是贪心算法了
  • 322零钱兑换(动态和贪心)
  • 455 分发饼干
  • 860 柠檬水找零
  • 874 模拟行走机器人
  • 55跳跃游戏
  • 45 跳跃游戏2
  • 下面开始复习二叉树和递归 因为二叉树很多递归的题目
  • 94 二叉树的中序遍历
  • 144二叉树的前序遍历
  • N叉树的后序遍历
  • 589 N叉树的前序遍历
  • 429 N叉树的层次遍历
  • 22括号生成
  • 226翻转二叉树
  • 98验证二叉搜索树
  • 104二叉树的最大深度
  • 111二叉树的最小深度
  • 236 二叉树的最近共同祖先(重)
  • 补充!!!!
  • 105 从前序和中序遍历序列构建二叉树
  • 77组合
  • 46 全排序
  • 补充
  • 78 子集
  • 50 pow(x,n)(重)
  • 169 多数元素
  • 17 电话号码的字母组合
  • 51N皇后问题(待补充)
  • 515 在每行树行中找最大值
  • 127 单词接龙
  • 433 最小基因变化
  • 126单词接龙 升级版本(待补充)
  • 200 岛屿的数量
  • 460 岛屿的周长(补充的题目)
  • 695 岛屿的最大面积(补充的题目)
  • 49 字母异位词分组
  • 283 移动零
  • 11 盛最多水的容器
  • 70 爬楼梯
  • 15 三数之和
  • 26 删除排序数组的重复项
  • 189 旋转数组
  • 88 合并两个有序数组
  • 1两数之和
  • 66 加一
  • 接下来就是复习链表
  • 206 反转链表
  • 141 环形链表
  • 24两两交换链表中的节点
  • 142 环形链表2 (数学)
  • 20 有效的括号
  • 155最小栈
  • 84 柱状图中最大的矩形
  • 42 接雨水(补充 高频)
  • 739 每日温度(补充 单调栈- 第二遍复习
  • 496 下一个更大元素1
  • 503 下一个更大元素2
  • 239 滑动窗口的最大值

58 最后一个单词的长度

错误while()那句话

  • 第二遍复习补充一种写法
class Solution {
public:
    int lengthOfLastWord(string s) 
    {
        int i =s.size()-1;
        int target=0;
        while(i>=0 && s[i]==' ') i--;
        while(i>=0 && s[i]!=' ') 
        {
            target++;
            i--;
        }
         return target;

    }
};

771 宝石和石头

  • 第二遍复习
      • 判断 是否在另外一堆 很容易想到hash

387 字符串中第一个唯一字符

  • 这个有一个想法上错误就是我想的是便利map但是实际上我们遍历字符串 每次在map中找它的值是不是==1就好了

  • 第二遍复习

    • 一遍过

8 字符串中的整数

还是步骤没有记住清楚

  • 第一步一定是while取出前置空格

  • 第二部是如果不是正负号不是数字 是字母 直接返回

  • 第三部就是如果是负号设置标志否则为正数

  • 第四步就是如果是字母要 i++ 跳过

  • 第五部 开始处理数字 while遍历知道超过或者遇到非数字

    • res=res*10+当前数字 且乘上符号 放入判断是不是最大值 是的化就要返回,否则等待循环结束 直接返回最终结果
  • 第二遍复习

14最长公共前缀

  • 补充一种巧妙的解法,就是先对所有的数组进行排序,最后一个和第一个差异性肯定是最大的,我们只需要比对第一个和最后一个就好了
class Solution {
public:
    string longestCommonPrefix(vector<string>& word) {
        int len = word.size();
        if (!len) return "";
        // 只需要比较最大和最小的公共前缀就是整个数组的公共前缀
        vector<string> temp = word;
        sort(temp.begin(), temp.end());

        string max = temp[len - 1], min = temp[0];
        string res = "";
        for (int i = 0;i < max.size() && i < min.size();i++){
            if (max[i] == min[i])
                res += max[i];
            else break;
        }
        return res;
    }
};

作者:neaya
链接:https://leetcode-cn.com/problems/longest-common-prefix/solution/qiao-jie-by-neaya-qbrl/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 第二遍复习,没有想到这个方法,时间复杂度0(nlogn+m)

344 反转字符串

  • 循环 双指针 一个从头遍历 一个从尾遍历
  • 第二遍复习
    • 没什么问题,注意第二个for循环的写法

541 反转字符串2

[leetcode]第一遍+第二遍复习_第1张图片

  • 写错了 首先是for循环遍历
  • 在for循环内只有两种情况 剩余长度小于k的全部反转 第二种情况就是长度处于k -2k之间 翻转k个
  • reverse(s.begin() + i, s.begin() + s.size()); 和 reverse(s.begin() + i, s.end())); 这两个一样
class Solution {
public:
    string reverseStr(string s, int k) {
        for (int i = 0; i < s.size(); i += (2 * k)) {
            // 1. 每隔 2k 个字符的前 k 个字符进行反转
            // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
            if (i + k <= s.size()) {
                reverse(s.begin() + i, s.begin() + i + k );
                continue;
            }
            // 3. 剩余字符少于 k 个,则将剩余字符全部反转。
            reverse(s.begin() + i, s.begin() + s.size());
        }
        return s;
    }
};

  • 第二遍复习:按照自己的方法写的,先处理完整的2k部分 其余的退出for循环再处理
class Solution {
public:
    string reverseStr(string s, int k) 
    {
        int i;
        for(i=0;i+2*k<s.size();i=i+2*k)
        {
            reverse(s.begin()+i,s.begin()+i+k);
        }
        if(i+k < s.size()) reverse(s.begin()+i,s.begin()+i+k);
        else
        {
            reverse(s.begin()+i,s.end());
        }
        return s;

    }
};

151 翻转字符串的单词

  • 就是整体翻转 再局部翻转

  • 第一步 while 避开空白点

  • 第二步 定义j=i 遇到字母移动j

  • 第三步 通过i和j翻转单词

  • 第四步 单词前移 需要变量k

  • 第五步 单词后补零

  • 第六步 遍历结束 通过k我们知道实际字母的具体 删除后面的所有字母 忘记了 还有记得-1 最后一个不同补0

  • 其实这个题最重要的是知道i++ 到底表示的是什么 空白处的下一个 我们要特别清楚

  • 第二遍复习: 整体想法还是简单,大的翻转,再小的翻转,就是要注意一些细节的处理

    • 问题一:外层for循环改成while也可以
    • 问题二:内层while判断 ++ – 要加上范围判断
    • 三个变量 前面 i给j初始化 后面 j给i初始化

242 有效的字母异位词

  • 这个题倒是没什么

  • 需要注意有两种方法,或者说三种方法

    • 一个map 两个for循环 一个用来+ 一个用来-
    • 两个map 两个for循环分别放进去 最后比较是不是一样的
    • 第三个就是sort排序 最后比较
  • 第二遍复习:

    • 第一遍题解有问题,map不能比较 要用数组。想到hash,就要想到数组。
    • 补充一个写法,之前的有问题
class Solution {
public:
    bool isAnagram(string s, string t) 
    {
        if(s.length()!=t.length()) return false;
        vector<int> vec(26,0);
        for(int i =0;i<s.length();i++)
        {
            vec[s[i]-'a']++;
            vec[t[i]-'a']--;
            
        }
        for(int i =0;i<26;i++)
        {
            if(vec[i]!=0) return false;
        }
        return true;
    }
};

438 找到字符串中所有字母异位词

  • 这个是比较经典的做法 第一次复习还是想的起来的 滑动窗口 创建两个map 或者数组 一个用来比较 一个每次添加一个 去掉一个
  • 修改了一下代码 最后一个改成了for循环了,就是记得在开始 定义好l和r
  • 还有就是滑动窗口一开始放入2个。其实放三个也行 就是l和r要修改 后面的顺序要变
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
       vector<int> ans;
       if(p.size()>s.size()||s.size()==0) return ans;
       vector<int> need(26);//这个是用来比较的
       vector<int> windows(26);//这个是用来放入取出的
        for(int i=0;i<p.size();i++)
        {
            need[p[i]-'a']++;
        }
        for(int i=0;i<p.size()-1;i++)
        {
            windows[s[i]-'a']++;

        }
        int l=0,r=p.size()-1;//起始点下标
        for(l,r;r<s.size();r++,l++)
        {
            windows[s[r]-'a']++;
            if(windows==need) ans.push_back(l);
            windows[s[l]-'a']--;
        }
       return ans;
    }
};
  • 第二遍复习
    • 中等题
    • 如果能想到滑动窗口+map那就简单
    • 重新写了一遍,和之前没什么区别,就是最后一个循环定义一个l和r边界会方便一点,同时增加,固定增加 for循环。

125验证回文串

  • 一开始的for循环如果不好搞 就用while循环就好

  • 题目要求只考虑字母和数字字符 那就说明你不能==‘ ’ 来跳过了 需要用到isalnum

  • 还有这种跳过什么字母的 我们在每一个判断都在l《r 这种东西

  • 还有就是题目没说只有小写 大小写默认一样 那就tolower 或者 toupper转成一样的

  • 第二遍复习

    • 主要是注意一些函数的使用

680 验证回文串2

  • 这个也简单 就是分成两种情况 继续迭代 结果返回 ||

  • 第二遍复习 换一种写法,挺容易写错的

class Solution {
public:
    bool validPalindrome(string s) 
    {
        if(s.empty()||s.size()==1)
        return true;
        int size=s.size();
        int l=0;
        int r=size-1;
        for(l,r;l<=r;l++,r--)
        {
            if(s[l]!=s[r]) break;
            else continue;
        }
        int flat = true ;
        int flat_ =true;
        for(int l_new=l+1,r_new=r;l_new<=r_new;l_new++,r_new--)
        {   
            if(s[l_new]!=s[r_new])
            {
                flat=false;
                break;
            }
            
        }
        for(int l_new=l,r_new=r-1;l_new<=r_new;l_new++,r_new--)
        {   
            if(s[l_new]!=s[r_new])
            {
                flat_=false;
                break;
            }
        }
        return flat||flat_;
    }
};

后面开始 dp动态规划问题了

  • 还是那句话 方案 最长 最短这种都可以往dp上面考虑

5 最长回文子串

  • 第二次看到 第一个想到的,两种 :这个长度的字符串的最长回文子串,以这个为结尾的回文子串。 还是第二种靠谱。看了眼题解,跟我想的都不一样,dp【】【】 代表的分别是两个坐标。

  • 主要是他往回文子串怎么判断上面想了,我们可以从最短的长度 每次增加左右两个 判断是不是回文子串 数组内放的判断 。

  • 外层循环控制长度 内层循环控制的是左边的坐标 巧妙的方法

  • 代码写的比较成功 就是题目要求返回子串 要用到substr(起始点坐标 ,长度) 我们在遍历的时候遇到true记得随时保存

  • 第二遍复习

    • 第一个想到下标为长度肯定不行,下标为解决,就算你增加一个你也和dp【i-1】联系不上。所以想想能不能一次性增加两个 dp【】【】变成二维的。
    • 很久没做动态规划,少考虑了 i和j之间差距为1的可能性,这个可以当做初始化,也可以作为新的递推公式。
    • 换了一种写法,但是对比之前的,还是之前的好些。
    • 第二个代码为简洁代码复习一下步骤
      • 如果s[i]!=s[j]为一种情况
      • 如果相等 分三种情况考虑,长度为1 长度为2 长度大于2
class Solution {
public:
    string longestPalindrome(string s) 
    {
        int n=s.size();
        //特判
        if(n<2)
        {
            return s;
        }
        int maxlen=1;//
        int begin=0;
        vector<vector<int>> dp(n,vector<int>(n));
        //确定dp的含义和下标的含义
        //确定递推公式
        //初始化
        for(int i=0;i<n;i++)
        {
            dp[i][i]=true;
        }
        for(int l=2;l<=n;l++)//遍历长度
        {
            for(int i=0;i<n;i++)
            {
                int j= i+l-1;
                if(j<=n-1)
                {
                    if(j-i>1) dp[i][j]=dp[i+1][j-1] && s[i]==s[j];
                    else dp[i][j]=s[i]==s[j];
                    if(dp[i][j]==true && j-i+1>maxlen) 
                    {
                        maxlen=j-i+1;
                        begin=i;    
                    }
                }
                else
                {
                    break;//这边可以提前退出循环
                }
            }
        }
        return s.substr(begin,maxlen);
    }
};
class Solution {
public:
    string longestPalindrome(string s) {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        int maxlenth = 0;
        int left = 0;
        int right = 0;
        for (int i = s.size() - 1; i >= 0; i--) {
            for (int j = i; j < s.size(); j++) {
                if (s[i] == s[j]) {
                    if (j - i <= 1) { // 情况一 和 情况二
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // 情况三
                        dp[i][j] = true;
                    }
                }
                if (dp[i][j] && j - i + 1 > maxlenth) {
                    maxlenth = j - i + 1;
                    left = i;
                    right = j;
                }
            }

        }
        return s.substr(left, right - left + 1);
    }
};


647 回文子串

  • 这个题和上面的一样,不用全部返回 统计个数 很简单
  • 和上面一样

517 最长回文子序列

  • 区别一下回文子序列 回文子串的区别

  • 就是要求可以删除部分字母 不改变相对位置找到最长的

  • 思考一下沿用上面的思想 可不可取

  • 怎么说了 当左右两边相等 毫无疑问+2

  • 当左右两边不想等 就是max() 每次放入一个,怎么理解呢 我们两个不能匹配 喷别看看能不能和里面的人匹配 反正长度肯定是比我当前小 那肯定一定已知了

  • 然后就是考虑边界问题了

  • 初始化肯定 斜线那一排肯定是1

  • 如果长度=2 不想等那就是1(不对 好像也不用特判 max 求的的结果一样)

  • 注意最后返回结果就好

  • 第二遍复习

    • 挺久没做,差点递推公式都忘记了
    • 注意到一个点 就是如果s[]==s[]的情况,不在判断长度了,例如dp【3】【2】初始化为0,要不要分情况写都一样,但是我还是分了,如下
class Solution {
public:
    int longestPalindromeSubseq(string s) 
    {
        if(s.size()<=1) return s.size();
        int n = s.size();
        vector<vector<int>> dp(n,vector<int>(n));
        //初始化
        for(int i=0;i<n;i++)
        {
            dp[i][i]=1;            
        }
       
        //确定递推公式 就是确定一共几种情况
        for(int l=2;l<=n;l++)
        {
            for(int i=0;i<n;i++)
            {
                int j=i+l-1;
                if(j >= n) break;
                if (s[i] != s[j])
                {
                    dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
                }
                else 
                {
                    if(j-i==1)
                    {
                        dp[i][j] = 2;
                    }
                    else
                    {
                       dp[i][j]=dp[i + 1][j - 1]+2;
                    }
                    
                    
      
                }

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

    }
};

1143 最长公共子序列

  • 最长 毫无以为 bp了

  • 没啥思路 看了两分钟

  • 这个要记住两个比对

  • dp[i][j]=dp[i−1][j−1]+1, 当 text1[i - 1] == text2[j - 1];text1[i−1]==text2[j−1];

  • dp[i][j]=max(dp[i−1][j],dp[i][j−1]), 当 text1[i - 1] != text2[j - 1]text1[i−1]!=text2[j−1]

  • 怎么理解两个不想等的情况,我记得做了很多次了,就是我们两个元素不匹配,我去看看能不能和你之前的匹配上(不考虑你了),都是这样。然后我之前的有结果了

  • 还有一个考虑的就是习惯了第一个for是长度,他这个正常的两个for,然后我想那怎么每次取两个 其实是a取一个 b已经更换了一遍了 就是每次最后两个元素比较 懂吧

  • 在想初始化出现了问题 比如 a ab dp【0】【1】=max(dp【-1】【1】,dp【0】【0】,而且这种出现0的也不好特判啊 比如 ab a |cb e 也分可能性的 总不能把长的每一个元素比对把,这时候我们用长度表示下标 空间m+1 n+1 并且i 和 j就可以从1开始 初始化全为0 就可以把那些长度为0的初始化为0

  • 特别注意i 和 j从1 开始太经典了

  • 子序列的太喜欢 max了 记住了

  • 第二遍复习

    • 如果最后一个相等,那就是dp【i-1】【j-1】+1
    • 如果最后一个不想等,dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 不难想,各自删掉一个再看看 和最长子序列一样。
    • 要点:分别考虑 下标表示坐标和长度的情况 明显后者比较简单。如果其中一种复杂就去考虑另外一种。
    • 重写了代码,有时候一定要学会初始化放在递归里面
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) 
    {
       //如果下标表示实际坐标
       //情况一 如果相等 那就dp【】【】+1

       //情况二 如果不想等 那就 max dp【i-1】【】 dp【】【j-1】
       //考虑初始化 dp【0】【..】 和 dp【...】【0】 也不是不能初始化 就是两个for循环(一点点麻烦)
       //如果下标表示长度  长度从1开始 初始化带0的都为0 确实方便了   
        int m=text1.size();
        int n=text2.size();
        vector<vector<int>> dp(m+1,vector<int>(n+1,0)) ;
        for(int i=0;i<=m;i++)
        {
            for(int j=0;j<=n;j++)
            {
                if(i==0 || j==0)
                {
                    dp[i][j]=0;
                    continue;
                } 
                if(text1[i-1]==text2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
                else
                {
                    dp[i][j]= max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }

};

72 编辑距离

  • 老样子 一开始看有点没思路 ,但是有点印象就简单了
  • 按照正常 还是两个for循环 然后 比较最后两个单词 如果一样 肯定简单 啥都不变 对吧
  • 如果不一样,走出误区(不是让你选择哪一个能够成功 而是每一个都可以达到目的 就是哪一个步数最少懂吧)
  • 还有就是不要用下标 你自己看看会超过 老样子 i j 从1 开始 0 初始化,这边初始化比较特别 看看
  • dp 值表示 到目前这个长度需要的步数
  • 第二遍复习

10正则表达式(这个耶待补充 好吧)

115不同子序列(困难)

  • 第二遍复习
    • 这个题有点难想想,dp表示以长度分别为i和j能匹配上的子序列个数。下标表示长度
    • 递推公式(固定的套路,大情况 都是分为2种 一个是最后一个字母相匹配 )
    • 二者相等:dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]。如果是最后一个匹配那就抵消 如果不匹配 那就是t最后一个想和前面的匹配 那就直接把s最后一个字母删除。
    • 二者不想等 那就是s最后一个没用 直接删除
    • 注意代码的初始化
class Solution {
public:
    int numDistinct(string s, string t) {
        vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));
        for (int i = 0; i < s.size(); i++) dp[i][0] = 1;
        for (int j = 1; j < t.size(); j++) dp[0][j] = 0;
        for (int i = 1; i <= s.size(); i++) {
            for (int j = 1; j <= t.size(); j++) {
                if (s[i - 1] == t[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[s.size()][t.size()];
    }
};

44 通配符

  • 可能是刚做没多久的原因,记得比较清楚,最重要的就是代数问题 把一个方程带入了另外一个方程中
  • 这边还是使用长度 因为确实需要考虑长度为0的情况,他这边比较特殊的就是i=0 判断可不可以,如果全是* 那当然可以 一开始我想着全部遍历一遍 判断字母。但是答案写的就比较巧妙,他是初始化一开始都为0 不可以,然后遍历从长度1开始 如果是* 就放入1 如果不是 那我后面的也不判断了 不用开都不是了

746 使用最小花费爬楼梯

  • 这个题也是一个动态规划问题
  • 比较简单 下标表示什么 我是用最容易理解的方式
  • 注意题目的意思 我是要越过那个点 长度+1
  • 第二遍复习
    • 没什么问题,就是dp不同的含义可以写出不同的代码。
    • 最小 楼梯很容易想到动态规划
class Solution {
public:

    int minCostClimbingStairs(vector<int>& cost) 
    {
        //dp[i]=min();
        //dp 表示走到当前这个格子需要的花费
        int n=cost.size();
        vector<int> dp(n);
        dp[0]=cost[0];
        dp[1]=cost[1]; 
        for(int i=2;i<n;i++)
        {
            dp[i]=min(dp[i-1],dp[i-2])+cost[i];
        }
        return min(dp[n-1],dp[n-2]);

    }
};

53 最大子序和

  • 这个题我还是想到了 就是下标表示以当前坐标为结尾的子序列最大值,你想想是不是就两种情况 一个和之前那一段接起来 要么重新开始 单独一个 。max取大不就好了

  • 唯一要注意的就是我们是在所有dp中找最大值 所以可以再遍历的时候顺便比较 不断更新最大值

  • 第二遍复习

    • 最大
    • 以当前为结尾,下标
    • dp[i]=max(dp[i-1]+nums[i],nums[i])
    • 一遍过
class Solution {
public:
    int maxSubArray(vector<int>& nums) 
    {
        if(nums.size()==0) return 0;  
        int n=nums.size();
        int max_=nums[0];
        vector<int> dp(n);
        dp[0]=nums[0];
        for(int i=1;i<n;i++)
        {
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            max_=max(max_,dp[i]);
        }
        return max_;
    }
};

917 仅仅翻转字母

  • 这个简单题 然我想到了一开始做的时候无从下手 其实没什么差别好吧 遇到非字母的++ --就好了,还有就是跳过啥玩意 用while

  • 第二遍复习

    • 第一眼想到了那个判断回文子串,跳过不是字母和数字的部分 那不就一样么。
    • 老样子,while嵌套的两个while找到第一个符合的,进行交换就好了。他这个只是跳过字母,不能用isalnum
    • 注意一:交换需要加if判断 防止是越界跳出
    • 注意二:可以三个while 也可以 for +二个while 双指针
class Solution {
public:
    string reverseOnlyLetters(string S) 
    {
        int n=S.size();
        int l=0;
        int r=n-1;
        while(l<r)
        {
            while(!((S[l]>='a'&&S[l]<='z')|(S[l]>='A'&&S[l]<='Z')) && l<r)
            {
                l++;
            }
              while(!((S[r]>='a'&&S[r]<='z')|(S[r]>='A'&&S[r]<='Z')) && l<r)
            {
                r--;
            }
        
            if(l<r)
            {
                swap(S[l],S[r]);
                l++;
                r--;
            }
    }
      return S;
}
};

205 同构字符串

  • 双向映射 当初好像不完全自己写的 今天补上
  • 补上一个简单的做法,什么是同构假设 第一个字符串中的a就相当于第二个字符串中b那就意味中他们出现的下标一致,那是不是可以用下面这种方法了
  • 第一种写法 忘记咋写了 记的 两个map 记得插入前要查询有没有 没有不插入
class Solution {
public:
    bool isIsomorphic(string s, string t) {
        for(int i = 0; i < s.size(); ++i)
            if(s.find(s[i]) != t.find(t[i]))
                return false;
        return true;
    }
}
  • 第二遍复习
    • 忘记了巧妙做法
    • 双向映射,就好像构建两本字典如果之前count ==0没有记载 那就记录,并且每次都去查字典 看看是不是符合的。两个字典都符合那就返回。
    • 挺简单的 就是容易写错
class Solution {
public:
    bool isIsomorphic(string s, string t) 
    {
        if(s.length()!=t.length()) return false;
        unordered_map<char,char> map1;
        unordered_map<char,char> map2;
        for(int i=0;i<s.size();i++)
        {
            if(map1.count(s[i])==0) map1[s[i]]=t[i];
            if(map2.count(t[i])==0) map2[t[i]]=s[i];
            if(map1[s[i]]!=t[i] || map2[t[i]]!=s[i]) return false;

        }
        return true;

    }
};

300最长递增子序列

  • 其实我还是看的懂的 dp【i】= max(dp[j]+1) && num[i] >num[j]
  • dp表示以当前元素为结尾的最长子序列 ,我最后 那肯定要找比我小的对吧
  • 还是比较少见的 一个字符串两个for循环来解决的 少见
  • 还算比较简单 ,中等题 修改了下返回值那边 用比较常见的方法 不是原本 *max_element
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n= nums.size();
        vector<int> dp(n,1);//最短都是1 不用想都知道
        int max_=1;
        for(int i = 0 ; i < n ; i++)
        {
            for(int j=0;j<i;j++)
            {
                if(nums[i]>nums[j])
                {
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
            max_=max(max_,dp[i]);
        }
        return max_;
    }
};
  • 第二遍复习
    • 最长+数组 很容易想到动态规划
    • dp表示当前长度 还是以当前结尾。
    • 比较容易想到有一点不一样,当前结尾如果大于前一个值那么dp【i】=dp【i-1】+1;也简单,但是如果小于就和之前不一样了 就要找到第一个小于的 这边应该是要一个for循环(上面的分析 完全错了)
    • 正确如下
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        int n =nums.size();
        if(n<=1) return n;
        vector<int> dp(n,1);//全部初始化为1
        dp[0]=1;
        //
        int result=1;
        for(int i=1;i<n;i++)
        {
            
            for(int j=0;j<i;j++)
            {
                if(nums[i]>nums[j])
                {
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
            result=max(result,dp[i]);
        }
        return result;
    }
};

91 解码方法

  • 这个人情况分析有点困难 主要就是最后一个是0 倒数第二个是1 和倒数第二个是2(一) 和倒数第二个为2(二)四种情况
  • 对了 非常容易错的是 if if if 和 if if else else 差别很大 注意的 有重复的可能
  • 下标表示长度 他假设长度为0 是一种情况

下面第一个放了一个好理解的版本 可以学习

class Solution {
public:
    int numDecodings(string s) {
        if (s[0] == '0') return 0;
        vector<int> dp(s.size()+1);//
        dp[0]=1;dp[1]=1;
        for (int i =2; i <= s.size(); i++) {
            if (s[i-1] == '0')//1.s[i]为0的情况
                if (s[i - 2] == '1' || s[i - 2] == '2') //s[i - 1]等于1或2的情况
                    dp[i] = dp[i-2];//由于s[1]指第二个下标,对应为dp[2],所以dp的下标要比s大1,故为dp[i+1]
                else 
                    return 0;
            else //2.s[i]不为0的情况
                if (s[i - 2] == '1' || (s[i - 2] == '2' && s[i-1] <= '6'))//s[i-1]s[i]两位数要小于26的情况
                    dp[i] = dp[i-1]+dp[i-2];
                else//其他情况
                    dp[i] = dp[i-1];
        }
        return dp[s.size()];
    }
};
class Solution {
public:
int numDecodings(string s) {
    if (s[0] == '0') return 0;
    int pre = 1, curr = 1;//dp[-1] = dp[0] = 1
    for (int i = 1; i < s.size(); i++) {
        int tmp = curr;
        if (s[i] == '0')
            if (s[i - 1] == '1' || s[i - 1] == '2') curr = pre;
            else return 0;
        else if (s[i - 1] == '1' || (s[i - 1] == '2' && s[i] >= '1' && s[i] <= '6'))
            curr = curr + pre;
        pre = tmp;
    }
    return curr;
}
};
  • 第二次复习

    • dp 表示方案数 i表示正常的下标
    • 分情况(第一想法写的)
      • 如果倒数第二个数大于2 那最后一个数固定只有一种解法 那么就取决于dp[i-1]
      • 如果倒数第二个数等于2 如果是1到6那就两种解法 如果大于6那就和第一种一样 固定最后一个的解法
      • 如果倒数第二个数等于1 那就两种组合解法
      • 如果遇到0可能还要分情况
    • 标准解法(2+3 (以最后一个是不是0分成两个部分))
      • 最后一个为0(if else) 如果最后一个不为0(倒数第二个为1,倒数第二个为2 并且倒数第一个在 1~6 和其他情况)
  • 一开始 我让下标表示真实的下标 那么必然就要确定dp【0】 和 dp【1】 但是此时 dp【1】就需要分很多种情况进行讨论 所以 更换下标含义为长度

  • 重写了代码,没啥问题 基本一次过 就是注意判断的是‘ 0’ 字符,还有一开始的特判

class Solution {
public:
    int numDecodings(string s) 
    {
        //特判 
        if(s[0]=='0') return 0;
        int n=s.size();
        vector<int> dp(n+1);//
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++)
        {
            if(s[i-1]=='0')
            {
                if(s[i-2]=='1' || s[i-2]=='2')
                {
                    dp[i]=dp[i-2];//最后两个解法固定了 解法数量取决与前面了
                }
                else
                {
                    return 0;
                }
            }
            else//最后一个不为0
            {
                if(s[i-2]=='1')//固定两种解法
                {
                    dp[i]=dp[i-1]+dp[i-2];
                }
                else if(s[i-2]=='2' && s[i-1]<='6')
                {
                    dp[i]=dp[i-1]+dp[i-2];
                }
                else 
                {
                    dp[i]=dp[i-1];
                }
      
            }
        }
        return dp[n];

    }
};

下面开始是排序问题-------------------------

242 有效的字母异位词

  • 太简单了,两种方法 我都不想废话了
  • 第二遍复习
    • 确实很容易想到两种方法
    • 第一种就是map边+边-
    • 第二种就是排序

1122 数组的相对排序

  • 也是一个计数排序,第一眼看好像不是,其实认真看 真的是,稍稍变动一下,按照指定的顺序,那我们就按照那个顺序找到数组输出不就好了,多加一个循环 题目还要求把没有考虑到的输出 这个就正常输出就好了

  • 记得用while来取出数

  • 第二遍复习

    • 最先想到的肯定是暴力解法
    • 如果想到的相对排序
    • 代码写的过程就一个地方错了,-- 太容易忘记了
class Solution {
public:
    vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) 
    {
        vector<int> vec(1001,0);
        vector<int> result;//用来存放结果的
        for(int i=0;i<arr1.size();i++)
        {
            vec[arr1[i]]++;
        }
        for(int i=0;i<arr2.size();i++)
        {
            while(vec[arr2[i]]!=0) 
            {
                result.push_back(arr2[i]);
                vec[arr2[i]]--;
            }
        }
        for(int i =0;i<1001;i++)
        {
            while(vec[i]!=0)
            {
                result.push_back(i);
                vec[i]--;
            }
        }
        return result;
    }
};

56 合并区间

  • 重新温习一下,他这个左边界是不断变化的 所以要取出设置为temp,不断更新左边界。 如果不满足要求退出,重新设置下一个左右边界,所以需要两层循环,外层循环

  • 有一个时刻需要注意的就是,下一次的右边界是上一次左边界的下一个所以如果用for循环 +while循环 外层for循环 i不要++ 切记

  • 第二遍复习

    • 第一样看到这种一对数的 很容易想到之前做的类似的 先排个序再来考虑
    • 我的想法很简单,保存第一个数对的第一个为start 如果第二个的第一个数在上一个数的对的中间 那就合并 max 两个数对的第二个数。不在那就…
    • 按照自己的想法 重写了代码 之前写的代码 现在写的更清楚。初始化start 和
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) 
    {
        vector<vector<int>> ans;
        sort(intervals.begin(),intervals.end());
        int n= intervals.size();
        int start=intervals[0][0];
        int end =intervals[0][1];
        for(int i=1;i<n;i++)
        {
           if(intervals[i][0]<=end)
           {
               end=max(end,intervals[i][1]);//更新一下范围
           }
           else
           {
               ans.push_back({start,end});
               start=intervals[i][0];
               end=intervals[i][1];
           }
            
        }
        ans.push_back({start,end});//最后一次忘记存储了
        return  ans;

    }
};

493 翻转对(跳过了)

146 LRU缓存机制(中等)

  • 第二遍复习
    • 复习了一下 确实可以一遍写出
    • 很多细节的处理,例如
      • 节点中为什么有key的值
      • 为什么定义链表的插入和删除函数
      • 在LRU需要四个成员变量 并且初始化
      • 注意 put函数内部多个ifelse 首先是存不存在 然后才是容量满没满 只有这里才是真正删除节点 释放空间
      • 不要忘记及时从map删除节点 很容易错
class Node
{
    public:
    int value;
    int key;//本来没有这个的 为了方便删除尾巴节点 得到key 从map也删除这个
    Node* right;
    Node* left;
    Node(int key_,int value_)
    {
        key=key_;
        value=value_;
    }

};

class LRUCache {
public:
    //如果我要在这个类的函数实现功能 是不是把需要调用的设为成员 方便使用呢 对吧
    //四个成员
    Node* L;//空白头结点方便插入 有点想岗哨
    Node* R;//尾巴 是容量慢了 方便从尾巴删除
    int n;//表示容量
    unordered_map<int,Node*> hash;//初始化一个hash表
    LRUCache(int capacity) 
    {
        n = capacity;
        L = new Node(-1,-1),R = new Node(-1,-1);//不插入hash
        L->right=R;//串起来
        R->left=L;
    }
    //链表节点的删除操作,并不是真的释放空间 特别注意
    void remove(Node* p)//传入需要删除的节点
    {
        p->left->right=p->right;//断开指向自己的 指向我后面的
        p->right->left=p->left;//同理
    }
    void insert(Node* p)//传入需要插入的节点
    {
        //先来处理L右边的 
        //箭头指出去的在左边  a=b 也可以叫上a挂上b
        p->right=L->right;
        L->right->left=p;

        L->right=p;
        p->left=L;
    }
    //作用:通过key 得到元素的value
    //实际事情:map查找节点  删除节点关系 插入(现在的关系)(get 无需考虑容量 put才要)
    int get(int key) 
    {
        //查询 首先在map查询
        if(hash.count(key)==1)
        {
            auto p= hash[key];//取出节点
            remove(p);
            insert(p);
            return p->value;
        }
        return -1;
    }
    //作用放入:存在则变更value 不存在就插在头
    //实际:1 存在 修改后 删除 插入
     //2 不存在 容量慢了 容量没满
    void put(int key, int value) 
    {
        if(hash.count(key)==1)//存在 
        {
            auto p=hash[key];
            p->value = value;//修改
            remove(p);
            insert(p);
        }
        else
        {
             if(hash.size() == n) //如果缓存已满,则删除双链表最右侧的节点
             {
                 auto p= R->left;
                 remove(p);
                 hash.erase(p->key); //更新哈希表,这个很容易忘记
                 delete p;//这个也很容易忘记
             }
             //处理好了 插入新节点
            auto p=new Node(key,value);
            hash[key] = p;//两个 不要忘记了
            insert(p);
        }
        
    }
};

/**
 * 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);
 */

191 位1的个数

  • 肯定忘记了 这次复习想起来了
  • 我觉的掌握 & | ^ 三种的含义 还有10进制和2进制的转换
  • 常见的x&(x-1) 清除最低位的1
  • x&-x 得到最低位的1
  • 判断奇偶 x&1 == 1 就是奇数 ==0就是偶数 这个比求余数判断更快
  • 第二遍复习
    • 没什么问题

231 2的幂

  • 这个题也是利用 x &(x-1)看看是不是消除一次 就是0了 怎么说呢 没做过还真不一定想的起来

  • 第二遍复习

    • 1 2 4 8 16 没啥问题

190 颠倒二进制位

  • 这个非常的巧妙啊
  • 注意几个东西 一个是 n<< = 2 和 n<<2 这两个不一样 一个是右边移动赋值 一个是移动后咩有赋值
  • 我修改了一下写法
  • 记住先移动结果值
  • 再取出放入
  • 再取出放入的值 顺序不能变
class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
       uint32_t ans=0;
       for(int i=0;i<32;++i)
       {
           //这个不能放在后面 否贼会多出一个0少掉一个数
           ans<<=1;//最开始是0 移动等于没有变化
           ans = ans|(n&1);
           n>>=1;//理解一下 去除取出的数字 几个数字就要去除几次
          
       }
       return ans;
    }
};
  • 第二遍复习
    • 没啥问题 就是复习&1 取最后一位 0| a 相当于拷贝上去
class Solution {
public:
    uint32_t reverseBits(uint32_t n) 
    {
        int result=0;
        for(int i=0;i<32;i++)
        {
            result=result<<1;
            result=result|(n&1);
            n=n>>1;
        }
        return result;
        
    }
};

338比特位计数

  • 特别注意 这边 不要用pushback添加数据 如果初始化了大小
  • vector result(n+1);这种方式初始化大小初始值都是0 你如果在用pushback就会多出很多
  • 要么你就用reserve 第二段代码
class Solution {
public:
    int countOnes(int x) {
        int ones = 0;
        while (x > 0) {
            x &= (x - 1);
            ones++;
        }
        return ones;
    }

    vector<int> countBits(int n) {
        vector<int> result(n+1);
        for(int i=0;i<=n;i++)
        {
            //int number = countOnes(n);
            //统计结果
            result[i]=countOnes(i);
            //result.push_back(number);
        }
        return result;

    }
};
class Solution {
public:
    int countOnes(int x) {
        int ones = 0;
        while (x > 0) {
            x &= (x - 1);
            ones++;
        }
        return ones;
    }

    vector<int> countBits(int n) {
        vector<int> bits;
        bits.reserve(n+1);
        for (int i = 0; i <= n; i++) {
            int count = countOnes(i);
            bits.push_back(count);

        }
        return bits;
    }
};


  • 没什么问题

51 N皇后

  • 位运算没咋看

先看二分查找了 刚做的dp再做好嘞

  • 今天状态 不好 先做简单的

69 x的平方根

  • 有几个比较固定的写法 就是 《= mid= 返回值
  • 我做了修改 分成三种情况
class Solution {
public:
    int mySqrt(int x) {
        int l = 0, r = x;
        while (l <= r) 
        {
            int mid =l+(r-l)/2;
            if((long long)mid*mid < x)
            {
                l= mid+1;
            }
            else if((long long)mid*mid > x)
            {
                r=mid-1;
            }
            else
            {
                return mid;
            }
        }
        return r;
    }
};
  • 第二遍复习
    • 第一个想到的是如何立刻反应题目是要二分查找,首先是找一个值 这个值在一个范围内

367 有效的完全平方根

  • 一样的 分成三种情况 好理解
  • 第二遍复习
    • 一遍过
class Solution {
public:
    bool isPerfectSquare(int num) 
    {
       int l=0;
       int r=num;
       while(l<=r)
       {
           int mid = l+(r-l)/2;
           if((long long) mid*mid < num)
           {
               l=mid+1;
           }
           else if((long long) mid*mid > num)
           {
               r=mid-1;
           }
           else
           {
               return true;
           }
       }  
       //非正常退出
       return false;

    }
};

33 搜索旋转排序数组

  • 看到查找 什么的 要想到二分查找
  • 总结起来就一句话 它就是分成四种情况。或者说就是判断一个东西 就是在不在有序的那一段不在就在另外一段 因为一分为二肯定有一段是有序的 一段是无序的 无序的我们判断不了是吧。
  • 有序无序好判断 是不是这样就可以达到一分为二的特点了
  • 记得增加点特判能减少计算量
  • 这边虽然是三段式 但是不一样 我们是 if if else 意义边了 后面if else是为了判断有没有序
  • 还有一个写错的地方是 判断是不是在范围是<=
  • 第二个错误是 if(nums[0]<=nums[mid]) < 会判定错误 举例是 【3,1】 1

第二个错误要特别记住 有序 小于等于 mid 会取到前面的那个数

  • 第二遍复习
    • 前两题都是简单题,这题是中等题
    • 查找判断目标值是否存在 二分查找
    • (r-l)/2+l偏向右边
    • 第一个想法 就是一分为一半有序 一半无序 一定能在有序那一堆找到目标值 因为最坏的情况 分成单独一个元素了 那也是有序
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = (int)nums.size();
        if (!n)//长度为0 
        {
            return -1;
        }
        if (n == 1) //长度为1
        {
           if (nums[0]==target) return 0;
           else -1;
        }
        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])
            {
                if(nums[0] <=target && target <nums[mid])
                {
                    r=mid-1;
                }
                else
                {
                    l=mid+1;
                }
            }
            else
            {
                 if (nums[mid] < target && target <= nums[n - 1])//并且在这个区间
                 {
                    l = mid + 1;
                } else 
                {
                    r = mid - 1;//不在这个区间
                }
            }

            
        }
        return -1;
    }
};
  • 第二遍复习
    • 还是写错了一个地方,有序判断必须<= ,没错思考 记住就好!!!
class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int n = (int)nums.size();
        if (!n)//长度为0 
        {
            return -1;
        }
        if (n == 1) //长度为1
        {
           if (nums[0]==target) return 0;
           else -1;
        }
        int l=0;
        int r=n-1;
        while(l<=r)
        {
            int mid =l+(r-l)/2;
            if(nums[mid] == target) return mid;
            if(nums[0]<=nums[mid])//表示左边有序
            {
                //情况一 在有序范围内
                if(nums[0]<=target && target<nums[mid])
                {
                    r=mid-1;//mid不用包括 一开始判断了
                }
                //情况二 不在有序范围内
                else
                {
                    l=mid+1;
                }
            }
            else//右边有序
            {
                if(nums[mid]<target && target<=nums[n-1])
                {
                    l=mid+1;//mid不用包括 一开始判断了
                }
                //情况二 不在有序范围内
                else
                {
                    r=mid-1;
                }
            }
           
            
        }
         return -1;//退出循环 还没找到 就是没有
    }    
};

74 搜索二维矩阵(中等)

  • 经典二维矩阵转成一维 用 / 和 %
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) { // 二分法,核心思想是把矩阵拉成一条数组。
        // 可行的原因是每一行开头的数值比上一行末尾的大。可以把下一行拼接到上一行,最后将矩阵变成一行。
    int m = matrix.size();
    int n = matrix[0].size();
    int l=0;
    int r=m*n-1;
    while(l<=r)
    {
        int mid = l+(r-l)/2;
        if(target==matrix[mid/n][mid%n]) return true;
        else if(matrix[mid/n][mid%n]>target)
        {
            r=mid-1;
        }
        else
        {
            l=mid+1;
        }
    }
    
     return false;
    }
};
  • 第二遍复习 没什么问题 一遍过
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) 
    {
        if(matrix.size()==0) return false;
        int m=matrix.size();
        int n=matrix[0].size();
        int l=0;
        int r=m*n-1;
        // 11 2 3 11/4=2 11%4=3
        while(l <=r )
        {
            int mid = l+(r-l)/2;
            if(matrix[mid/n][mid%n]==target) return true;
            else if(matrix[mid/n][mid%n] < target)
            {
                l=mid+1;
            }
            else
            {
                r=mid-1;
            }
        }
        return false;
    }
};

153搜寻旋转排序数组中的最小值

看看这个题解 关于边界的判断说的很好

[leetcode]第一遍+第二遍复习_第2张图片

  • 和之前那个一样 最小值一定是在无序序列中
  • 这个题稍微需要点记忆 否则很容易错,就是mid 和mid+1
  • 他首先判断的是有半部分是否有序nums[mid] < nums[r] 有序就 r=mid 。举个例子 7890123 恰好在中间 我们要把它归到另外一边。跳出说明只有一个元素 就是我们要的了

延伸思考一下 最大值怎么着 不就是最小值左边那个 如果是-1 那就是最后一个 代码不变 这样不用再考虑别的情况

  • 第二遍复习
    • 之前想的有一点点问题,这个题为什么是判断右边有序,正常情况下 最小值都在无序的那一边,但是有一种特殊情况,01234567.如何解决呢:正如代码所写我们判断的是左半边是否有序(区别于在旋转数组找target)
    • 旋转数组找target 是判断num[0]<=nums[mid] 左半边并且有等于,本题是右半边并且没有等于
    • r = mid; 注意这边 很容易想到

62不同路径

  • 虽然是中等题 但是自己写一遍 按照动态规划四步骤 一点问题没有。
class Solution {
public:
//dp表示走到当前这个地方需要多少步数 下标表示实际坐标
//递推公式就是 dp【i】【j】=dp[i][j-1]+dp[i-1][j]
//初始化问题 一般其实就是考虑 0的那边 然后遍历从1开始 也确实是这样
    int uniquePaths(int m, int n) {
        //m行 n列
        vector<vector<int>> dp(m,vector<int>(n));
        for(int i=0;i<m;i++)
        {
            dp[i][0]=1;
        }
        for(int i=0;i<n;i++)
        {
            dp[0][i]=1;
        }
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                dp[i][j]=dp[i][j-1]+dp[i-1][j];
            }
        }
        return dp[m-1][n-1];
    }
};
  • 第二遍复习
    • 能很快反应 当前路径从左边和上面值的汇合
    • 不用分多种情况
    • 初始化放在递归里面 节省时间
class Solution {
public:
    int uniquePaths(int m, int n) 
    {
        vector<vector<int>> dp(m,vector<int>(n));
        //初始化 


        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(i==0 || j==0) 
                {
                    dp[i][j]=1;
                    continue;
                }               
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];

    }
};

63 不同路径2

  • 找个要考虑障碍,不就相当于 递推公式多种 根据情况判定么
  • 第一个想法就是 把障碍设置为0 这样貌似可以 写些看、
  • 这个题写错了一个地方就是初始化的地方 就是前面是障碍 后面就都是0了 不用再判断了 自己写第一次错误了
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
       int m= obstacleGrid.size();
       int n= obstacleGrid[0].size();
       vector<vector<int>> dp(m,vector<int>(n,0));//一开始初始化都是障碍
       for(int i=0;i<m;i++)
       {
           if(obstacleGrid[i][0]==0)
           {
               dp[i][0]=1;//不是障碍 放入1
           }
           else
           {
               break;
           }
       }
       for(int i=0;i<n;i++)
       {
            if(obstacleGrid[0][i]==0)
           {
               dp[0][i]=1;//不是障碍 放入1 一种方案
           }
           else
           {
               break;
           }
       }
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                if(obstacleGrid[i][j]!=1)//如果不是障碍 
                {
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
                //如果是障碍 其实不用else 我们要放入0 但是初始化就是0了 不用修改
            }
        }
        return dp[m-1][n-1];
    }
};
  • 第二遍复习
    • 第一个想法就是 如果是1 dp直接置为0 ,注意初始化 前面有障碍 后面都只能dp 0
    • 代码没啥问题,一遍过,就是== 很容易写成= 习惯不好
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) 
    {
        int m = obstacleGrid.size();
        int n= obstacleGrid[0].size();
        vector<vector<int>> dp(m,vector<int>(n,0));//全都初始化为0 
        for(int i=0;i<m;i++)
        {
            if(obstacleGrid[i][0]==0)
            {
                dp[i][0]=1;
            }
            else
            {
                //遇到障碍直接跳出 后面全都是初始化的0
                break;
            }
        }
        for(int i=0;i<n;i++)
        {
            if(obstacleGrid[0][i]==0)
            {
                dp[0][i]=1;
            }
            else
            {
                //遇到障碍直接跳出 后面全都是初始化的0
                break;
            }
        }

        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                if(obstacleGrid[i][j] == 1)//障碍
                {
                    continue;
                }
                else//不是障碍
                {
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }
};

120 三角形最小路径和

  • 写一个自底向上的好理解一点 ,怎么说呢 自底向上就没有那么多边界要处理 要不太麻烦了,当然也不是说不可以,下面的代码还可以优化 初始化可以放在外边,递推方程就只有一个了。 这样简单一点


class Solution {
public:
	int minimumTotal(vector<vector<int>>& triangle) {
		int m= triangle.size();
        int n= triangle.back().size();
        vector<vector<int>> dp(m,vector<int>(n));
        for(int i=m-1;i>=0;i--)
        {
            for(int j=0;j<=i;j++)
            {
                if(i==m-1)
                {
                    dp[i][j]=triangle[i][j];
                }
                else
                {
                    dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j];
                }
            }
        }
        return dp[0][0];

	}
};




  • 我也修正了一下从上到下的写法 就是把边界处理放在前面好理解一点。但是这样时间复杂度有点高 不太行 要把初始化放在内部。就像下面第二个代码 找个要好好学习,就是可以优化的地方
class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int n = triangle.size();
        vector<vector<int>> dp(n, vector<int>(n));
        //初始化
        dp[0][0] = triangle[0][0];
        for(int i=1;i<n;++i)
        {
            dp[i][0]=dp[i-1][0]+triangle[i][0];
        }
        for(int i=1;i<n;++i)
        {
            dp[i][i]=dp[i-1][i-1]+triangle[i][i];
        }
        for (int i = 1; i < n; ++i) {
            for (int j = 1; j < i; ++j) 
            {
                dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j];
            }
        }
        return *min_element(dp[n - 1].begin(), dp[n - 1].end());
    }
};


class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int n = triangle.size();
        vector<vector<int>> f(n, vector<int>(n));
        f[0][0] = triangle[0][0];
        for (int i = 1; i < n; ++i) {
            f[i][0] = f[i - 1][0] + triangle[i][0];
            for (int j = 1; j < i; ++j) {
                f[i][j] = min(f[i - 1][j - 1], f[i - 1][j]) + triangle[i][j];
            }
            f[i][i] = f[i - 1][i - 1] + triangle[i][i];
        }
        return *min_element(f[n - 1].begin(), f[n - 1].end());
    }
};


  • 第二遍复习
    • 很顺利 可以看看自己的分析过程,我把异常作为情况的一种而不是作为初始化的,和第一遍的理解不一样,代码写的很顺利
//好像一样不分情况 然后 最后求一下 最后一行的最小值 
//可能需要判断越界

//也可以分一下情况 正常情况
dp[i][j]=nums[i][j]+min(dp[i-1][j],dp[i-1][j-1]) 
//异常情况 在边界上(当i==j) 我只能
dp[i][j]=nums[i][j]+dp[i-1][j-1];
//异常情况二 (j==0) 在第一排  
dp[i][j]=nums[i][j]+dp[i-1][j];
class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int m = triangle.size();
        vector<vector<int>> dp(m,vector<int>(m));
        //初始化 
        dp[0][0]=triangle[0][0];
        for(int i=1;i<m;i++)//从第二行开始
        {
            for(int j=0;j<=i;j++)
            {
                //情况一 
                if(j==0) 
                {
                    dp[i][j]=triangle[i][j]+dp[i-1][j];
                    continue;
                }
                if(j==i)
                {
                    dp[i][j]=triangle[i][j]+dp[i-1][j-1];
                    continue;
                }
                dp[i][j]=triangle[i][j]+min(dp[i-1][j],dp[i-1][j-1]);
            }
        }
        return *min_element(dp[m - 1].begin(), dp[m - 1].end());
    }    

};


198 打家劫舍

  • 一开始想到 dp可以有两种设定 我们很常见的两种,一个是i表示考虑的房屋数量(最后一间可以不偷) i表示是我偷的最后一件屋子总和最大值。
  • 代码随想录还是一考虑的房间数,然后分了两种情况 当前偷还是不偷 取max,他把情况叫做状态 ,习惯一下 联想一下,对了记得特判一下
  • 然后呢 处理好边界 因为递推公式有i-2 那我们把 i=0 和1 的先处理好
  • 最后考虑空间优化 其实挺简单的 就需要前一个值 和前前个值就好了
  • 注意 如果把dpi 认为最后一个必定偷 然后下面代码是错的 为什么呢 因为可能连续两个都是空的对吧 不好改 要i-3了
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] = nums[1];
        for (int i = 2; i < nums.size(); i++) {
            dp[i] = dp[i - 2]+nums[i];
        }
        return max(dp[nums.size()-1],dp[nums.size()-2]);
    }
};


121 买卖股票的最佳时机

  • 第二次做没啥想法,把这个归为专门的一类问题,就专门记忆一下,每一天有两个状态,0表示持有 1表示不持有。
  • 股票很像我们把一维的转变成二维的 很经典 和后面一个字符串很像(自己和自己比较)
  • 时间复杂度有点拉夸,修改一下,其实分析一下其实只是用到前一天的 0 和前一天的 1 保存这两个就好了
  • 修改如下 ,挺简单的
class Solution {
public:
    int maxProfit(vector<int>& prices) 
    {
        int n= prices.size();
        //vector> dp(n,vector(2));
        int dp_0=-prices[0];
        int dp_1=0;
        for(int i=1;i<n;i++)
        {
            dp_0=max(dp_0,-prices[i]);//表示持有股票
            dp_1=max(dp_1,dp_0+prices[i]);//表示不持有股票
        }
        return dp_1;
    }
};
  • 第二遍复习
    • 没什么问题 就是要固定 0表示持有1表示不持有,分析情况
      [leetcode]第一遍+第二遍复习_第3张图片
class Solution {
public:
    int maxProfit(vector<int>& prices) 
    {
        int len =prices.size();
        if(len==0) return 0;
        vector<vector<int>> dp(len,vector<int>(2));
        dp[0][0]= -prices[0];
        dp[0][1]= 0;
        for(int i=1;i<len;i++)
        {
            dp[i][0]=max(dp[i-1][0],-prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }
        return dp[len-1][1];
    }
};

122 买卖股票的最佳时机2(可以多次买入买出)

  • dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);

  • 没啥特别的 就一句话不一样

  • 原本今天持有

    • 昨天 持有和 今天第一次买入 二种情况
  • 现在 今天持有

    • 昨天持有 昨天不持有今天不一定买入(不一定第一次)二种情况
  • 第二遍复习 没什么问题

// 版本一
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len =prices.size();
        vector<vector<int>> dp(len,vector<int>(2));
        dp[0][0]=-prices[0];//第一天持有股票
        dp[0][1]=0;//第一天不持有股票
        for(int i=1;i<len;i++)//从第二天开始
        {
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }
        return dp[len - 1][1];//注意看这边是返回最后一天不持有股票的利润
    }
};

123买卖股票的最佳时机3

  • 最多进行两笔交易
  • 它分成5种状态没怎么想到,第一种啥事没干没想到,其实分成四种个人觉的更好理解,这样分反而和之前匹配起来(但是有一个坏处就是破坏了规律 参考第四题)
  • 今天持有第一支股票
  • 今天不持有第一支股票
  • 今天持有第二支股票
  • 今天不持有第二支股票
// 版本一
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if (prices.size() == 0) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
        dp[0][1] = -prices[0];
        dp[0][3] = -prices[0];
        for (int i = 1; i < prices.size(); i++) {
           //dp[i][0]=dp[i-1][0];//表示肯定昨天也啥事没干
           dp[i][1]=max(dp[i-1][1],-prices[i]); //昨天就买入了 或者今天才买入 昨天啥事没干 
           dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);//昨天酒卖出了 今天才卖出昨天有
           dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i]);//昨天酒第二次买入了 昨天不存在只走到了2今天买入
           dp[i][4]=max(dp[i-1][4],dp[i-1][3]+prices[i]);
        }
        return dp[prices.size() - 1][4];
    }
};
  • 第二遍复习
    • 没什么问题,注意看之前的注释就好了

188买卖股票的最佳时机4

  • 简单的说就是这个状态是重复的 2个2个重复了

  • 加一个循环遍历一下 j从1开始 <=2k结束,这边想不起来就用k=2的例子套进去看看对不对就这样

  • 初始化也是这样子

  • 我是在3的代码上修改的 挺简单的

  • 第二遍复习 没什么问题 直接那之前代码 3 4改就好

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

  • 没啥区别就是卖出 -fee
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][0] -= prices[0]; // 持股票
        for (int i = 1; i < n; i++) {
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
        }
        //return max(dp[n - 1][0], dp[n - 1][1]);
        return dp[n-1][1];
    }
};

309 最佳买卖股票含冷冻器(待补充)

  • 有点复杂 第二次看也有点复杂

字典树

  • 概念背一下
    • 空间换取时间
    • 用来查询和统计词频 效率比 hash表搞
    • 每个节点代表的字符都不相同节点本身不存完整单词。

208 实现trie(前缀树)

  • 写这种链表 记得成员和初始化!!!
  • 如果用的是传统的数组 记得memset清空一下
  • 加了点注释 ,有几个需要注意的地方比如新节点的常见,先进入下一层再判断他存不存在等等(细节的东西 还是要注意一下的)
class Trie {
public:
    /** Initialize your data structure here. */
    Trie* next[26];//26个孩子
    bool isEnd;//判断是不是到单词尾部
    Trie() {
        isEnd=false;
        memset(next,0x0,sizeof(next));//清空
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Trie* node =this;//之所以要创建这个 主要是要一层一层进去
        for(int i=0;i<word.size();i++)
        {
            
            if(node->next[word[i]-'a']!=NULL)//表示之前有人填充过
            {
                node=node->next[word[i]-'a'];//进入下一层
            }
            else//为空 那就要创建一个新的节点挂上去
            {
                 node=node->next[word[i]-'a']=new Trie();
            }     
        }
        //最后node指向最后一个字母节点 循环结束修改isend
        node->isEnd=true;

    }
    
    /** Returns if the word is in the trie. */

    bool search(string word) {
       Trie* node = this;
       for (int i=0;i<word.length();i++) 
        {
            node = node->next[word[i] - 'a'];

            //记住了先进入 再判断 (开始第一次next 才是抽象放入字母的地方)
            if(node == NULL)
            {
                //表示当前指向的字母就开始没有了
                return false;
            }
        }
        //循环走完了 指向最后一个字母了 看看它的isend
        return node->isEnd;

    }

    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Trie* node = this;
        for (int i=0;i<prefix.length();i++) {
            node = node->next[prefix[i]-'a'];
            if (node == NULL) {
                return false;
            }
        }
        return true;
    }
};

  • 第二遍复习
    • 没想清楚 构造函数 初始化什么 首先每次创建新结点 isEnd为false 开辟的空间 要自己memset
    • 基本一次性过,注意一下插入 创建节点 挂上 再进入
    • 长度为多少 就要next几次 因为第一个头结点没有实际含义。

并查集和字典其他两题好像举的例子不是很好 回头看看

下面开始就是贪心算法了

322零钱兑换(动态和贪心)

  • 这个题的具体代码 自己再查看一下

好好看这个 01背包问题

认真参考这个

  • 之前还忘记做了
  • 这个题可以是动态规划 也可以是贪心算法,比较经典的题目了。
  • 这个好像是经典动态规划的背包问题
  • 简单的说二维bp也算是比较好理解的,dp递推方程就是max(使用第i中物品 和 不使用第i种物品)理解 i表示0-i的物品可以选择 j表示背包大小
  • 二维初始化需要从后往前,0-1 只能放入一个。第二个就是实际上二维数组每次需要的只有上一层的数据 ,所以完全可以节省到一层。(它是用到正上面的数据和正上面前面的数据 一共两个)
  • dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
  • dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
  • 简单的说就是把i哪一个维度去掉了,降成一维
  • 还有一个就是一维的dp遍历顺序只能从后向前,为什么呢这个很好理解,(原本二维的是本层需要上一层(正上方和左边的数据)不存在覆盖什么的 从那一边都可以)现在只有一层 就是右边的数据需要左边的。等走到右边需要左边的值的时候都变化的不行。
void test_1_wei_bag_problem() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;

    // 初始化
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}

int main() {
    test_1_wei_bag_problem();
}
void test_2_wei_bag_problem1() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;

    // 二维数组
    vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));

    // 初始化 
    for (int j = bagWeight; j >= weight[0]; j--) {
        dp[0][j] = dp[0][j - weight[0]] + value[0];
    }

    // weight数组的大小 就是物品个数
    for(int i = 1; i < weight.size(); i++) { // 遍历物品
        for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
            if (j < weight[i]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

        }
    }

    cout << dp[weight.size() - 1][bagWeight] << endl;
}

int main() {
    test_2_wei_bag_problem1();
}

455 分发饼干

  • 有几个我写错的地方 s[index] >= g[i] 大于等于 if(index >= 0 && s[index] >= g[i] ) 还有一个就是这个的顺序 如果把index放在前面可能存在越界的问题 所以必须先对index进行判断非常重要
class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
//既然是用饼干去喂 那就把人作为循环 因为人是要走一遍的 但是饼干不一定 
//如果先满足大的孩子 我们可能拿最大的饼干发现每一个人符合 那就结束了 所以一个循环就好了
    sort(g.begin(),g.end());
    sort(s.begin(),s.end());
    int index= s.size()-1;
    int result=0;

    for(int i=g.size()-1;i>=0;i--)
    {
        if(index >= 0 && s[index] >= g[i] )
        //if(s[index] >= g[i] && index >= 0  )
        {
            result++;
            index--;
        }

    }
    return result;
    }
};


860 柠檬水找零

  • 这个题我就写一下伪代码把 比较简单
1 初始化变量 就两个 5块钱的 10块钱
进入循环
如果是5块钱 变量++
如果是10块钱  1 存在5块钱 -- 2 不存在5块钱 直接false
其他(也就是20块钱) 1 如果同时存在10块钱和5块钱 那就一起-- 2 如果存在3张或以上5块钱 -3 3 否则其他情况都返回false


874 模拟行走机器人

  • 这个题看不出是贪心,其实更像是考核代码和分析的能力,比较经典的

  • 为什么设置是北东南西 是因为一直向右旋转 变化 每次+1,如果是向左旋转 那就是北西南东每次正向+3变化你自己看看

  • 代码中有几个比较巧妙的地方比如设置set是为了到时候没走一步查看是否存在障碍。也能用map但是也一样要把键设置成pair的形式

  • 遇到一个错误就是把set 修改成unorderset 报错 查询是pair无法hash

  • for(int j=0;j这个代码习惯性写错,不要再size了 就是数 而且这边用j 不要是i 前面用过了 两个for的嵌套

  • 第三个就是 我增加了一个break 就是遇到障碍计算距离就退出了

  • 还有一个就是 我是遇到障碍回退 不再是用临时变量 结构更清楚了

class Solution {
public:
    int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        int dir_x[4]={0,1,0,-1}; //两个合起来看 每次走一步
        int dir_y[4]={1,0,-1,0};
        int x=0 ;//定义一个实际坐标 当前走到的位置
        int y=0; 
        int status=0; //遇到-1(右转) 就+1 %4 遇到 -2(左转)就+3%4
        int max_distance=0;//当前的最大距离是0
        //因为我们要判断障碍 随意放在set方便查询
        set<pair<int,int>> obstaclesSet;
        for(int i=0;i<obstacles.size();i++)
        {
            obstaclesSet.insert(make_pair(obstacles[i][0],obstacles[i][1]));//逐个插入 用 make_pair的方式 也可以匿名对象 
        }

        for(int i=0;i<commands.size();i++)
        {
            if(commands[i] == -1)
            {
                status=(status+1)%4;
            }
            else if(commands[i] == -2)
            {
                status=(status+3)%4;
            }
          
            else //正常 正数只走了
            {
                for(int j=0;j<commands[i];j++)
                {
                    //每次走一步
                    x=x + dir_x[status];
                    y=y + dir_y[status];
                    if(obstaclesSet.find(make_pair(x,y))!=obstaclesSet.end())//是障碍
                    {
                         x=x - dir_x[status];//回退
                         y=y - dir_y[status];//回退
                         max_distance = max(max_distance, x*x + y*y);
                         break;//跳出循环 其实不跳也可以就是一直判断是障碍 重复

                    }
                    else//如果不是障碍
                    {
                         max_distance = max(max_distance, x*x + y*y);//正常处理
                    }
                }
            }
     
       

            
        }
     return max_distance;

   
    }
};

55跳跃游戏

  • 不够明确写的时候,
  • 明确我们没走过一个点就要更新他的最大范围了,当然我们要走到这个点前提要判断能不能到达,i 和maxdistance进行比较。 madistance初始值是0 因为你想想第一个数肯定直接判断通过 并且范围单纯由元素决定的。

45 跳跃游戏2

有一个非常需要注意的就是 它要求是走到最后一个元素就好了,所以遍历不需要走到最后一个元素 比较特殊。

  • 题目要求竟可能少的步数,不用判断不能到的情况 肯定能到

  • 其实这个我还是记得他的思想的就是 第一步走的是固定的。第二步只能从就是从第一步走的范围选,那么走了几步 就是最小距离是多少。

  • 它这个简便的代码 也只有一层for循环正常遍历, 然后设置了一个标志位end(表示这一步的范围 初始肯定是0(0下标),然后每次i==end 表示这个范围走完了就更新end,并且数量++。你可以考虑最初是的情况没问题

  • 做了点修改 多了个判断 如果当前的距离已经够了 那就直接跳出 记得数量++ 这种自己举个例子就知道了

class Solution {
public:
    int jump(vector<int>& nums) {
        int ans=0;
        int end=0;
        int maxpos=0;
        for(int i=0;i<nums.size()-1;i++)
        {
            maxpos=max(maxpos,nums[i]+i);
            if(maxpos >= nums.size()-1) 
            {
                ans++;
                return ans;
            }
            if(i == end)//如果走到这一步范围的结尾了
            {
                end= maxpos;
                ans++;//数量+1
            }
        }

        return ans;
    }
};

下面开始复习二叉树和递归 因为二叉树很多递归的题目

94 二叉树的中序遍历

  • 方式一:递归
    • 递归终止条件(在这之前 考虑好递归函数的参数和返回值)
    • 处理本层逻辑
    • 进入下一层

[leetcode]第一遍+第二遍复习_第4张图片

  • 复习到这里 我都忘记了 中序遍历 前序遍历 后序遍历的顺序了。在递归里面走了一圈 感觉还可以(一个函数嵌套了两个函数,如果这两个函数都走完了 表示这个函数也结束了)(3个3个看 第一个函数表示的就是根节点 和左右节点)
  • 可以想象单独跟节点的情况 (左退出 输出 右退出 外层函数退出 结束)
  • 传入的数组是引用切记
class Solution {
public:
    void inorder(TreeNode* cur, vector<int>& vec) {
       if (cur == NULL) return;
       //进入左子树 可以想象单独跟节点的情况 (左退出 输出 右退出 外层函数退出 结束)
        inorder(cur->left,vec);

       //输出根节点
        vec.push_back(cur->val);
       //进入右子数
       inorder(cur->right,vec);
 
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        inorder(root, result);//传入的数组是引用切记
        return result;
    }
};


  • 如果用颜色标记法,注意auto的使用还不错 或者 first second 取出两个元素。

  • 自己写了一遍 挺简单的

    • 第一个注意的地方就是 初始化把跟放入 初始为白色 0
    • 第二个就是深刻理解 第一次放入都是白色的 取出再放入就是灰色的
    • 补充:放入栈的顺序 和要求相反 这个不用说了把
  • 第二遍复习

    • 过一遍顺序

    • [leetcode]第一遍+第二遍复习_第5张图片

    • 先序遍历顺序为:GDAFEMHZ

    • 中序遍历顺序为:ADEFGHMZ

    • 后序遍历顺序为:AEFDHZMG

    • 辅助栈和递归的写法都没什么问题 一遍过

144二叉树的前序遍历

-一样的

N叉树的后序遍历

  • 颜色标记法没啥好写的 改改递归好了
  • 补充了一下递归的写法 没啥问题
/*
// Definition for a Node.
class Node {
public:
    int val;
    vector children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    void inorder(Node* node,vector<int>& result)
    {
        if(node == NULL) return;
        for(int i=0;i<node->children.size();i++)
        {
            inorder(node->children[i],result);
        }
        result.push_back(node->val);

    }


    vector<int> postorder(Node* root)
     {
        vector<int> result;
        inorder(root,result);
        return result;

     }
};
  • 第二遍复习
    • 没什么问题

589 N叉树的前序遍历

  • 第二遍复习
    • 没什么问题

429 N叉树的层次遍历

  • 复习一下 就是把两个数组 标记 做成成员比较好

  • 初始还是把根节点放入

  • 循环开始

    • 取出 删除 把值放入数组 一个循环 把子节点都放入队列 flat-- 进行flat判断
  • 第二遍复习

    • 特别注意queue队列 是尾部插入 头部删除。这个和我们设计的单调队列恰好相反。
    • 队列 front() 返回最先被删除的元素 back返回最近被添加的元素 pop()删除 push()添加 。stack.top() stack.pop() 区别一下
    • 步骤
      • 取出 删除 子放入 flat 变化 判断(添加 重置 flat 清空)

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    vector<int> ans;//这个是用来存放放一层的数据 会随时清空
    vector<vector<int>> result;//这个是用来存放汇总的数据
    queue<Node*> que;//广度优先 使用队列
    vector<vector<int>> levelOrder(Node* root)
     {
        if(root ==NULL) return {};//特判 如果为空 直接返回
        //Node* p = root;//在循环开始前 总是把根节点加进去队列
        que.push(root);
        int flag =1;//初始标记 1 表示当前队列放入了一个
        while(que.empty()!=true)//如果队列不为空
        {
            auto p = que.front();//那我就每次从队列头部取出一个(最旧的元素 )
            que.pop();//老规矩 取出 就要删除
            ans.push_back(p->val);//取出就要放入数组内
 
        
            for(int i=0;i<p->children.size();i++)
           {
               que.push(p->children[i]);
           }
            --flag;  //用于记录当前层次的节点个数
            if(flag == 0)
            {
                flag = que.size();//这边就是统计队列剩余的数量 也就是下一层的数目了
                result.push_back(ans);//把当前层的数组加入总结果
                ans.clear();//一定要记得及时清空
            }
        }
        return result;
    }
};

22括号生成

  • 这种选择的 非a就是b的回溯挺常见的把(就是多剪枝)

  • 之前写的挺好的,

  • 经典的题目 写了一遍没啥问题

  • 第二遍复习

    • 非a 就是b 很明显就是回溯。但是我们不是要所有的可能 还要加上if 判断的剪枝。

226翻转二叉树

  • 挺简单的 就是标准四步 ,深度优先 自上而下处理
/**
 * 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) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root)
     {
        //终止条件
        if (root == nullptr)
         {
            return nullptr;
        }
        //当前层的逻辑处理
        TreeNode* temp=root->left;
        root->left=root->right;
        root->right=temp;
        //进入下一层 分别进入左子树和右子树
        invertTree(root->right);
        invertTree(root->left); 
        return root;   
    }
};
  • 第二遍复习
    • 没什么问题,返回值为空就好 因为也没有后序处理。 代码如下
/**
 * 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) {}
 * };
 */
class Solution {
public:
    //先序处理
    void dfs(TreeNode* root)
    {
        //递归终止条件
        if(root == NULL) return;
        //先序处理 
        TreeNode* temp = root->left;
        root->left=root->right;
        root->right=temp;
        dfs(root->left);
        dfs(root->right);
    }
    TreeNode* invertTree(TreeNode* root) 
    {
        dfs(root);
        return root;

    }
};

98验证二叉搜索树

  • 便捷的方法就是在之前中序遍历 放入节点的位置进行判断与上一个值,
  • 有一个疑惑 就是我咋觉的还是全部遍历了一遍呢 好像并没有及时退出啊
  • 我做了个修改 就是返回值直接进行判断 如果是false 直接return false 第三段代码

代码随想录

[leetcode]第一遍+第二遍复习_第6张图片

[leetcode]第一遍+第二遍复习_第7张图片

  • 补充个代码
class Solution {
private:
    vector<int> vec;
    void traversal(TreeNode* root) {
        if (root == NULL) return;
        traversal(root->left);
        vec.push_back(root->val); // 将二叉搜索树转换为有序数组
        traversal(root->right);
    }
public:
    bool isValidBST(TreeNode* root) {
        vec.clear(); // 不加这句在leetcode上也可以过,但最好加上
        traversal(root);
        for (int i = 1; i < vec.size(); i++) {
            // 注意要小于等于,搜索树里不能有相同元素
            if (vec[i] <= vec[i - 1]) return false;
        }
        return true;
    }
};


class Solution {
public:
    TreeNode* pre = NULL; // 用来记录前一个节点

    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;
        bool left = isValidBST(root->left);

        if (pre != NULL && pre->val >= root->val)
        {
            return false;//前一个值一定比后一个值小 如果大 那就return
        }
        pre = root; // 记录前一个节点

        bool right = isValidBST(root->right);
        return left && right;
        //return true;
    }
};

class Solution {
public:
    TreeNode* pre = NULL; // 用来记录前一个节点

    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;
        bool left = isValidBST(root->left);

        if (pre != NULL && pre->val >= root->val)
        {
            return false;//前一个值一定比后一个值小 如果大 那就return
        }
        if(left==false)
        {
            return false;
        }
        pre = root; // 记录前一个节点

        bool right = isValidBST(root->right);
        if(right==false)
        {
            return false;
        }
        //return left && right;
        return true;
    }
};

  • 第二遍复习
    • 还是解决与返回值问题,如果我们在某一条边确定了结果就要返回,那就用bool。
    • 当然我认为可以void 设置一个全局flat 终止条件那边判断flat被修改就一路返回,修改代码如下

class Solution {
public:
    TreeNode* pre =NULL;
    int flat=0;
    void dfs(TreeNode* root)
    {
        if(flat==1) return;
        if(root==NULL) return;
        dfs(root->left);
        if(pre!=NULL && pre->val >= root->val)
        {
            flat=1;
            return;
        }
        else//要么是pre为空 要么正常情况
        {
              pre = root;
        }
        dfs(root->right);
    }
    bool isValidBST(TreeNode* root) 
    {
        dfs(root);
        if(flat==1) return false;
        else return true;
    }
};

104二叉树的最大深度

  • 遍历顺序是后序的 之前那个验证是中序的

[leetcode]第一遍+第二遍复习_第8张图片


class Solution {
public:
    int maxDepth(TreeNode* root) 
    {
        if(root == nullptr)//想想初始点
        {
            return 0;
        }
        //处理当前层的逻辑
        int left = maxDepth(root->left);
        int right = maxDepth(root->right);
        int ret=max(left,right)+1;
        return ret;
    }
};
  • 第二遍复习
    • 更深刻了,后序很容易想到,我们需要判断左子树和右子树的高度 返回大的哪一个+1(加上当前层的高度)。

111二叉树的最小深度

代码随想录

  • 比较特别 区别于最大深度
  • 一样是后序 先统计子的结果 再来统计父的结果
class Solution {
public:
    int getDepth(TreeNode* node) {
        if (node == NULL) return 0;
        int leftDepth = getDepth(node->left);           // 左
        int rightDepth = getDepth(node->right);         // 右
                                                        // 中
        // 当一个左子树为空,右不为空,这时并不是最低点
        if (node->left == NULL && node->right != NULL) { 
            return 1 + rightDepth;
        }   
        // 当一个右子树为空,左不为空,这时并不是最低点
        if (node->left != NULL && node->right == NULL) { 
            return 1 + leftDepth;
        }
        int result = 1 + min(leftDepth, rightDepth);
        return result;
    }

    int minDepth(TreeNode* root) {
        return getDepth(root);
    }
};


  • 第二遍复习
    • 需要注意没有叶节点 不用来比较
    • 按照自己的理解重写了代码 和之前不同 但是大同小异

class Solution {
public:
    int minDepth(TreeNode* root) 
    {
        if(root == NULL) return 0;
        int leftDepth = minDepth(root->left);           // 左
        int rightDepth = minDepth(root->right);         // 右
        if(leftDepth == 0 && rightDepth !=0)
        {
            return rightDepth+1;
        }
        else if(leftDepth!=0 && rightDepth==0)
        {
            return leftDepth+1;
        }
        else if(leftDepth==0 && rightDepth==0)
        {
            return 1;
        }
        else
        {
            return min(leftDepth,rightDepth)+1;
        }


    }
};

236 二叉树的最近共同祖先(重)

代码回想录

  • 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从低向上的遍历方式。

  • 在回溯的过程中,必然要遍历整颗二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。

  • 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。


补充!!!!

后续 就是从底向上 先左边后右边 一层一层向上,这个要明确
(452 673 1)后(左右跟) 只要左边还有就继续深入
(4 2 3 1 6 3 7)中(左跟右)只要左边还有继续深入否则输出3个为一个结构

  • 终止条件:遇到p 或者遇到q 或者为空 就开始返回(当然空 就返回空 非空返回节点地址)

  • 当前层的逻辑就是:

    • 如果左右子树的返回值都不为空, 就返回当前层的root 这个最好理解 后序遍历
    • 如果左子树 或者右子树为空 那就返回哪一个(情况一:确实只找到一个) 情况二:找到了两个 它的祖先就是当前的一边而已不管什么情况最后返回到跟的就是结果(这种遍历方式是需要走一遍的)好像也有那种遇到就一路返回的,我之前的那种方式,主要现在这种方式 你没办法判断返回的是哪一种情况 只能确定返回到跟的必然是结果
  • 进入下一层 包含在逻辑里面了

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
     {
        if (root == q || root == p || root == NULL) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if (left != NULL && right != NULL) return root;

        if (left == NULL && right != NULL) return right;
        else if (left != NULL && right == NULL) return left;
        else  { //  (left == NULL && right == NULL)
            return NULL;
        }

    }
};
  • 第二遍复习
    • 首先注意终止条件,我们什么时候停止
    • 清楚为什么用后续,因为我们找共同祖先 需要从后向前 子找父 一个意思
    • 终止条件是递归的第一个返回值,再想想后序如何处理。(换句话说就是后续第一个处理的值是终止条件 想想如何判断返回)

105 从前序和中序遍历序列构建二叉树

  • 比较少见的先序遍历,我们要从上倒下
  • 1 2 45 3 6 7 因为你要先处理跟节点再处理子节点(先序不就是先输出根节点 再深入 区别于之前的后续遍历 从后向前)
  • 传入的参数肯定有两个序列 还有边界
  • 返回值就是节点
  • 这边还构建的hash 键为中序的值 值为下标
  • preorder_left > preorder_right 关注一下为什么以这个作为终止条件 其实你看传入的参数 前序的左边界一定是上一次左边界+1(也就是下一个)右边界是上一次左边界加长度(为空=+0 ) 所以就这样了
终止条件 就是元素只有一个的时候
先序序列的第一个作为根节点 找到在中序的下标
先序遍历  构建出第一个跟节点 
通过中序序列 求得左子树长度 记得+1
root->left=递归函数 左边(两个序列,前序边界(+1,加上长度-1)中序边界(left 和中间-1))
同理右边 ()

  • dfs主要分为几步

    • 终止条件
    • 计算保存先序和中序序列根节点的坐标
    • 根据中序和跟节点坐标计算左子树的长度(记住不要包含跟节点)
    • 常见一个新的节点
    • 挂上左子树和右子树
      • 先序左子树左边界 preorder_left+1
      • 先序左子树右边界preorder_left+1+size_left-1(表示左边界+长度-1)
      • 中序的左子树左边界 inorder_left
      • 中序的左子树右边界 inorder_root-1(跟节点坐标-1)
      • 先序的右子树的左边界preorder_left+size_left+1(表示左子树右边界+1)
      • 先序的右子树的右边界preorder_right
      • 中序的右子树的左边界是inorder_root+1
      • 中序的右子树的有边界是inorder_right
  • 第二遍复习

    • 准确的说是先序+后序,先序分割+够开辟当前节点的空间,后序把返回的构建小树挂载当前节点的左右。
    • 有空把重新写一遍,还是需要点时间的 边界容易弄错了
class Solution {
private:
    unordered_map<int, int> index;

public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right)//简单的说就是序列长度为0 
        {
            return nullptr;
        }
        
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
        
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目-1 少一个
        int size_left_subtree = inorder_root - inorder_left + 1;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree-1, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree-1 + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < n; ++i) {
            index[inorder[i]] = i;
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};

77组合

代码回想录

[leetcode]第一遍+第二遍复习_第9张图片

  • 第一个感觉 就是那种非a就是b的题目 回溯做 和括号一样(区别在于这边每一次递归函数的选择的不一样了 不能选择重复的 所以设置了一个start 每一个递归函数 的for都有变化 这就是区别
  • 要么做成全局 要么做成引用 要么临时不需要回退。理解组合 输出一定是小数在前大数在后
class Solution {
private:
    vector<vector<int>> result; 
    vector<int> path;
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方
            path.push_back(i); // 处理节点 
            backtracking(n, k, i + 1);
            path.pop_back(); // 回溯,撤销处理的节点
        }
    }
public:

    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

作者:carlsun-2
链接:https://leetcode-cn.com/problems/combinations/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-hui-s-0uql/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        dfs(n,k,1);
        return Res;
    }
    vector<int> res;//这边的两个设为全局变量
    vector<vector<int>> Res;
    void dfs(int n, int k,int start)
    {
        //终止条件
        if(res.size()==k)
        {
            Res.push_back(res);
            return;
        }
        for(int i= start;i<=n;i++)
        {
            //处理当前层的逻辑
            res.push_back(i);
            dfs(n,k,i+1);//进入下一层
            res.pop_back();//回溯
        }
        

      
    }
};
  • 第二次复习
    • 规定 start 导致从小到大 并且不选择重复 这样就可以达到组合 而非排序。原话:每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex。

46 全排序

代码回想录

  • 多个状态和start每次从1开始
  • 其实就是通过状态 数组 和 循环每次从1开始 构成一个环 才能出现12 21这种情况
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 此时说明找到了一组 终止条件
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for(int i=0;i<nums.size();i++)//解释说 12 21 发现1用了两次和组合不同 所以必须从1开始遍历 要不出现不了 21 这种 start必然从小到大排列的
        {
            if(used[i]==true) continue;
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(nums,used);
            path.pop_back();//回退
            used[i]=false;//状态也回退
        }
      
    }
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), false);//其实这个也可以设置成全局这样 就不用传入作为引用了
        backtracking(nums, used);
        return result;
    }
};
  • 第二遍复习
    • 先想到 全排列 就想每个格子进行非a就是b的选择 ,但是要避免选过的再去选择 (这个是我们要处理的)(所以设置了一个状态数组)
    • 其实可以自己模拟一下 1 2 3. (1) 退到3那一层 回溯 for结束 回到上一层(2层) 回溯(2去掉) for继续 1 3 进入下一层 2放入。综上可以出现 从大到小排序
    • 每次从1开始而不是start 才可能把小数添加到后面 构成一个环

补充

补充一个 就是把括号和全排列 组合联想一起 for循环不就等于几个单独的if 区间么

78 子集

连接
连接
[leetcode]第一遍+第二遍复习_第10张图片

[leetcode]第一遍+第二遍复习_第11张图片

  • 可以认为每一个位置是放入和不放入
  • 边界条件是下标越界了 == nums.size()
  • 这个和之前不同的是 我需要自己传入下一次考虑的位置,之前是一直push放入判断长度,这次要告诉下一个我放不放入。

[leetcode]第一遍+第二遍复习_第12张图片

class Solution {
public:
    vector<int> t;//全局变量 
    vector<vector<int>> ans;//全局变量

    void dfs(int cur, vector<int>& nums) {
        if (cur == nums.size())//终止条件 当前位置等于总长度 
        {
            ans.push_back(t);//cur 表示的是当前位置
            return;
        }
     
        //选择考虑当前位置
        t.push_back(nums[cur]);
        dfs(cur + 1, nums);//进入下一层(下一个位置)
        t.pop_back();//什么时候删除,走出来函数就删除
        //考虑不走当前位置
        dfs(cur + 1, nums);
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(0, nums);
        return ans;
    }
};
  • 也可以是这样
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path); // 收集子集
        if (startIndex >= nums.size()) { // 终止条件可以不加
            return;
        }
        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

上面这个 你认真看他在退出前收集了 不是在退出的时候收集 认真对比一下这个代码和组合的区别

50 pow(x,n)(重)

  • 就是那种题目 需要从小的 可以推出大的 就用分治 或者说 递归。
  • 比如说排序 先排2个 再排 4 再排8个
  • 比如这个题 求x 八次方 我们一个先求x 2 再 x4 再 x8 这种问题就是后续
  • 然后我们要确定参数和返回值,我们就思考这个是一层一层往下 从下返回结果,参数就是x 还有 n的阶层 这两个都简单,然后返回值因为是通过下层的结果返回上来 所以返回值肯定是具体的次方结果
class Solution {
public:
    double quick(double x,int n)
    {
        //1终止跳进
        if(n == 1)
        {
            return x;
        }
        //2进入下一层
        double y = quick(x,n/2);
        //3分治
        if(n % 2==0)
        {
            return y*y;
        }
        else
        {
            return y*y*x;
        }

    }
   
    double myPow(double x, int n) {
        long long N=n;
        double res;
        if( n == 0)
        {
            return 1;
        }
        if(N>0)
        {
            res =quick(x,N);
        }
        else
        {
            res =1.0/quick(x,-N);
            
        }
        return res;
        
    }
};

169 多数元素

  • 就是元素个数大于1/2的数 叫做多数

  • 方法比较多 用哈希 或者取巧的方法都可以 递归也行,这边复习一下递归的写法

  • 根据上一题总结的思路 重新写了一遍 加上注释没啥问题 很完美 这种序列二分我们要执行两个递归函数 并传入边界(时间复杂度o(nlogn) 空间复杂度 0(logn))

class Solution {
public:
    //构建了一个辅助函数
    int count_number(vector<int>&nums,int target,int l,int r)
    {
        int count=0;
        for(int i=l;i<=r;i++)
        {
            if(nums[i]==target)
            {
                count++;
            }
        }
        return count;
    }
    
    //递归函数
    int helper(vector<int>&nums,int l,int r)
    {
        //终止条件
        if(l==r)//说明只有一个元素 就是众数
        {
            return nums[l];
        }
        int mid=l+(r-l)/2;
        int left = helper(nums,l,mid);//后序 先递归
        int right =helper(nums,mid+1,r);
        //后序的第三部分 或者说分治 left 和 right都是下面(子的结果)
        if(left == right) return left;
        else//否则分别在当前层 统计他们的个数
        {
            int left_count= count_number(nums,left,l,r);
            int right_count =count_number(nums,right,l,r);
            if(left_count > right_count) return left;
            else return right;
        }
    }
    int majorityElement(vector<int>& nums) 
    {
        int ret =helper(nums,0,nums.size()-1);
        return ret;
    }
};
  • 第二遍复习
    • 后序处理 不一定是对返回值的加减乘除 也可能是没有返回值 或者对返回值进行判断
    • 后序遍历中 第一个函数返回值就是终止条件产生的值,我们可以想象最小的单元结构 比如两个数序列 2 2 进行比较 找到 22 组合起来的众数

17 电话号码的字母组合

  • 一看到这种选择 就是多个空的格子 非a就是b的就想到用回溯
  • 我们在组合中 下一个格子只能在比当前字母大的选 所以用了一个start
  • 全排序中是没选过的都可以选 所以用了一个状态的数组和每次从0遍历达到一个效果
  • 这个题他每个格子的选择就是一个专门的区域。如果选数字12 那就是第一个格子在1选 第二个格子在2选。所以每次递归进入下一个函数应该更换他for的那个nums就对了 我就是这么觉的的 其他的应该不用变。
  • 学习一下map如何一次性插入多个元素
  • 其实我们构建一个map 键就是数字 值就是对应的字母 然后递归传入参数 1当前操作第几个格子 2通过数字作为下标 找到传入的字符串对应下标的数字 3 通过数字就能 键值对找到字符串字母
  • 一定要明确我们为什么从0开始,你明白其他的例子为什么从start开始就简单了,因为一个for表示当前这个格子选择 每一个区域都是分开的当然从0

自己写了一遍 没啥问题

class Solution {
public:
    unordered_map<char,string> phone
       {
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        };
    //返回值没啥好说的觉就是void
    void helper(vector<string>& result,string& ans,string& digits ,int index)
    {
        //终止条件 下标越界 ==长度
        if(index == digits.length())
        {
            result.push_back(ans);
            return;
        }
        //特别指出 在这 做一些准备操作 
        char digit = digits[index];//取出数字
        string letters = phone[digit];//取出对应的字符串
        for(int i=0;i<letters.length();i++)
        {
            ans.push_back(letters[i]);
            helper(result,ans,digits,index+1);
            ans.pop_back();
        }
        
    }

    vector<string> letterCombinations(string digits) {
       vector<string> result;
       if(digits.empty())
       {
           return result;
       }
       //构建一个map 键就是数字(char类型)
      
    string ans;//这个存放的一维的数据
    helper(result,ans,digits,0);//回溯函数
    return result;

    }
};
  • 第二遍复习(重)
    • 相同了一个点 就是分治中后序处理(可能对返回结果进行任意操作 第一个产生的值就是来自终止条件 切记)
    • start 和 电话 传入的index 的作用都是为了下一个格子确定选择的范围
    • 这种start index 标记传入形参就好了 不用引用 要不还要回溯

51N皇后问题(待补充)

515 在每行树行中找最大值

  • 怎么说呢 就是层次遍历的复习,之前是保存所有的值 现在是保存最大值而已 区别不是很大
/**
 * 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) {}
 * };
 */
class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);//老样子 初始把值放入
        vector<int> result;//这次不需要二维了
        while (!que.empty()) 
        {
            int size = que.size();
            int maxValue = INT_MIN; // 取每一层的最大值 这个也是临时变量 每次for循环之前都要初始化
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                if(node->val>maxValue)
                {
                    maxValue=node->val;
                }
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(maxValue); // 把最大值放进数组
        }
        return result;
    }
};
  • 第二遍复习
    • 这个题有一个特别之处在于要判断左子树和右子树不为空再放入否则会报错
/**
 * 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) {}
 * };
 */
class Solution {
public:

    vector<int> largestValues(TreeNode* root) 
    {
        if(root == nullptr) return{};
        int flat =1;
        int max_value= INT_MIN;
        vector<int> result;
        queue<TreeNode*> que;
        que.push(root);
        while(que.empty()!=true)
        {
            auto  p = que.front();
            que.pop();
            max_value=max(max_value,p->val);
            if(p->left) que.push(p->left);
            if(p->right) que.push(p->right);
            flat--;
            if(flat == 0)
            {
                flat=que.size();
                result.push_back(max_value);
                max_value=INT_MIN;
                
            }
        }
        return result;

    }
};

127 单词接龙

  • 其实这个题最重要的还是理清楚为什么要用广度搜索,其他倒是没什么

  • 一个容易错的就是 两个for循环 为了防止修改多个位置,所以需要 在一个for循环创建一个临时变量

  • 第二个就是 两个for循环嵌套的第一个if判断就是这个单词是不是word end 如果是返回path+1 记得+1。 第二个if如果在set出现(说明是字典集的)不在map出现(说明没有重复,这个是这题的精髓 存在重复的)那就把这这个单词放在队列 并且把他和path+1 放入到map中

  • 需要注意为什么初始放入path为1 他这个path是指序列的长度 而不是变化的次数 注意读题

  • 第二遍复习

    • 看之前的解释 主要还是那些问题 以及一些细节的处理
class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> wordset(wordList.begin(),wordList.end());//创建一个set用来放入字典的字符串方便查询
        unordered_map<string,int> visitmap;//创建一个map方便取出对应的步数+1
        //进行特判
        if(wordset.find(endWord)==wordset.end())
        {
            return 0; //直接返回结果
        }
        //标准创建一个队列
        queue<string> que;
        que.push(beginWord);
        //这边还需要放入map中
        visitmap[beginWord]=1;
        //这边不需要设置标志位了 因为确实不需要一层一层处理了

        while(que.empty()!=true)
        {
            string word = que.front();//取出队列哪一个
            que.pop();//删除哪一个
            int path=visitmap[word];//取出路径

            //这边正常层次遍历做的应该是一个for循环 把孩子都放进去.本题这边是两个for循环
            //特别容易错的
            for(int i=0;i<word.size();i++)
            {
                string newword= word;//这边是必须的 每次操作都是在全新的单词上操作 否则就变成叠加操作了
                for(int j=0;j<26;j++)
                {
                    newword[i]=j+'a';
                    if(newword == endWord) return path+1;
                    if(wordset.find(newword)!=wordset.end() && visitmap.find(newword) == visitmap.end())
                    {
                        //两个同时满足
                        visitmap[newword]=path+1;//记得+1
                        que.push(newword);
                    }
                }
            }
        }
        return 0; //都遍历一遍都找不到 那就返回01把

    }
};

433 最小基因变化

  • 和单词接龙一样
    就是内层循环原本是26个字母进行选择 现在固定只有4个没有区别 多一个数组放这4个字母就好了

126单词接龙 升级版本(待补充)

200 岛屿的数量

  • 二叉树
  • 经典的网格深度优先遍历,在图上面的展示就是向四周扩散,模拟二叉树的向下扩散
  • 这边用的是先序遍历 从中间向四周处理。
  • 我们基本上就是对每一个1 都做了一次深度优先遍历
  • 整体没什么问题
  • 有一个比较精髓的就是遇到1 变换成2,防止转圈圈
  • 他就是遇到岛屿先把自己标记成遍历过的 再向四周扩散 注意顺序
  • 、他统计岛屿个数是在外层统计的 这个比较难想到 我以为在内部统计 其实不是 就是看看就几个起源点
class Solution {
public:
    bool inArea(vector<vector<char>>& grid,int r,int c)
    {
        //进行边界判断
        bool a = (0 <= r && r <= grid.size()-1);  
        bool b =(0 <=c && c<=grid[0].size()-1);
        return a && b;
    }
    //传入网格 传入坐标
    void dfs(vector<vector<char>>& grid,int r,int c)
    {
        //终止条件判断 越出网格边界
        if(inArea(grid,r,c)==false)
        {
            return;
        }
        //处理当前层的逻辑 先序
        if(grid[r][c]!='1')//表示标记过了 或者是海 不能写==0
        {
            return;
        }
        grid[r][c]=2;//把他变换成’海‘
        //进入下一层
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);

    }
    int numIslands(vector<vector<char>>& grid) 
    {
        int nr = grid.size();
        if (!nr) return 0;
        int nc = grid[0].size();
        int num_islands = 0;
        for (int r = 0; r < nr; ++r) 
        {
            for (int c = 0; c < nc; ++c) 
            {
                if (grid[r][c] == '1')//这边判断和递归函数内部的判断不冲突 递归函数内的判断是扩散的判断
                {
                    ++num_islands;
                    dfs(grid, r, c);

                }
            }
        }
         return num_islands;
    }
};

460 岛屿的周长(补充的题目)

  • 这个在求岛屿数量上进行修改就好了
  • 设置一个全局变量 不用传参
  • 遇到海 ==0 那就周长+1 return遇到边界周长+1 return 就这两种
  • 然后再双层for循环 我们找到一个是1的岛屿 就return 结果就好了
class Solution {
public:
    int num_islands=0;
    bool inArea(vector<vector<int>>& grid,int r,int c)
    {
        //进行边界判断
        bool a = (0 <= r && r <= grid.size()-1);  
        bool b =(0 <=c && c<=grid[0].size()-1);
        return a && b;
    }
    //传入网格 传入坐标
    void dfs(vector<vector<int>>& grid,int r,int c)
    {
        //终止1条件判断 越出网格边界
        if(inArea(grid,r,c)==false)
        {
            num_islands++;
            return;
        }
        //终止2
        if(grid[r][c]==2)//表示标记过了 或者是海 不能写==0
        {
            return;
        }
        //终止3
        if(grid[r][c]==0)
        {
            num_islands++;
            return;

        }
        //当前层的逻辑
        grid[r][c]=2;//把他变换成’海‘
        //进入下一层
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);

    }
    int islandPerimeter(vector<vector<int>>& grid) {
         int nr = grid.size();
        if (!nr) return 0;
        int nc = grid[0].size();
        for (int r = 0; r < nr; ++r) 
        {
            for (int c = 0; c < nc; ++c) 
            {
                if (grid[r][c] == 1)//这边判断和递归函数内部的判断不冲突 递归函数内的判断是扩散的判断
                {
                    dfs(grid, r, c);
                    return num_islands;

                }
            }
        }
         return num_islands;
    }

};
  • 第二遍复习 没什么问题 一次过

695 岛屿的最大面积(补充的题目)

  • 这个题和之前有一点点不一样,这个是深度遍历 先序把把走过的标记
  • 后序 统计子的面积也可以说是分治进行一个汇总 加上自己本身+1
  • 经典先序+后续
  • 还有一个就是遇到终止条件的设定
  • 最后一个就是关注 最大面积在外层进行一个统计
class Solution {
public:
    bool inArea(vector<vector<int>>& grid,int r,int c)
    {
        //进行边界判断
        bool a = (0 <= r && r <= grid.size()-1);  
        bool b =(0 <=c && c<=grid[0].size()-1);
        return a && b;
    }
    //传入网格 传入坐标
    int dfs(vector<vector<int>>& grid,int r,int c)
    {
        //终止条件判断 越出网格边界
        if(inArea(grid,r,c)==false)
        {
            return 0;
        }
        // 也算是边界处理
        if(grid[r][c]==2)//表示标记过了 或者是海 不能写==0
        {
            return 0;
        }
        //也算是边界处理
        if(grid[r][c]==0)
        {
            return 0;

        } 
        grid[r][c]=2;//把他变换成’海‘
        //进入下一层
        int a_ = dfs(grid, r - 1, c);
        int b_ = dfs(grid, r + 1, c);
        int c_ = dfs(grid, r, c - 1);
        int d_ = dfs(grid, r, c + 1);
        

        return a_+b_+c_+d_+1;

    }
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int nr = grid.size();
        if (!nr) return 0;
        int nc = grid[0].size();
        int max_ = 0;
        for (int r = 0; r < nr; ++r) 
        {
            for (int c = 0; c < nc; ++c) 
            {
                if (grid[r][c] == 1)//这边判断和递归函数内部的判断不冲突 递归函数内的判断是扩散的判断
                {
                    int max_num = dfs(grid, r, c);
                    max_ = max(max_num,max_);

                }
            }
        }
         return max_;
    }

};
  • 第二遍复习
    • 比较容易想到 一个岛屿的面积等于 一个方块的面积加上四个子方向面积的和。需要后续进行结果的汇总
    • 代码一遍过

49 字母异位词分组

第二次写有些问题如下

  • sort排序会修改原始数据 所以当我们需要再排序之前创建一个变量
  • map的值是一个vector 所以在添加数据 是 ans【】.push_back 不是=
  • map 如何利用迭代器遍历输出 取出值 用的是second 和 first

283 移动零

  • 这个题不算第一次见了把 在字符串那边 好像也用过这个方法 比较常见的
  • 补充了一种写法在下面 其实怎么写都可以就是怕for循环加一个 i++ 一次加了2次那就错了
void moveZeroes(int* nums, int numsSize){
int k=0;
int i = 0;
while(i<numsSize)
{
    if(nums[i]!=0)
    {
        nums[k++]=nums[i++];
        //指向下一个待填入的位置
    }
    else if(i<numsSize && nums[i]==0)
    {
        i++;
    }
//如果是0 那就啥事没有等外层循环i++ 就好了
}
while(k<numsSize)
{
    nums[k]=0;//变成0就好
    k++;
}
}
  • 第二遍复习
    • 没啥问题,就是换成了for循环写法,因为固定向后移动 所以想用for方便一点。
    • 喜欢成为这类为快慢指针(双指针)慢的表示待填入 快的表示正常移动的
class Solution {
public:
    void moveZeroes(vector<int>& nums) 
    {
        int k=0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]!=0)
            {
                nums[k++]=nums[i];//K指向下一个待填入的地方
            }
        }
        for(int i=k;i<nums.size();i++)
        {
            nums[i]=0;
        }
        

    }
};

11 盛最多水的容器

  • 也是双指针 简单是说他的原理就是移动小的指针,反证法思考 如果移动长的柱子 面积是一定不可能增加的 由于面积取决的最小值 那我们只能改变当前最小值才有可能变的更好
  • 自己写了一遍还是比较简单的 双指针这种我觉的 还是用while好 毕竟移动由我们控制
class Solution {
public:
    int maxArea(vector<int>& height) 
    {
        int n = height.size();
        int i = 0;
        int j = n-1;
        int max_=0;//这个表示最大的面积

        while(i<j)
        {
            int max_new = (j-i)*min(height[i],height[j]);
            max_=max(max_new,max_);
            if(height[i]>height[j]) j--;
            else i++;
        }
        return max_;
    }
};
  • 第二遍复习
    • 没什么问题,需要一个循环不断去缩小边界,但是缩小那一边不是由我们控制的(不固定) 所以while简单点
class Solution {
public:
    int maxArea(vector<int>& height) 
    {
        int n =height.size();
        int i=0;
        int j=n-1;
        int max_=0;
        while(i<=j)
        {
            max_=max(max_,(j-i)*min(height[i],height[j]));
            if(height[i]>height[j])
            {
                j--;
            }
            else
            {
                i++;
            }
        }
        return max_;

    }
};

70 爬楼梯

  • 我做了点修改,就是更加符合 dp【i】=dp【i-1】+dp[i-2]所以i从2开始
  • 这边需要注意是 0台阶是1种可能
class Solution {
public:
    int climbStairs(int n) {
    if(n == 0) return 0;
    if(n == 1) return 1;
    int pre = 1;
    int pre2 = 1;
    int now;
    for(int i = 2;i <= n;i++)
    {
        now =pre+pre2;
        pre2 =pre;
        pre = now;
        
    }
    return now;
    }
};
  • 没什么问题,就是注意进阶 编程0-1背包问题

15 三数之和

  • 简单的说 就是三个指针 外层for循环控制第一个指针 然后就变成了双指针的问题 双指针肯定就是while循环
  • 这边几个需要处理的就是 特判如果第一个指针》0 可以直接返回结果了不用再进行判断
  • 还有就是外层指针重复数 for + if(i > 0 && )continue 这边的continue 和 i>0 用的很好
  • 内层双指针用的是while 所以不能用continue 只能用 while (left < right && nums[left]==nums[left-1]) 这种方式了 很常见的 需要再while加上 <的判断
  • 如果相等需要做三件事 放入 收缩 重复数的判断 切记
  • res.push_back({nums[i],nums[left],nums[right]}); res.push_back(vector{nums[i],nums[left],nums[right]});如何批量放入 这两种写法都是可以的
  • 当前值和上一个值比较的时候 要注意 i>0 易错 难找
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        int size = nums.size();
        if (size < 3) return {};
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());
        for(int i = 0 ; i<size ; i++)
        {
            if (nums[i] > 0) return res;
            if(i > 0 && nums[i]==nums[i-1]) continue;
            int left = i+1;
            int right = size - 1;
            while(left < right)
            {
                if(nums[i]+ nums[left]+nums[right] > 0)
                {
                    right--;
                }
                else if(nums[i]+ nums[left]+nums[right] < 0)
                {
                    left++;
                }
                else
                {
                    //res.push_back(vector{nums[i],nums[left],nums[right]});//放入                 
                    res.push_back({nums[i],nums[left],nums[right]});
                    right--; //收缩
                    left++;
                    while (left < right && nums[left]==nums[left-1]) left++;
                    while (left <right && nums[right]==nums[right+1]) right--;
                }
            }
        }
        return res;

    }
};
  • 第二遍复习
    • 没什么问题,基本一次过
    • 注意一: if(i > 0 && nums[i]==nums[i-1]) continue;
    • 注意二: while (left < right && nums[left]==nums[left-1]) left++; while跳过某些数一定记得判断越界
    • 思想很简单,每一次固定一个数,另外两个数比较。第一个指针固定增加用for

26 删除排序数组的重复项

  • 自己写的竟然没啥问题
  • 这个题和那个跳过0的题用的方法一样,这个不能叫双指针 交快慢指针比较合适
  • 一个指针指向等待填入的位置 一个指针指向待比较的位置
  • 我喜欢把待填入的位置设为k 初始为1 因为0的那个位置不需要考虑
  • 然后i(快指针 ) 从1开始是为了 和前一个比较 这样方便写 不能和后一个比 要不还要考虑后一个有没有超过范围
  • 00 1 举个例子 如果i和前一个一样那就后移 表示没找到不重复的数 直到找到了 那就放到k里面 然后k和i后移。如果前后一样 那就单独移动i继续寻找就好
  • 主要是注意为什么两个初始值都是1
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int size =nums.size();
        if (size < 2) return size;
        int k = 1;
        int i = 1;
        while(i<size)
        {
            if(nums[i]==nums[i-1])
            {
                i++;
            }
            else
            {
                nums[k]=nums[i];    
                k++;i++;
            }
        }
        return k;
}   
};

189 旋转数组

  • 对于c++来说这个题 其实很简单的 没什么难度,就是可能比较不好想如果没做过这个题
  • 当然reverse这个函数可以自己来写 用swap两两交换。
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n= nums.size();
        k = k % n;
        reverse(nums.begin(),nums.end());
        reverse(nums.begin(),nums.begin()+k);
        reverse(nums.begin()+k,nums.end());
       
    }
};
  • while 和 for 的方式都可以写一下
    void reverse(vector<int>& nums, int start, int end) {
        while (start < end) {
            swap(nums[start], nums[end]);
            start += 1;
            end -= 1;
        }
    }

88 合并两个有序数组

  • 其实这个题也常见 要么就和那个归并排序一样 需要额外的空间去存放元素
  • 要么就从后向钱存放, 需要的就是其中一个数组的空间足够的大
  • 直接自己写了一遍 定义了三个变量 标准 i j k
  • 这个其实和归并排序考虑我问题一样 就是有剩余直接放进去 分成三个部分好像比较好考虑 其实第一个k不需要判断 只要i和j都大于0 那它不会小于0
class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        //这边的 m 和 n 都表示的是元素的个数
        int k = m + n-1;
        int i = m-1;
        int j = n-1;
        while(k >= 0 && i >=0 && j >= 0)//表示当前的位置
        {
            if(nums1[i]>nums2[j])
            {
                nums1[k--]=nums1[i--];
            }
            else
            {
                nums1[k--]=nums2[j--];
            }
            
        }
        while(i >= 0)
        {
            nums1[k--]=nums1[i--];
        }
        while(j >= 0)
        {
            nums1[k--]=nums2[j--];
        }

    }
};

1两数之和

  • 只会存在一种答案 答案说的
  • 这个题还有一个特别的就是 要返回下标 所以你不能用头尾双指针 要么就双指针加上一个hash(这样也存在错误 如果target =6 数组存在两个3 那就会报错如下的代码)
  • 补充一下 一遍hash的方法 他就是在插入的时候查看有没有存在target-num【i】的元素 有点巧妙 自己写一遍 代码我也自己写了一遍放在下面了
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) 
    {
        vector<int> ans;
        int i = 0;
        int j = nums.size()-1;
        unordered_map<int,int> map;
       
        for(int i = 0;i<nums.size();i++ )
        {
            map[nums[i]]=i;
        }
        sort(nums.begin(),nums.end());//排序一下
        
        while(i < j)
        {
            if(nums[i]+nums[j] < target)
            {
                i++;
            }
            else if(nums[i]+nums[j] > target)
            {
                j--;
            }
            else
            {
                
             ans.push_back(map[nums[i]]);
                ans.push_back(map[nums[j]]);

                return ans;
            }
        }
        return ans;

    }
};
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) 
    {
       unordered_map<int,int> map;
       vector<int> ans;
       for(int i =0;i<nums.size();i++)
       {
           if(map.find(target-nums[i])==map.end())
           {
               map[nums[i]]=i;//没有匹配 那就插入
           }
           else
           {
               ans.push_back(i);
               ans.push_back(map[target-nums[i]]);
               break;
           }

       }
       return ans;

    }
};

66 加一

  • 这个题就是模拟整形+1 我来试试
  • 自己写的第一遍错了一个地方就是 +1等于10 那就需要变成0 不是还是9不变 傻了
class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
    for(int i = digits.size()-1;i >= 0 ;i--)
    {
        //899 变成 999   
        if(digits[i]+1 < 10)
        {
            digits[i]=digits[i]+1;
            return digits;
        }
        else
        {
            digits[i] = 0;
        }
        //否则啥事不做 等到跳到前一个继续做判断
    }
    //如果走出了循环 说明是极端 999 这种
    vector<int> ans(digits.size()+1,0);//比之前打一个
    ans[0]=1;//把第一个变成1 就好了
    return ans;
    
    }
};

接下来就是复习链表

206 反转链表

  • 怎么说呢需要一点点记忆
  • 需要三个值 pre cur next
  • pre 初始是为空的 这个很经典 反转常用
  • cur表示当前值 初始为head next初始为空 在循环里面挂上就好
  • 步骤 4不
    • 保存cur的下一个值
    • 改变指向
    • 修改pre的值
    • 修改cur的值
  • 注意点一:就是循环条件是cur不为空 因为 cur 每次都是指向下一个待处理的节点(cur=next)如果它为空说明到结尾了 还有就是注意返回值是pre 这个移动的值
  • cur->next=pre 这句话怎么读 就是 cur的下一个节点是pre
  • 你要想想一个节点和它指出去的是一起的 当你变换它的方向 你就要保存它原本指向的值 就是这么简单
  • 代码做了点点修改 如下
/**
 * 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) {
        ListNode* pre = nullptr;
        ListNode* cur =head;
        ListNode* next;
        while(cur!= nullptr)
        {
            next = cur->next;
            cur->next=pre;
            pre = cur;
            cur = next;//我指向的是下一个待处理的值
        }
        return pre;

    }
};
  • 第二遍复习 没啥问题

141 环形链表

  • 这个题就是判断链表有没有环
  • 这个快慢指针和我们之前说的那个快慢指针不一样 其实这个题算是一个比较特点的题目把
  • 看看之前的解释 在
  • while(quick!=nullptr&&quick->next!=nullptr)唯一会错的就是这句话,快指针在前面的 如果它为空那必然说明没有环 为什么还要判断 quit->next 如果你不判断这个 quick=quick->next->next;这句话会出错 你可以得到quick->next为空 但不可以再往下指下去
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == NULL || head->next ==NULL)
        {
            return false; //
        }
        ListNode* slow = head;
        ListNode* quick = head->next;
        //while(slow != NULL&&quick!= NULL )
        while(quick!=nullptr&&quick->next!=nullptr)
        {
            if(slow == quick)
            {
                return true;
            }
            else
            {
                slow=slow->next;//移动到下一个待比较的点
                quick=quick->next->next;//移动到下一个待比较的点
            }
        }
        return false;
    }
};

24两两交换链表中的节点

  • 我觉的这个画一个图可以理解(就是那个更改指向的图)(4个为一个单元)
  • 我觉的这个六步骤记住反而简单一点
    • 保存第二个值
    • 保存第三个值
    • 第一个值指向第三个值
    • 第二个值指向第四个值
    • 第三个指向第二个值
    • 更新temp
  • 我觉的最简单的写法就是一开始保存好 初始的四个值 全部保存下来 那样就不会乱掉 我们这边保存了第二个和第三个值 其实第四个也可以保存下来
  • 加了点注释 还有个需要注意的是temp是有实际的new的 因为他要next 而且拷贝了一份 就是为了保存一开始的next关系 因为后面这个变量会产生变化不保存一开始这个地址了
  • 写了一遍有一个地方错了 就是 下一个要加next 老是忘记
/**
 * 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* swapPairs(ListNode* head) {
        ListNode* temp =new ListNode(0);
        temp->next=head;
        ListNode* temp1 =temp;//这个主要用于返回
        while(temp->next!=nullptr &&temp->next->next!=nullptr)
        {
            //取出第二个点
            ListNode* node2 = temp->next;
            //取出第三个点
            ListNode* node3 = temp->next->next;
            //让第一个点指向(下一个)第三个点
            temp->next=node3;
            //让第二个点指向(下一个)第四个点
            node2->next=node3->next;
            //第三个点指向(下一个)第二个点
            node3->next=node2;
            //修改temp 想这 空 1 2 3 -> 空 2 1 3 1为新的temp 就是node2 第二个值
            temp = node2;
        }
        return temp1->next;


    }
};
  • 第二遍复习,修改做法 我们只要修改指向之前 把之前指向的关系 保存起来 然后一个一个修改就好了 不用那么复杂 看代码注释
/**
 * 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) {}
 * };
 */
 // 1 2 3 4      1 2 3 4    
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* temp =new ListNode(0);
        temp->next=head;
        ListNode* temp1 =temp;//这个主要用于返回
        while(temp->next!=nullptr &&temp->next->next!=nullptr)
        {
            //取出第二个点
            ListNode* node2 = temp->next;
            //取出第三个点

            ListNode* node3 = temp->next->next;
            //取出第四个点
            ListNode* node4 =temp->next->next->next;
            //让第一个点指向(下一个)第三个点
            temp->next=node3;
            //让第三个点指向(下一个)第二个点
            node3->next=node2;
            //第二个点指向(下一个)第四个点
            node2->next=node4;
            //修改temp 想这 空 1 2 3 -> 空 2 1 3 1为新的temp 就是node2 第二个值
            //修正新的值
                temp=node2;


        }
        return temp1->next;


    }
};

142 环形链表2 (数学)

  • 一个需要注意的就是 这次快慢指针都是从同一个点开始的
  • while判断不变
  • 如果相遇了 从head 和 slow同时一起走 相遇点就是入环点
  • 主要对比之前那个题
//0716修正写法 对比之前的快慢指针
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
   if(head==nullptr||head->next==nullptr)
        {   
            return nullptr;
        }
        ListNode* slow=head;
        ListNode* quick=head;
        while(quick!=nullptr&&quick->next!=nullptr)
        {
            slow=slow->next;
            quick=quick->next->next;
            if(quick==slow)
            {
               ListNode *ptr = head;
                while (ptr != slow) 
                {
                    ptr = ptr->next;
                    slow = slow->next;
                }
                return ptr;
            }
  
        }
        return nullptr;
    }
};

20 有效的括号

  • 有一个需要注意的就是
  • 什么时候入栈
    • 情况一 栈为空
      • 这边会出现两种情况 右边多 还有空的时候放入左 这两种情况都会导致最后栈剩余,所以最后判断情况就是for循环退出 还有没有元素剩余
    • 情况二 左右不匹配(连续的右边)只能放入
class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for(int i=0;i<s.length();i++)
        {
         char c =s[i];
         if(stk.empty()!= true)
         {
             char t = stk.top();
             if(t=='[' && c==']' || t=='(' && c==')' || t=='{' && c=='}')
             {
                 stk.pop();
             }
             else
             {
                 stk.push(c);
             }
         }
         else 
         {
             stk.push(c);   
         }
        }
        return stk.empty();
    }
};

155最小栈

  • 这边唯一容易错的就是我们每次放入元素不是要更新最小值就是去除栈顶当前元素的最小值进行放入。这边的做法就是一开始放入一个INT_MAX 其实我们也可以做一个判断 如果不为空,再放入一个最小值也可以我觉的

84 柱状图中最大的矩形

  • 这次是第三次做了

  • 之前有一个误区 其实左右边界的定义我们是很明确了,取出的元素作为高度我们也很明确了,还有一个我们要知道的是取出元素是作为最小值,计算面积的时候它是在中间的 举个例子 比如 789 1 计算8的面积的时候 他就是在最左边 2 3 9 8 1 先弹出了9 计算8是它是在最右边

  • 总而言之我就是向这样能考虑到所有情况么 显然是可以的

  • 我觉的这个题最难的是数组头插和尾差0 和栈的插入0 就是不理解数组不头插入0会越界 这个是咋无法理解 ,它有一个好处就是方便计算 举个例子 一个数1 10(下标 0 1)计算面积 就会是1-0-1=0*高度 这样就无法计算开始的那个矩阵(最后不断测试找到原因了 就是如果一开始我们数组不放入0 那么栈中放入的那个0!!! 这个下标最后对应的一定是一个大于0的数 所以数组最后的零会吧这个数取出作为高度 然后再stack.top取他到左边界就会报错)

  • 还有一个就是无论比它大还是小都要放入矩阵,这个很容易忘记

  • 第二遍复习

    • 首先要知道单调栈的应用场景是是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
    • 这个题可以转变成 以i为中心向左边找到第一个小于height[i]的位置和右边同理,这样就可以把问题转换了,如何找到呢,就可以使用单调栈
    • 理解边界的含义
    • 因为单调增 所以 高度左边的那个值必然是左边界,然后当前比较的比高度低的必然是右边界。
    • 切记占中放入的下标 一开始放入0在实际比较中表示数组的第一个数
    • 为什么数组开始插入一个0内,简单的说就是配对,让栈中0表示高度0,这样最后数组最后一个数0 比较等于 会直接把数组最后一个元素放入而不是把栈中第一个值取出作为下标进而取出高度 计算高度导致栈越界了。(我们把 《= 分为一种情况 》分为另外一种 切记 )
    • 必定要小于才取出 等于不动
    • 栈把第一个数组下下标放入
    • 别想那么多 记住就好
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> stack;
        heights.insert(heights.begin(),0);
        heights.push_back(0);//尾巴放入是让最后所有剩余的值作为高度进行计算
        //栈中放入的下标
        stack.push(0);//这个是方便一开始就可以比较  比较时候表示数组内第一个值
        int max_result = 0;
        for(int i=0;i<heights.size();i++)
        {
            //不能是if 判断 可以不断的取 所以用while
            while(heights[i]<heights[stack.top()])
            {
                int h = stack.top();//高度
                stack.pop();//及时删除 高度 方便取后面的
                int w = i- stack.top()-1;
                max_result=max(max_result,w*h);
            }
            //1处理好后 2 或者 一开始就大于等于
            stack.push(i);
        }
    }
};

42 接雨水(补充 高频)

连接

  • 这个顺序要求 是从高到底 遇到比之前高的说明出现凹槽进行 处理 ,这边处理一样用while循环
  • 1不同点在于这边一开始栈中防御第一个元素的下标 0
  • for循环从一开始
  • while情况处理除了 要加入的对应的值比之前的大 还要求栈不为空!!!!!! 认真看 栈不为空放前面 后面就出错了
  • 我自己写有一个错误就是在whike循环取出凹槽的高度后 不是还要取出左边界进行计算面积么 这边需要一个判断就是在取出 pop后判断此时栈的top是否为空 为空就啥都不做 不管他 让他一起取出中间然后为空退出
  • 765 1 9 模拟一下面积的计算 凹槽的面积是由三个三个独立的矩阵构成的 注意
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> stack;
        //从大到小排序是正常的
        stack.push(0);//把第一个元素下标放进去 到时候从第二个开始遍历
        int sum=0;
        for(int i=1;i<height.size();i++)
        {
            //要放入的(还没放)比栈大 说明形成了凹槽 进行处理
            while(stack.empty()!=true && height[stack.top()] < height[i])
            {
                int mid = stack.top();//低点的下标
                stack.pop();
                if ( stack.empty()!= true)
                {
                    //这个就是凹槽的左右两边的下标 - 中间凹处的高度
                    int h = min(height[i],height[stack.top()]) - height[mid];      
                    //宽度老样子
                    int w = i - stack.top()-1;
                    sum =sum + w*h;
                }
 
            }
            //特殊情况处理完 都要放入
            stack.push(i);
        }
        return sum;


    }
};
  • 第二遍复习
    • 找到左边和右边第一个大的说明形成凹槽了,所以是单调减。
    • 这个和上面有点区别 1数组不需要进行操作 2 栈放入第一个坐标0 遍历从1开始 3进行空的判断 (必须要 举例 1234)4 高度计算
class Solution {
public:
    int trap(vector<int>& height) 
    {
        stack<int> stack;
        stack.push(0);
        int sum=0;
        for(int i=1;i<height.size();i++)
        {
            //这两个empty()判断是必须的 举个例子 1234 
            //不用记住那么多 就是用到就判断
            while(stack.empty()!=true && height[stack.top()]<height[i])
            {
                int mid =stack.top();//最低点
                stack.pop();
                if(stack.empty()!=true)
                {
                     int h = min(height[i],height[stack.top()]) - height[mid];      
                     int w =i-stack.top()-1;//
                      sum =sum + w*h;
                }
                 //特殊情况处理完 都要放入
               
            }
             stack.push(i);
        }
        return sum;

    }
};

739 每日温度(补充 单调栈- 第二遍复习

- 没什么问题
- 在上一题的基础上继续改进
- 首先就是nums1构建map 方便o(1)的查找
- 特别注意初始值 如果找到到下一个比我大的应该返回多少,初始值就设置为多少 )!!!
  • 通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。当然接雨水和 矩阵的最大面积这种抽象的 要记住也行 时间复杂度是0(n)
    [leetcode]第一遍+第二遍复习_第13张图片
  • 自己写了一遍 发现不是很难 和接雨水的区别在于 whileI里面不用加if判断了因为这个只需要两个当前和栈中第一个值做计算 和接雨水矩阵三个值的不同
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures)
    {
        stack<int> stack;
        vector<int> result(temperatures.size(),0);//
        stack.push(0);//把第一个放入方便比较
        for(int i=1;i<temperatures.size();i++)
        {
            //你想想正常肯定 从大到小因为啥都做不了 出现大的了 就可以计算答案了 合理
            while(stack.empty()!=true && temperatures[stack.top()] < temperatures[i])
            {
                result[stack.top()] = i- stack.top();//这个就是计算间隔几天了
                stack.pop();//删除顶端元素
            }
            stack.push(i);
        }
        return result;

    }
};
  • 第二遍复习
    • 2个值进行计算 区别于上面的三个值
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) 
    {
        stack<int> stack;
        stack.push(0);//方便比较
        vector<int> result(temperatures.size(),0);//保存结果
        //单调减
        for(int i=1;i<temperatures.size();i++)
        {
            while( stack.empty()!=true &&  temperatures[i]>temperatures[stack.top()])
            {
                //这边不需要想接雨水 需要上一个值 还加个if判断
                result[stack.top()]=i-stack.top();
                stack.pop();
            }
            stack.push(i);
        }
        return result;

    }
};

496 下一个更大元素1

  • 这边主要是要想到for循环找一个元素在另外一个数组哪里出现 这种查找我们用hash其他都还要
  • 有一个容易错的就是这个题 pop 记得删除 还要可能存在元素 不存在注意看题
class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) 
    {
        vector<int> result(nums1.size(),-1);//初始放入-1 
        stack<int> stack;
        if(nums1.size() == 0) return result;
        unordered_map<int,int> map;//键是值 值是下标 我们要利用这个在结果集对应位置放入结果
        for(int i =0;i<nums1.size();i++)
        {
            map[nums1[i]]=i; //题目说了不包含重复的元素
        }
        stack.push(0);
        for(int i =1 ; i<nums2.size();i++)
        {
            while(stack.empty()!= true && nums2[stack.top()] < nums2[i])
            {
                //查看一下 比要插入小的这个值在不在map里买呢
                if(map.count(nums2[stack.top()]) == 1)
                {
                    //存在就取出他的下标
                    int index = map[nums2[stack.top()]];
                    result[index] = nums2[i];//题目要放入第一个比它大的值 所以这边把值放入
                    
                }
                stack.pop();//记得及时删除 这边删除两个意思 一个是存在处理好删除 一个是不存在也删除
                
                

            }
            stack.push(i);
        }
        return result;

    }
};
  • 第二遍复习
    • 没什么问题
    • 在上一题的基础上继续改进
    • 首先就是nums1构建map 方便o(1)的查找
    • 特别注意初始值 如果找到到下一个比我大的应该返回多少,初始值就设置为多少

503 下一个更大元素2

  • 中等题

[leetcode]第一遍+第二遍复习_第14张图片

  • 这个题主要是想到循环类的问题如何解决 有人想两个数组拼接 或者下面这这种节省空间的做法 很巧妙 栈中

连接

// 版本二
class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        vector<int> result(nums.size(), -1);
        if (nums.size() == 0) return result;
        stack<int> st;
        int n = nums.size();
        for (int i = 0; i < nums.size() * 2; i++) { 
            // 模拟遍历两边nums,注意一下都是用i % nums.size()来操作
            while (!st.empty() && nums[i % n] > nums[st.top()]) {
                result[st.top()] = nums[i % n];
                st.pop();
            }
            st.push(i % n);
        }
        return result;
    }
};

239 滑动窗口的最大值

  • 其实就是实现一个单调队列
  • 单调队列 就是实现每次添加一个元素 只能符合单调递增或者单调递减 两种规则
  • 这个题的代码随想录有动态图
  • 我们这边要实现的是单调递增的队列(头部(新加入的)---尾部(过去加入的)) 为什么呢 如果 1过去的值比我小 还比我早加入(下标比我小)比我2早淘汰为啥选你啊
  • 为了维护这个递增单调队列(区别于普通的队列)我们知道普通队列的底层实现就是deque 所以我们自己构造一个
  • 如果我们要删除元素 首先就是 单调队列不为空 第二个就是 删除的元素 == 队列的最后一个元素(因为这个元素可能早就不符合加入的规则被删除了)
  • 如果要加入元素 首先不能为空 因为我们要做判断 第二个就是和头部的元素进行判断(我是要加在头部的 while逐个判断比我小的都去掉 ) 然后把自己加入进去
  • 下面第二个我修改的 比较好理解 第三个是我自己写的代码 就是固定头部是新加入的 尾部是旧的 递增序列 每次取尾部
class Solution {
private:
    class MyQueue 
    { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);

        }
        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
            que.push(nums[i]);
        }
        result.push_back(que.front()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};


class Solution {
private:
    class MyQueue 
    { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int value) {
            if (!que.empty() && value == que.back()) {
                que.pop_back();
            }
        }
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int value) {
            while (!que.empty() && value > que.front()) {
                que.pop_front();
            }
            que.push_front(value);

        }
        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return que.back();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
            que.push(nums[i]);
        }
        result.push_back(que.front()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};


class Solution {
public:
    class MyQueue
    {
        public:
        //单调队列
        deque<int> que;
        MyQueue()
        {}
        void push(int value)//我们指定从头部放入
        {
            //先把比我小的都去除掉
            while(que.empty()!= true && value > que.front())
            {
                que.pop_front();
            }
            //处理好后放入
            que.push_front(value);
        }

        void pop( int value)//我们指定从尾部去除 去除旧的
        {
            //这边做判断 是因为 这个元素可能已经不存在了
            if( que.empty()!= true && que.back() == value)
            {
                que.pop_back();
            }
        }
        //返回尾巴的元素 最大值 返回值为整形
        int back()
        {
            return que.back();
        }
    };
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;//存放结果
        //为什么两个循环呢 两个阶段被 前面k个是一直放入 一个最大值
        //后面都是放入一个取出一个 最大值每次都有一个
        for(int i =0 ; i<k ; i++)
        {
            que.push(nums[i]);
        }
        //记得把第一个加入进去 老是忘记
        result.push_back(que.back());
        for(int i =k;i<nums.size();i++)
        {
            //这两个顺序无所谓吧
            que.push(nums[i]);
            que.pop(nums[i-k]);
            result.push_back(que.back());
        }        
        return result;
    }
};
  • 第二遍复习
    • 主要是总结,代码都很简单
    • 习惯可以定一下 头部插入 尾部删除
    • 单调队列 删除 和 插入方法 注意判断空
    • 窗口 (窗口大小可能固定可能不固定)(一串连续数组)

你可能感兴趣的:(leetcode周记录,leetcode)