[leetcode刷题]HOT100

C++ leetcode

文章目录

  • Hot 100
    • 2两数之和(中等)
    • 19删除链表的倒数第N个节点(中等)
    • 31下一个排序(数组)(中等)(特殊)
    • 39组合总和(中等)(回溯)
    • 48 旋转图像(中等)(找规律)
    • 64 最小路径和(中等)
    • 75颜色分类(中等)(双指针)(重要)
    • 96不同的二叉搜索树(中等)(动态规划)
    • 114二叉树展开为链表(中等)
    • 128 最长连续序列(中等)(hash)(特别)
    • 136只出现一次的数字(简单)(位运算)
    • 139单词拆分(中等)(动态规划)(背包)
    • 152 乘积最大子数组(中等)(动态规划)
    • 876 链表的中间节点(简单)
    • 148排序链表(中等)(归并 快排)
    • 160相交链表(简单)(特殊)
    • 207. 课程表(中等)(待补充)(拓扑)
    • Sort的实现原理及相关问题
    • 215 数组中的第k个最大元素
    • 221 最大正方形(中等)(特殊动态规划)(待复习)
    • 234回文链表(简单)(重要)
    • 238 除自身以外数组的乘积(中等)
    • 240搜索二维矩阵2
    • 279完全平方数(中等)(背包 动态规划)(待完善)
    • 287寻找重复数(中等)(二分 快慢)(特别注意)(特别重要)(待复习)
    • 347 前k个高频元素(中等)(重要)
    • 394. 字符串解码(中等)(辅助栈)(重要)
    • 406 根据身高重建队列(中等)(贪心)(特别)
    • 448 找到所有数组中消失的数字(简单)(数组的原地修改)
    • 461 汉明距离(简单)(位运算)
    • 494 目标和(中等)(回溯)(背包)
    • 543 二叉树的直径(简单)(递归)(特别)
    • 560 和为k的子数组(中等)(前缀+哈希)
    • 617合并二叉树(简单)(可以复习)
    • 416 分割等和子集(中等)(背包问题 )
    • 538把二叉树转换成累加树(中等)(技巧)
    • 581最短无序连续子数组(中等)(规律)
    • 437 路劲之和3(中等)(回溯 + 前缀和)
    • 621任务调度器(中等)
    • 3 无重复字符的最长子串(中等)(滑动窗口)(代码可优化)(dp)
    • 337 打家劫舍3(中等)(递归+动态规划)
    • 198 打家劫舍1(中等)
    • 213打家劫舍2(中等)

Hot 100

2两数之和(中等)

[leetcode刷题]HOT100_第1张图片
[leetcode刷题]HOT100_第2张图片

  • 写代码建议先写上步骤 再具体写
    • 比如空判断 补上0节点
    • 求和 记得加上进位
    • 求下一个 进位 和真实位数值
    • 创建新的节点挂上
    • 新链表 temp后移
    • l1 和 l2 后移
    • 退出循环还需要进行一步判断
/**
 * 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* p=new ListNode(-1);//头结点,为了方便后面用new
        ListNode* re=p;//结果结点
        int jin=0;//进位
        while(l1!=NULL||l2!=NULL){//l1和l2中有一者不为空
            if(l1==NULL)
                l1=new ListNode(0);//l1比l2短,所以将l1补0
            if(l2==NULL)
                l2=new ListNode(0);//l2比l1短,所以将l2补0
            int n=l1->val+l2->val+jin;//计算l1和l2的和
            int realNum=n%10;//记录到新结点中的数字
            jin=n/10;//进位
            p->next=new ListNode(realNum);将新节点添加进来
            p=p->next;//p后移
            l1=l1->next;//l1后移
            l2=l2->next;//l2后移
        }
        if(jin>0)//如果加到最后一位仍然有进位的时候
            p->next=new ListNode(jin);//把进位直接补全到最后
        return re->next;//由于一开始有头结点,所以答案是re->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* addTwoNumbers(ListNode* l1, ListNode* l2) 
    {
        //创建一个岗哨 
        ListNode* temp =new ListNode(-1);
        ListNode* temp1 = temp;
        int jin =0; //创建一个标量保存进位
        while(l1!=NULL || l2!=NULL) //退出条件就是两个同时为空
        {
            //特殊处理 为空就挂上一个1
            if(l1 == NULL)
            {
                l1= new ListNode(0);
            }
            if(l2 == NULL)
            {
                l2 =new ListNode(0);
            }
            //求和(包括上一次的进位)
            int n = l1->val+l2->val+jin;
            //求进位 和实际放入的值 下一次用
            jin = n/10;
            int realsum = n % 10;
            //挂入新的节点
            temp->next = new ListNode(realsum);
            temp=temp->next;//后移 保存下一个节点
            //全部后移
            l1=l1->next;
            l2=l2->next;
        }
        if(jin>0)
        {
            temp->next=new ListNode(1);
        }
    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) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) 
    {
        //首先 创建 岗哨 
        //一个变量表示进位 当前节点和表示 两个节点和相加+进位数
        //分情况如果两个不为空 这样
        //如果如果单个为空 继续
        //如果两个为空 终止
        ListNode*temp=new ListNode(-1);
        ListNode*temp1=temp;
        int sum=0;//表示综合
        int jin=0;//表示进位
        while(l1!=nullptr || l2!=nullptr)
        {
            if(l1!=nullptr && l2!=nullptr)
            {       
                sum=l1->val+l2->val+jin;
                if(sum >=10)
                {
                    sum=sum%10;
                    jin=1;
                }
                else
                {
                    jin=0;
                }
                temp1->next=new ListNode(sum);
                temp1=temp1->next;
                l1=l1->next;
                l2=l2->next;
            }
            else if(l1!=nullptr && l2 ==nullptr)
            {
                sum = l1->val+jin;
                if(sum>=10)
                {
                    sum=sum%10;
                    jin=1;
                }
                else
                {
                    jin=0;
                }
                temp1->next= new ListNode(sum);
                temp1=temp1->next;
                l1=l1->next;
               
            }
            else//l1为空 l2 不为空
            {
                sum = l2->val+jin;
                if(sum>=10)
                {
                    sum=sum%10;
                    jin=1;
                }
                else
                {
                    jin=0;
                }
                temp1->next= new ListNode(sum);
                temp1=temp1->next;
                l2=l2->next;
            }
        }
        if(jin==1)
        {
            temp1->next=new ListNode(1);
        }
        return temp->next;

    }
};

19删除链表的倒数第N个节点(中等)

[leetcode刷题]HOT100_第3张图片

  • 很容易想到最快的方法是双指针

  • 分析过程

    • 首先我想到可能删除第一个节点 所有设置岗哨
    • 第二个想到的问题,不同与找到那个节点,删除需要找到倒是第k+1个节点,所以循环次数应该是k+1次。这边我第一次写错了…写成了k-1
    • 第三个遗漏的问题就是记得及时释放空间,岗哨 。
  • 其他方法有1计算长度 2 通过栈的方式很容易找到要删除的节点和前驱节点。时间复杂度和空间复杂度都是o(n)

/**
 * 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* removeNthFromEnd(ListNode* head, int n) 
    {
        //我觉的先创建一个岗哨 如果删除的是第一个节点也好返回
        ListNode* temp = new ListNode(-1);
        temp->next = head;

        ListNode* temp1=temp;
        ListNode* temp2=temp; 
        //举个例子 这次我不是找到那个节点 我是要找到删除节点的前一个方便我删除所以n-1
        for(int i = 0;i<n+1;i++)
        {
            temp2=temp2->next;
        }
        while(temp2!=NULL && temp1!=NULL)
        {
            temp2=temp2->next;
            temp1=temp1->next;
        }
        temp1->next=temp1->next->next;
        ListNode* ans =temp->next;
        delete temp;
    return ans;
    }
};

31下一个排序(数组)(中等)(特殊)

[leetcode刷题]HOT100_第4张图片
[leetcode刷题]HOT100_第5张图片

[leetcode刷题]HOT100_第6张图片

  • 总而言之
    • 第一步 从后向前找到第一个相邻升序 i 和 j
    • 第二步 从后向前(到j)找到第一个大于i的值下标设为k
    • 第三步 交换 i k 并且reserve j 到 end
    • 如果整个循环找到到第一步 那就整个翻转返回
    • 第三步和第四步可以合并写在一起,如下代码
    • 比较重要的是我们根据while设置两个退出条件 退出后记得判断哪一种退出的
class Solution {
public:
    void nextPermutation(vector<int>& nums) 
    {
        //1第一步 从后向前 找升序
        int i =nums.size()-2;//如果用i 和 i--要用i--进行越界判断。 所以我们用i 和 i++ 对i进行判断就好
        while(i>=0  && nums[i] >= nums[i+1])
        {
            i--;//移动到下一个
        }
        //跳出循环两种可能 1越界了 2要么找到了
        if(i>=0)
        {
            int k = nums.size()-1;
            //这边不同判断k的边界 最坏的情况就是k=j
            while(nums[k]<=nums[i])
            {
                k--;
            }
            //第二部
            swap(nums[i],nums[k]);
            //第三步
            reverse(nums.begin()+i+1,nums.end());
        }
        else
        {
            //第四部 特殊情况
            reverse(nums.begin(),nums.end());
        }
    
    }
};

class Solution {
public:
    void nextPermutation(vector<int>& nums) 
    {
        //1第一步 从后向前 找升序
        int i =nums.size()-2;//如果用i 和 i--要用i--进行越界判断。 所以我们用i 和 i++ 对i进行判断就好
        while(i>=0  && nums[i] >= nums[i+1])
        {
            i--;//移动到下一个
        }
        //跳出循环两种可能 1越界了 2要么找到了
        if(i>=0)
        {
            int k = nums.size()-1;
            //这边不同判断k的边界 最坏的情况就是k=j
            while(nums[k]<=nums[i])
            {
                k--;
            }
            //第二部
            swap(nums[i],nums[k]);
            //第三步
        }
          reverse(nums.begin()+i+1,nums.end());

    
    }
};
  • 第二次复习
    • 重新复习一下步骤 代码很容易写出来
    • 还是那个地方 while 移动处理设置边界 和 跳出条件 最后要记得判断哪一种情况跳出的

39组合总和(中等)(回溯)

[leetcode刷题]HOT100_第7张图片
[leetcode刷题]HOT100_第8张图片

  • 第一个想法就是回溯,非a就是b 如果和超过了那就回退 并且for循环后面的就不走了,提前排序

  • 看了题解发现想法一样

    • 有一个需要注意的 我们不要同时出现 2 3 和3 2 。这个排序问题 所以就要设置start 每次都比之前的大。
    • 第二个就是可以重复!!! 所以start从i开始而不是从i+1开始
  • 特别注意第二个代码,我对题解进行的修改,用时更少,把终止条件放在for循环 及时退出(特殊)。特别注意退出前把当前位置的sum回溯。一个for循环控制的是一个格子,所有退出吧当前格子的数减去 返回上一个格子继续考虑

[leetcode刷题]HOT100_第9张图片
[leetcode刷题]HOT100_第10张图片

  • 这段代码sum可以传入形参,修改成如下backtracking(candidates,target,sum+candidates[i],i);
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    
    void backtracking(vector<int>& candidates,int& target,int& sum,int start)
    {
        //递归终止条件 2种
        if(sum == target)
        {
            result.push_back(path);
        }
        if(sum > target)
        {
            return;
        }
        //递归回溯
        for(int i =start;i<candidates.size();i++)
        {
            sum = sum+candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i);//特别注意这边传入的是i
            sum =sum-candidates[i];//两种情况 符合条件条件如result 回溯 不符合 一样回溯
            path.pop_back();

        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) 
    {
        result.clear();
        path.clear();
        //sort(candidates.begin(),candidates.end());
        int sum = 0;
        backtracking(candidates,target,sum,0);
        return result;
    }
};

[leetcode刷题]HOT100_第11张图片

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    
    void backtracking(vector<int>& candidates,int& target,int& sum,int start)
    {
        //递归终止条件 2种
        if(sum == target)
        {
            result.push_back(path);
        }
        //递归回溯
        for(int i =start;i<candidates.size();i++)
        {
            sum = sum+candidates[i];
            if (sum > target) 
            {
                sum = sum - candidates[i];
                break;
            }
            
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i);//特别注意这边传入的是i
            sum =sum-candidates[i];//两种情况 符合条件条件如result 回溯 不符合 一样回溯
            path.pop_back();

        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) 
    {
        result.clear();
        path.clear();
        sort(candidates.begin(),candidates.end());
        int sum = 0;
        backtracking(candidates,target,sum,0);
        return result;
    }
};
  • 第二遍复习
    • 代码重写就一个地方写错了 for循环内是 下标是i 不是 start …

48 旋转图像(中等)(找规律)

[leetcode刷题]HOT100_第12张图片

  • 第一个想法就是 从坐标中找到规律进行移动,或者多次翻转(但是没找到规律啊)

  • 看了题解:顺时针90度应该是左上/右下对角线翻转+左右翻转,或者右上/左下对角线翻转+上下翻转。时间复杂度n^2

  • 左右翻转可以用双指针 或者 第二种方式
    [leetcode刷题]HOT100_第13张图片

class Solution {
public:
    void rotate(vector<vector<int>>& matrix)
    {
        int n =matrix.size();
        //左上 右下进行对折翻转 举个例子就是坐标对调 遍历一个三角就好
        for(int i =0;i < n ;i++)
        {
            for(int j =0;j<i;j++)
            {
                swap(matrix[i][j],matrix[j][i]);
            }
        }
        // 再沿竖线进行左右翻转 行数不变 列数 = n-i 和i
        for(int i =0;i<n;i++)
        {
             for(int j = 0, k = n - 1; j < k ; j++, k--) //类似于双指针,由两端向中心靠齐
                swap(matrix[i][j],matrix[i][k]);        
 
        }



    }
};

class Solution {
public:
    void rotate(vector<vector<int>>& matrix)
    {
        int n =matrix.size();
        //左上 右下进行对折翻转 举个例子就是坐标对调 遍历一个三角就好
        for(int i =0;i < n ;i++)
        {
            for(int j =0;j<i;j++)
            {
                swap(matrix[i][j],matrix[j][i]);
            }
        }
        // 再沿竖线进行左右翻转 行数不变 列数 = n-i 和i
        for(int i =0;i<n;i++)
        {
             //for(int j = 0, k = n - 1; j < k ; j++, k--) //类似于双指针,由两端向中心靠齐
               // swap(matrix[i][j],matrix[i][k]);        

                for(int j =0;j<= (n-1)/2;j++)
                {
                    swap(matrix[i][j],matrix[i][n-1-j]);
                }
        }



    }
};
  • 第二遍复习
    • 没啥问题 基本一次过
    • 明确外层是行 内层是列 是三角还是一半的长度
    • swap(matrix[i][j],matrix[i][n-j-1]); 这个要减去-
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) 
    {
        int n=matrix.size();
        //我先左上到右下翻转把
        //就是先遍历一个三角 
        for(int i=0;i<n;i++)//遍历第几行
        {
            for(int j=0;j<i;j++)//固定每行几个
            {
                swap(matrix[i][j],matrix[j][i]);
            }
        }
        //左右翻转 遍历所有的行 一半的列
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n/2;j++)
            {
                swap(matrix[i][j],matrix[i][n-j-1]);
            }
        }

    }
};

64 最小路径和(中等)

[leetcode刷题]HOT100_第14张图片

  • 很明显就是dp,写了一遍没啥问题
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) 
    {
        int n = grid.size();
        int m=grid[0].size();
        vector<vector<int>> dp(n,vector<int>(m));
        //初始化
        dp[0][0] =grid[0][0];
        //初始化第一列
        for(int i = 1;i<n;i++)
        {
            dp[i][0]=grid[i][0]+dp[i-1][0];
        }
        for(int i = 1;i<m;i++)
        {
            dp[0][i]=grid[0][i]+dp[0][i-1];
        }
        for(int i =1;i<n;i++)
        {
            for(int j=1;j<m;j++)
            {
                dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }
        return dp[n-1][m-1];

    }
};

  • 做了太多次了 就不重新做了

75颜色分类(中等)(双指针)(重要)

[leetcode刷题]HOT100_第15张图片

[leetcode刷题]HOT100_第16张图片

  • 方法一就和奇偶排序一样(或者说快慢指针 k 和 i 遇到奇数进行交换 不存在奇数换奇数 因为k都是经过i走过的路 k走过的都会变成偶数或者自己和自己更换) 只是进行了两次而已 o(n)
  • 方法二 排序 无论是快排 归并 时间复杂度都是 nlogn
  • 方法三 就是遇到0放在开头 遇到2放在结尾 ,但是这个有很多注意事项。就是放在结尾的你需要考虑2替换2的情况 所以把原先if更换为while进行遍历替换 这个我没想到 这个写法最简便
while (i <= p2 && nums[i] == 2)
                swap(nums[i], nums[p2--]);
  • 还有一个就是把判断2的放在0的前面是有原因的,更换完可能吧0换在i的位置,此时不能后移要继续进行判断。否则就是我代码2的复杂写法
    [leetcode刷题]HOT100_第17张图片
class Solution {
public:
    void sortColors(vector<int>& nums) 
    {
        int n =nums.size();
        int k=0;
        for(int i =0;i<n;i++)
        {
            if(nums[i]==0)
            {
                swap(nums[k++],nums[i]);
            }
        }
        for(int i =k;i<n;i++)
        {
            if(nums[i]==1)
            {
                swap(nums[k++],nums[i]);
            }
        }

    }
};
class Solution {
public:
    void sortColors(vector<int>& nums) 
    {
        int n =nums.size();
        int k=0;
        int k2=n-1;
        for(int i =0;i<=k2;i++)
        {
            if(nums[i]==0)
            {
                swap(nums[i],nums[k++]);
            }
            if(nums[i]==2)
            {
                //找到一个不是2的位置
                while(i<k2 && nums[k2]==2) k2--;
                if(i<k2)
                {
                    swap(nums[i--],nums[k2--]);
                }
            }
        }


    }
};
class Solution
{
public:
    void sortColors(vector<int> &nums)
    {
        int n = nums.size();
        int p0 = 0, p2 = n - 1;
        for (int i = 0; i <= p2; i++)
        {
            while (i <= p2 && nums[i] == 2)//防止p2位置原本就是2,交换后2被遗漏在了前面
                swap(nums[i], nums[p2--]);
            if (nums[i] == 0)
                swap(nums[i], nums[p0++]);
        }
    }
};

  • 第二遍复习
    • 第一个就是 简便写法 while内部的判断条件
    • 第二个 2的判断放在前面
    • 第三个 for的判断条件
    • 这个简便方法 太容易出错了
    • 常规写法简单一点 先移动0再移动1 也比较不会出错 时间复杂度也不高
class Solution {
public:
    void sortColors(vector<int>& nums) 
    {
        int n =nums.size();
        int k=0;
        for(int i=0;i<n;i++)
        {
            if(nums[i]==0)
            {
                swap(nums[i],nums[k++]);
            }
        }
        for(int i=k;i<n;i++)
        {
            if(nums[i]==1)
            {
                swap(nums[i],nums[k++]);
            }
        }

    }
};

96不同的二叉搜索树(中等)(动态规划)

[leetcode刷题]HOT100_第18张图片
[leetcode刷题]HOT100_第19张图片

  • 看到题目属实没啥思路…难道思考每次多一个数会多几种变化?

[leetcode刷题]HOT100_第20张图片[leetcode刷题]HOT100_第21张图片

  • G(n)其中的n表示有几个连续的树 数字1-3 和 2-4种类结果是一样的。
  • 外层循环控制整数变化 内层循环控制跟的变化 比如整数 2(根有1 2) 整数3(跟有1 2 3)
  • 属实有点难想了
class Solution {
public:
    int numTrees(int n) 
    {
        vector<int> dp(n + 1, 0);//下标代表整数 所以n+1 最大值+1
        dp[0] = 1;
        dp[1] = 1;
        //外层循环从2开始 0和1初始化了
        for(int i =2;i<=n;i++)
        {
            //内层循环控制跟f累加
            for(int j = 1;j<=i;j++)
            {
                dp[i]=dp[i]+dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }
};

  • 第二遍复习
    • 和之前差不多,有一点点不同
    • 外层循环遍历遍历dp的下标 内层遍历递推公式中j
    • 举个例子就很容易例如 dp5 =dp0dp4 dp1dp3。。。设一个下标为j 从0到i-1 另外一个就是n-1-j

114二叉树展开为链表(中等)

[leetcode刷题]HOT100_第22张图片

  • 和之前做过的有一题很像,题目要求先序遍历的顺序一致,第一个想法就是先序序列递归,需要一个保存上一个节点,不断next当前节点,然后当前节点作为pre节点。
  • 我的第一次写法不符合题目left为空的要求 我们要先把它保存下来 然后再遍历处理
/**

/**
 * 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<TreeNode*> ans;
    void dfs(TreeNode* root)
    {
        //递归终止条件
        if(root == nullptr) return;
        //先序遍历 跟左右
        //逻辑处理
        ans.push_back(root);
        dfs(root->left);
        dfs(root->right);

    }
    void flatten(TreeNode* root) 
    {
        if(root ==NULL) return;
        dfs(root);
        for(int i=0;i<ans.size()-1;i++)//前后挂钩
        {
            ans[i]->right=ans[i+1];
            ans[i]->left=NULL;
        }
    }
};

128 最长连续序列(中等)(hash)(特别)

[leetcode刷题]HOT100_第23张图片

  • 注意存在重复的数字的
  • 我觉的要么就排序 找到所有的连续长度(两两差值 《=1 最后收尾元素相减+1知道长度)
  • 题解方式如下 外层循环需要 O(n) 的时间复杂度,只有当一个数是连续序列的第一个数的情况下才会进入内层循环利用前驱判断 剩下很多功夫
  • 不知道为啥 auto的方式更省时间

[leetcode刷题]HOT100_第24张图片
[leetcode刷题]HOT100_第25张图片

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> hash;
        for(auto x : nums) hash.insert(x);    //放入hash表中
        int res = 0;
        for(auto x : hash)
        {
            if(!hash.count(x-1))
            {
                int y = x;   //以当前数x向后枚举
                while(hash.count(y + 1)) y++;
                res = max(res, y - x + 1);  //更新答案
            }
        }
        return res;
    }
};


class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> set;
        //全部插入 取出重复
        for(int i =0;i<nums.size();i++)
        {
            set.insert(nums[i]);
        }
        int result=0;//保存最大长度
        for(int i =0;i<nums.size();i++)
        {
            if(set.count(nums[i]-1)==0)//表示前驱不存在
            {
                int x =nums[i];//保存第一个值
                int y = x;
                while(set.count(y)==1) y++;//退出时指向第一个不存在的下标
                result=max(result,y-x);
            }
        }
        return result;

    }
};
  • 第二遍复习
    • 第一眼真以为dp做确实没想到hash做
    • 简单的说就是把所有的值放入map中 再一个for循环 找哪些没有前驱的数字 说明就是起点 在判断中加一个while循环看看后继有多长。 没啥问题 代码一遍过
class Solution {
public:
    int longestConsecutive(vector<int>& nums) 
    {
        unordered_set<int> hash;
        for(auto x:nums) hash.insert(x);
        int result=0;
        for(auto x:hash)
        {
            if(hash.count(x-1)==0)
            {
                int y=x;
                while(hash.count(y)==1) y++;//最后退出指向一个不存在的
                int ans = y-x;
                result=max(result,ans);
            }
        }
        return result;

    }
};

136只出现一次的数字(简单)(位运算)

[leetcode刷题]HOT100_第26张图片

  • 位运算求解 或者一次循环判断求解,但是第二个方法注意就是需要先排序,题目没有排序...如果题目又说排序 第二种方法可能更容易(所以第一种方法更快)
class Solution {
public:
    int singleNumber(vector<int>& nums) 
    {
        int result =0;
        for( auto x : nums)
        {
            result=result^x;
        }
        return result;

    }
};
class Solution {
public:
    int singleNumber(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
       for(int i =1;i<nums.size();i+=2)
       {
           if(nums[i]!=nums[i-1])
           return nums[i-1];//一定是前面那个数 i移动是偶数上 不同的数在奇数上
       }
       //循环走完了 还没有退出 那就是出现在最后一个位置了
       return nums[nums.size()-1];

    }
};

139单词拆分(中等)(动态规划)(背包)

[leetcode刷题]HOT100_第27张图片

  • 在背包问题总结过这个题,值得注意的是时间复杂度是哦^3因为substr0(n)的时间复杂度

152 乘积最大子数组(中等)(动态规划)

[leetcode刷题]HOT100_第28张图片

  • 很容易想到 dp动态规划 下标表示以当前数值为结尾的子数组。但是需要想到 可能存在 23-2 此时以-2为结尾的最大值是-2 但是如果下一个数也是负数那就需要保留整串了,
    -对于乘法,我们需要注意,负数乘以负数,会变成正数,所以解这题的时候我们需要维护两个变量,当前的最大值,以及最小值,最小值可能为负数,但没准下一步乘以一个负数,当前的最大值就变成最小值,而最小值则变成最大值了
  dp_max[i]=max(max(dp_max[i-1]*nums[i],nums[i]),dp_min[i-1]*nums[i]);
  dp_min[i]=min(min(dp_min[i-1]*nums[i],nums[i]),dp_max[i-1]*nums[i]);
  • 看了题解思路写了代码 有一个地方写错了就是result最大值初始化为nums[0]忘记了
  • 当然可以空间优化 因为每次只用到前一个值,第二个代码有简介版本
class Solution {
public:
    int maxProduct(vector<int>& nums) 
    {
        int n =nums.size();
        if(n == 0) return 0;
        else if(n == 1) return nums[0];
        vector<int> dp_min(n);
        vector<int> dp_max(n);
        dp_min[0]=nums[0];
        dp_max[0]=nums[0];
        int result=nums[0];
        for(int i=1;i<n;i++)
        {
            dp_max[i]=max(max(dp_max[i-1]*nums[i],nums[i]),dp_min[i-1]*nums[i]);
            dp_min[i]=min(min(dp_min[i-1]*nums[i],nums[i]),dp_max[i-1]*nums[i]);
            result=max(result,dp_max[i]);
        }
        return result;

    }
};
  • 第二遍复习
    • 之前做过了 就不重复了

876 链表的中间节点(简单)

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};


  • 第二遍复习
    • 就是自己举个三个节点和4个节点的例子测试一下就好

关于下一题的重要补充

  • quick = head 偶数指向中间一组的后一个 奇数指向中间
  • quick = head->next 偶数指向中间一组的前一个 奇数指向中间
  • quick = head->next->next 偶数指向中间一组的前一个 奇数指向中间的前一个

148排序链表(中等)(归并 快排)

认真总结

[leetcode刷题]HOT100_第29张图片

[leetcode刷题]HOT100_第30张图片

  • 题目没有说不能修改节点内的值,算是取巧的方法
  • 归并排序 和 快速排序
  • 自己写了一遍如下,特别注意链表的断开 和 快慢指针的设置
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptrptr) {}
 *     ListNode(int x) : val(x), next(nullptrptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) 
    {
        //1递归终止条件 只有一个节点 或者一个节点没有
        if (head == nullptr || head->next == nullptr) 
        {
            return head;
        }
        //2当前层的逻辑 一分为二 把一个链表彻底分为两个链表
        ListNode* midNode = middleNode(head);
        ListNode* rightHead = midNode->next;//下一个节点为第二个链表的头节点
        midNode->next = nullptr;//第一个链表的尾部设置为NULL为了后面有序链表的合并
 
        //3递归调用
        ListNode* left = sortList(head);
        ListNode* right = sortList(rightHead);

        // 4 归并 有序链表的合并
        return mergeTwoLists(left, right);
    }
    
    //  找到链表中间节点(876. 链表的中间结点)
    ListNode* middleNode(ListNode* head) 
    {
        //切记 我们要返回的是第一个链表的最后一个节点slow
        //所以不能用fast=head 对于偶数个节点 会分成3 和 1
        //只能用 fast=head->next   =head->next->next
        ListNode* slow = head;
        ListNode* fast = head->next->next;//这样确保所得中是偏向前的
        //ListNode* fast = head;

        while (fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
       
        return slow;
    }

    // 合并两个有序链表(21. 合并两个有序链表)
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* temp = new ListNode(-1);
        ListNode* temp1 = temp;

        while(l1 != nullptr && l2 != nullptr) {
            if(l1->val < l2->val) 
            {
                temp->next = l1;
                l1 = l1->next;
                temp = temp->next;
            } else 
            {
                temp->next = l2;
                l2 = l2->next;
                temp = temp->next;
            }

            
        }
        if(l1!=nullptr)
        {
            temp->next=l1;
        }
        if(l2!=nullptr)
        {
            temp->next=l2;
        }
        return temp1->next;
    }
};


160相交链表(简单)(特殊)

  • 第二次遇到,写错了,错误代码如下,原因在于如果a走到了空,此时a=b就算一步,不能a=b 然后 next连续走两步。第二段代码正确
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 
    {
        if (headA == nullptr || headB == nullptr) 
        {
            return nullptr;
        }
        ListNode* pa=headA;
        ListNode* pb=headB;
        while(headA!=headB)
        {
            if(headA==NULL)
            {
                headA=pb;
            }
            if(headB==NULL)
            {
                headB=pa;
            }
            headA=headA->next;
            headB=headB->next;
        }      
        return headA;  
        
    }
};
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 
    {
        if (headA == nullptr || headB == nullptr) 
        {
            return nullptr;
        }
        ListNode* pa=headA;
        ListNode* pb=headB;
        while(headA!=headB)
        {
            if(headA==NULL)
            {
                headA=pb;
            }
            else
            headA=headA->next;
            if(headB==NULL)
            {
                headB=pa;
            }
            else
            
            headB=headB->next;
        }      
        return headA;  
        
    }
};
  • 第二遍复习
    • 这个确实很容易错

207. 课程表(中等)(待补充)(拓扑)

[leetcode刷题]HOT100_第31张图片
[leetcode刷题]HOT100_第32张图片

Sort的实现原理及相关问题

参考链接
[leetcode刷题]HOT100_第33张图片

215 数组中的第k个最大元素

  • 内置实现
    [leetcode刷题]HOT100_第34张图片

  • 第二个就是自己实现归并。if((j > r|| a[i]<=a[j] && i<=mid) )这句话的判断顺序不能颠倒,会数组越界。

  • 第三个就是快排 代码复习一下

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end());
        int l=nums.size();
        return nums[l-k];
    }
};


class Solution {
public:
    vector<int> t;
    void merge_sort(vector<int>& a,int l,int r)
    {
        //递归终止条件
        if(l == r) return;
        //处理当前层的逻辑
        int mid = (l+r)/2;
        //进入下一层
        merge_sort(a,l,mid);
        merge_sort(a,mid+1,r);
        //汇总结果
        int i=l;
        int j=mid+1;
        for(int k=l;k<=r;k++)
        {
            //2 种情况 //注意
            if((j > r|| a[i]<=a[j] && i<=mid) )
            {   
                t[k] = a[i];
                i++;

            }
            else
            {
                t[k]=a[j];
                j++;
            }
        }
        for(int k=l;k<=r;k++)
        {
            a[k]=t[k];
        }
        
    }
    int findKthLargest(vector<int>& nums, int k) 
    {
        vector<int> t_new(nums.size());
        t =t_new;
        merge_sort(nums,0,nums.size()-1);
        return nums[nums.size()-1-k+1];

    }
};


#include
using namespace std;
void quickSort(int s[], int l, int r)
{
	//终止条件
	if (l>= r) //元素个素只有一个
	{
		return;
	}
	//处理当前层的逻辑
	int i = l, j = r, x = s[l];//枢纽
	while (i < j)//注意是从右边开始 因为我们是取左边第一个为枢纽 那个值可以填入
	{
		while(i < j && s[j]>= x) // 从右向左找第一个小于x的数
		j--;//下一个待比较的值 
		if(i < j && s[j]<x ) //右边第一个小于枢纽的值 填入到 当前i的位置
		{
			s[i]=s[j];
			i++;//i值被覆盖了 指向下一个待比较的数字
		}
		while(i < j && s[i]< x) // 从左向右找第一个大于等于x的数
			i++; 
		if(i < j)
		{
			s[j]=s[i];
			j--;//指向下一个待比较的数字
		}
		
	}
	s[i] = x;
	//进入下一层
	quickSort(s, l, i - 1); // 递归调用
	quickSort(s, i + 1, r);
	
}


int main()
{
	int array[]={34,65,12,43,67,5,78,10,3,70},k;
	int len=sizeof(array)/sizeof(array[0]);
	cout<<"The orginal arrayare:"<<endl;
	for(k=0;k<len;k++)
		cout<<array[k]<<",";
	cout<<endl;
	quickSort(array,0,len-1);
	cout<<"The sorted arrayare:"<<endl;
	for(k=0;k<len;k++)
		cout<<array[k]<<",";
	cout<<endl;
	system("pause");
	return 0;
}



221 最大正方形(中等)(特殊动态规划)(待复习)

[leetcode刷题]HOT100_第35张图片

  • 太难想到动态规划了,没有做空间优化代码如下
  • 参考链接

[leetcode刷题]HOT100_第36张图片
[leetcode刷题]HOT100_第37张图片
[leetcode刷题]HOT100_第38张图片
[leetcode刷题]HOT100_第39张图片

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int m=matrix.size(),n=matrix[0].size();
        vector<vector<int>> dp(m,vector<int>(n,0));
        int max=0;
        for(int i=0;i<m;i++)
        {
            dp[i][0]=matrix[i][0]-'0';
            //这个方法好 就修改一次
            if(max==0&&matrix[i][0]=='1')
                max=1;
        }
        for(int j=0;j<n;j++)
        {
            dp[0][j]=matrix[0][j]-'0';
            if(max==0&&matrix[0][j]=='1')
                max=1;
        }
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                if(matrix[i][j]=='1')
                {
                    dp[i][j]=min(min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1])+1;
                    if(dp[i][j]>max) max =dp[i][j];
                }
                    
                else
                    dp[i][j]=0;
     
            }
        }
        return max*max;
    }
};

234回文链表(简单)(重要)

[leetcode刷题]HOT100_第40张图片

  • 两个想法 全部取出来然后reverse判断是不是一样
  • 要么就头尾指针,不过这个不是双向链表 所以一样要取出到数组然后再进行比较
  • 题解有一个方法很巧妙 就是快慢指针找到中间节点,然后翻转前半段,最后比较(一般就会要求这种解法)
  • 自己写了一下翻转的写法,有几个注意的点 一个是翻转后半段方便比较,因为如果奇数个此时翻转前半段必然可能第一个值不想等(还需要考虑奇数偶数的关旭)第二个就是快慢指针找中间节点 我这边固定前半段《=后半段,这样后面比较的时候只要比较前半段长度的次数就好,无需判断奇数偶数节点(这个方法无需额外的空间也更快 0(n))

[leetcode刷题]HOT100_第41张图片

/**
 * 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:
    bool isPalindrome(ListNode* head) 
    {
        vector<int> vec;
        while(head!=nullptr)
        {
            vec.push_back(head->val);
            head=head->next;
        }
        int n= vec.size()-1;
        for(int i =0,j=n;i<=j;i++,j--)
        {
            if(vec[i]!=vec[j])
            {
                return false;
            }
        }
        return true;
    }
};

[leetcode刷题]HOT100_第42张图片

/**
 * 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)
    {
        //翻转链表 3个一组
        ListNode* pre=nullptr;
        ListNode*cur=head;
        ListNode*next =nullptr;
        while(cur!=nullptr)
        {
            next=cur->next;//保存要修改的指向
            cur->next=pre;
            pre=cur;
            cur=next;
        }
        return pre;


    }
    bool isPalindrome(ListNode* head) 
    {
        if(head==nullptr||head->next==nullptr) return true;
        //我觉的喜欢前半段长度 <=后半段长度还是用如下的方式
        ListNode* slow =head;
        ListNode* quick=head->next->next;
        while(quick!=nullptr && quick->next!=nullptr)
        {
            //(参考链表排序哪一章节)
            //偶数节点 slow会指向中间一组的前一个
            //奇数节点 slow会指向中间的前一个 
            slow=slow->next;
            quick=quick->next->next;
        }
        ListNode* head1=slow->next;//保存一下后半段的头节点
        slow->next=nullptr;
        ListNode* cur1 =head;//前半段
        ListNode* cur2 =reverselist(head1);
        //这边特别注意 我们前半段固定小于=后半段 所以以前半段长度为基准
        while(cur1!=nullptr)
        {
            if(cur1->val!=cur2->val) return false;
            cur1 =cur1->next;
            cur2=cur2->next;
        }
        return true;
    }  
};
  • 第二遍复习
    • 没啥大问题 两个解法
    • 代码一个错误 就是链表分成两份 slow->next=nullptr;
    • 第二个错误就是 不一定要求前半段必须小于后半段 可以再while 判断cur1 和 cur2

238 除自身以外数组的乘积(中等)

[leetcode刷题]HOT100_第43张图片

  • 之前做过类似的很容易想到用类似dp的方法去做这个题,但是要想想用常数的空间(看了题解发现之前的方法就是常数(因为输出的数组不算辅助空间,差点忘记了))
  • 之前的题目(66 构建乘积数组)重新写了一遍确实没啥问题,就是一些细节的处理
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) 
    {
        int n =nums.size();
        vector<int> dp(n);//从第0行开始
        dp[0]=1;//
        //注意 第i行用的是第i-1的数 比如第二行用第一个数 所以是i-1
        for(int i=1;i<n;i++)
        {
            dp[i]=dp[i-1]*nums[i-1];
        }
        //从后向前 处理另外一个三角形
        //从倒数第二行还是
        int temp=1;//需要一个中间值保存
        //倒数第二行 用倒数第一个数 +1
        for(int i =n-2;i>=0;i--)
        {
            temp=temp*nums[i+1];
            dp[i]=dp[i]*temp;
        }
        return dp;

    }
};
  • 第二遍复习
    • 比较简单

240搜索二维矩阵2

[leetcode刷题]HOT100_第44张图片

  • 首先我觉的对每一行每一列做二分查找太蠢了
  • 做过两中题1 本题这种 2就是每一行可以拼接有序 二分查找的
    [leetcode刷题]HOT100_第45张图片
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) 
    {

        int n =matrix.size();
        if(n == 0)
        return false;
        int m=matrix[0].size();
        int i= 0;
        int j =m-1;
        while(i<n && j>=0)
        {
            if(matrix[i][j] < target)
            {
                //去掉一行
                i++;
            }
            else if(matrix[i][j] > target)
            {
                //去掉列
                j--;
            }
            else
            {
                return true;
            }
        }
        return false;

        
    }
};
  • 第二遍复习
    • 做过多次了

279完全平方数(中等)(背包 动态规划)(待完善)

[leetcode刷题]HOT100_第46张图片

  • 第一个想法难道先找最大的完全平方数填充?我咋感觉真是呢
    代码回想
  • 重新归纳一下背包问题的动态规划
// 版本一
class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i <= n; i++) { // 遍历背包
            for (int j = 1; j * j <= i; j++) { // 遍历物品
                dp[i] = min(dp[i - j * j] + 1, dp[i]);
            }
        }
        return dp[n];
    }
};

287寻找重复数(中等)(二分 快慢)(特别注意)(特别重要)(待复习)

  • 总结:数组中查找某一个数 可以在二分上思考一下下
    [leetcode刷题]HOT100_第47张图片
  • 注意题目需要使用常量的额外空间,所以排序也不行,hash也不行。
  • 第一个想法就是双指针两层for循环遍历比对
  • 这也能想到快慢指针,属实有点变态参考链接
  • 方法二:二分查找(这题只能< 如果是<=跳不出循环)出现这种问题找个边界上的测试 就行了 1233
class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        
        int fast = 0, slow = 0;
        while(true){
            fast = nums[nums[fast]];
            slow = nums[slow];
            if(fast == slow)
                break;
        }
        int finder = 0;
        while(true){
            finder = nums[finder];
            slow = nums[slow];
            if(slow == finder)
                break;        
        }
        return slow;
    }
};



class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int n=nums.size()-1;
        int left=1,right=n;
        while(left<right){
            int mid=left+(right-left)/2;
            int count=0;
            for(int num:nums)if(num<=mid)count++;//统计小于等于这个数的
            if(count>mid)right=mid;//如果这个数量大于这个mid说明就在小于等于这边否则在另外一边 包含mid
            else left=mid+1;
        }
        return left;
    }
};


时间复杂度是nlogn 空间复杂度o1

  • 第二遍复习
    • 确实没能到二分查找 ,不考虑额外空间确实只想到暴力
    • 这代码太容易写错了 得测试

347 前k个高频元素(中等)(重要)

[leetcode刷题]HOT100_第48张图片

  • 频率我们很容易想到hash,然后根据hash的值频率进行排序。
  • 具体步骤
    • 1就是构建hash进行计数
    • 2次数通过迭代器或者auto把值 放入vector 二维数组或者一维+pair
    • 3 构建cmp 修改sort的排序规则 (注意cmp的传入参数)
    • 4 遍历输出k个
bool   cmp(const pair<int,int> &p1,const pair<int,int> &p2)
{
    return p1.second > p2.second;//从大到小
}
class Solution {
public:
  

    vector<int> topKFrequent(vector<int>& nums, int k) 
    {
        unordered_map<int, int> map;
        for (auto i: nums)
            map[i]++;
        //这边也可以是二维数组 也可以
        vector<pair<int, int>> temp;
        int i = 0;
        //这边可以使用迭代器 或者auto的方式
        for (auto it = map.begin();it != map.end(); it++)
        {
            //temp.emplace_back(make_pair(it->first, it->second));
            temp.emplace_back(pair(it->first, it->second));
        }
        sort(temp.begin(), temp.end(), cmp);
        vector<int> res(k);
        for (int i = 0;i < k;i++)
            res[i] = temp[i].first;
        return res;
    }
};
  • 第二遍复习
    • 频率很容易想到hash或者vector 这边数据范围大 所以用hash 然后我们需要排序 map不能直接排序 所以取出一对数据根据频率排序 返回第一个值
    • 错误一:cmp不加括号
    • 错误二:cmp放在类外 或者类中为静态
    • temp.push_back(pair(it->first,it->second)); 写法多种 make_pair 或者说pair都可以

394. 字符串解码(中等)(辅助栈)(重要)

[leetcode刷题]HOT100_第49张图片
[leetcode刷题]HOT100_第50张图片

  • 第一个想法就是栈的方向思考了 它这个解码要从最内部的括号开始解码 ,自己没写出来
  • 需要两个辅助栈和两个临时变量,分为4种情况
    • 当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算,存入临时变量
    • 当 c 为字母时,在 res临时变量尾部添加 c;
    • 当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置空置 00.这也叫状态清空
    • 当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + last_multi * res,其中:
  • 需要自己演变一下这个过程,我的理解因为res临时变量始终表示需要重复的量,字符串栈中的表示的是对于当前阶段只需要重复一次的,因为都是[之前的。阶段变化一定是从内部考虑的。如果是并列的【】【】那就是前面先考虑。当我们从]取出后并没有入栈,因为还可能重复
  • 下面是我看的比较好的一个简洁代码

对入栈的问题需要考虑3个 1 栈元素是什么 2 什么时候入栈 3 什么时候出栈
对于这类问题 我觉的应该从一个最简单的结构进行分析 例如a3[bbb] 分解成最简单的一个结构

class Solution {
public:
  string decodeString(string s) {
	//两个栈分别压int res和用pair
	stack<pair<int, string>> sta;
	int num = 0; string res = "";
	//循环检查字符串
	for (int i = 0; i < s.size(); i++) {
		//遇到数字则存入num
		if (s[i] >= '0'&&s[i] <= '9') {
			num *= 10;
			num += (s[i] - '0');//这里括号是否需要
		}
		else if (s[i] == '[') {//遇到[压栈数字和字符串,置零置空
			sta.push(make_pair(num, res));
			num = 0;
			res = "";
		}
		else if (s[i] == ']') {//遇到]出栈数字和字符串,组装
			int n = sta.top().first;//n指示的是res的循环次数,不是a的
			string a = sta.top().second;
			sta.pop();
			for (int i = 0; i < n; i++)  a = a + res; //循环n次
			res = a;
		}
		else {//遇到字符存入字符
			res += s[i];
		}		
	}
	return res;
}
};

406 根据身高重建队列(中等)(贪心)(特别)

[leetcode刷题]HOT100_第51张图片

  • 第一眼看没啥思路,难不成递归遍历所有情况?

代码回想录

  • 题解总结:(套路):一般这种数对,还涉及排序的,根据第一个元素正向排序,根据第二个元素反向排序,或者根据第一个元素反向排序,根据第二个元素正向排序,往往能够简化解题过程。

  • 在本题目中,我首先对数对进行排序,按照数对的元素 1 降序排序,按照数对的元素 2 升序排序。原因是,按照元素 1 进行降序排序,对于每个元素,在其之前的元素的个数,就是大于等于他的元素的数量,而按照第二个元素正向排序,我们希望 k 大的尽量在后面,减少插入操作的次数(或者说保证正确性) 比如(5,2)(5,3)很明显顺序相同的身高按照第二个元素升序 降序必然会错误

[leetcode刷题]HOT100_第52张图片

  • 步骤

    • 1 排序规则:按照先H高度降序,K个数升序排序
    • 2 历排序后的数组,根据K插入到K的位置上
  • 注意

    • cmp的用法
    • insert的用法
  • 问题

    • vector的插入底层实现需要很多时间 所以可以修改成链表 注意看代码的区别,list是双向迭代器 只能++ -- 而vector deque是随机访问迭代器 可以 + 3 -4 这种 所以代码有所区别
class Solution {
public:
    //如果是类内写cmp 需要加上static
    bool static cmp(const vector<int>& a, const vector<int>&b)
    {
        if(a[0]>b[0]) return true;//true表示ab位置不变
        else if(a[0] < b[0]) return false;//false 表示改变
        else
        {
            //或者如下简洁写法 直接把要求写在return后面
            return a[1]<b[1];
        }
        
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) 
    {
        sort(people.begin(),people.end(),cmp);
        vector<vector<int>> result;
        for(int i =0;i<people.size();i++)
        {
            int position = people[i][1];
             result.insert(result.begin()+position,people[i]);//参数一 iterator loc
        }
        return result;
    }
};
// 版本二
class Solution {
public:
    // 身高从大到小排(身高相同k小的站前面)
    static bool cmp(const vector<int> a, const vector<int> b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1]; // 插入到下标为position的位置
            std::list<vector<int>>::iterator it = que.begin();
            while (position--) { // 寻找在插入位置
                it++;
            }
            que.insert(it, people[i]);
        }
        return vector<vector<int>>(que.begin(), que.end());
    }
};


448 找到所有数组中消失的数字(简单)(数组的原地修改)

[leetcode刷题]HOT100_第53张图片

  • 其实我自己第一个想法就是归为 第一次遍历,把数字交换对应的下标的位置(比如1 放入下标0 i-1的位置)如果 (1)情况1 两个数字一样 那就当前置为0 (2)如果交换的位置是0 那就换过去,把自己置为0 后移(3)如果遇到0就后移(4) 如果两个数字不一样都不是零 这边应该需要一个while不断交换。 第二次遍历就应该找0了
    总结 两种方法都是类似的 就是希望在原本数组信息做标记 但是又能保存原来的信息 ,一个是+n 后% 一个是修改正负

  • 看了题解,发现自己的方法还是太笨了,正常的用辅助数组的方法是出现的我们在对应的数组位置标记上。

  • 方法一如下:那如何在原数组进行标记么,我们完全可以把原本的正数变成负数,这样就可以保存原来的信息,比如一开始修改后边的数组元素 把正数变成负数,等便利到的时候我们可以取出取绝对值就好了。

  • 自己写有一个地方写错了 第一个循环要进行判断,如果修改的值已经是负数了 那就不要进行改变 否则负负为正 判断错误
    [leetcode刷题]HOT100_第54张图片

  • 方法二如下
    在这里插入图片描述

[leetcode刷题]HOT100_第55张图片

class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) 
    {
        int n=nums.size();
        vector<int> res;
        if(n<=0) return res;
        for(int i =0;i<n;++i)
        {
            int index =abs(nums[i])-1;//表示我要修改的坐标
            //注意这边的判断!!!!!!!
            if(nums[index]>0) nums[index]=-nums[index];
        }
        for(int i=0;i<n;++i)
        {
            if(nums[i]>0) res.push_back(i+1);
        }
        return res;

    }
};
class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        vector<int> res;
        if(nums.empty()) return nums;
        for(int i=0;i<nums.size();i++)
        {
            int index=(nums[i]-1)%nums.size();
            nums[index]+=nums.size();
        }
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]<=nums.size())
                res.push_back(i+1);
        }
        return res;
    }
};
  • 第二遍复习
    • 很容易想到辅助数组 对应数字放在对应下标 再遍历一遍很容易想到 但是很明显不是最优的做法
    • 优化做法就是把辅助空间也省去 数字1放的位置应该是0 2放在1 就是实际应该的位置是 index = nums[i]-1 把这个位置表示位负数 最后全部遍历一遍看看有没有没覆盖的 +一些细节处理
    • 重写了代码
class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) 
    {
        vector<int> res;
        for(int i=0;i<nums.size();i++)
        {
            int index = abs(nums[i])-1;//注意点一
            if(nums[index]>0)//表示没有标记过 注意点儿
            {
                nums[index] =-nums[index];
            }
        }
        for(int i =0;i<nums.size();++i)
        {
            if(nums[i]>0) res.push_back(i+1);//注意点三
        }
        return res;
    }
};

461 汉明距离(简单)(位运算)

[leetcode刷题]HOT100_第56张图片

  • 涉及到二进制那肯定想到位运算,很简单 很容易想到先用异或找到不同位,然后用& 统计1的个数
class Solution {
public:
    int hammingDistance(int x, int y) 
    {
        int result= x^y;
        int count=0;
        while(result!=0)
        {
            result =result & (result-1);
            count++;
        }
        return count;

    }
};

494 目标和(中等)(回溯)(背包)

[leetcode刷题]HOT100_第57张图片

  • 我的第一个想法:遇到最长最短 方案数可以想象动态规划

  • 我的第二个想法: 回溯 非加 就是减

  • 看了题解…确实想的差不多

  • 参考链接

  • 在背包专栏总结了

543 二叉树的直径(简单)(递归)(特别)

[leetcode刷题]HOT100_第58张图片

  • 不是很难,就是要把这个问题转换一下,其实就是比较每一个子树(左子树最大深度和右子树最大深度的和),取其中的最大值
  • 很容易出错的是我们默认以为最大直径就是过跟节点的左子树深度加右子树的深度,然后再主函数调用两次dfs 最后相加那么就错了
/**
 * 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:
    int max_result=0;
    int dfs(TreeNode* root)
    {
        //递归终止条件
        if(root==nullptr) return 0;//表示当前深度为0
        int left= dfs(root->left);
        int right =dfs(root->right);
        max_result =max((left+right),max_result);
        return max(left,right)+1;//还是照样保存当前子树的最大深度
    }
    int diameterOfBinaryTree(TreeNode* root) 
    {
        dfs(root);
        return max_result;
    }
};
  • 一篇文章解决所有二叉树路径问题(问题分析+分类模板+题目剖析)有空再看

  • 第二遍复习

    • 很容易想到 连续最大深度那个题 后序遍历 把左右深度相加就是当前节点的直径(没到一个节点就去统计) 需要一个max_最大值 不断去更新。返回值返回最大深度
    • 代码没什么问题

560 和为k的子数组(中等)(前缀+哈希)

[leetcode刷题]HOT100_第59张图片

  • 看到连续子数组 第一个想法就是滑动窗口…或许先来个排序 试试看吧,写完发现错了…这不能修改数组的顺序 要么怎么叫连续子数组呢。。想的太简单了

[leetcode刷题]HOT100_第60张图片
[leetcode刷题]HOT100_第61张图片

  • 简单的说前缀和是思想,从3层循环暴力 减少到2层,哈希表是优化,从二层循环减少到一层

  • 简单的说前缀表就是把问题转换成两数之差 会不会等于 k 情况的判断。

  • 我们之前做过两次for循环求两数之和的题 通过hash减少时间复杂度 这题同理

  • 特别的在于 前缀表的定义决定写法,如果下标表示长度 add(n+1) 长度从0开始 那么任意两数相减就可以遍历所有的情况 如果下标表示实际坐标 那么还要还要考虑 a-b的a本身 特别重要 注意下面 1 2代码的区别

  • 当然代码还可以简洁 前缀表的空间还可以省去 代码一的改进,两个并列for循环可以同时进行 修改为第三个代码

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int>add(n,0);
        add[0]=nums[0];
        for(int i=1;i < n;i++)add[i]=add[i-1]+nums[i];
        int ans=0;
        unordered_map<int,int> map;
        map[0]=1;//这边是判断等于本身的情况
        for(int i=0; i<n ;i++)
        {
            if(map.count(add[i]-k))ans+=map[add[i]-k];
            map[add[i]]++;

        }
        return ans;
    }
};




class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int>add(n+1,0);
        //add[0]=nums[0];
        for(int i=1;i <= n;i++)add[i]=add[i-1]+nums[i-1];
        int ans=0;
        unordered_map<int,int> map;
       
        for(int i=0; i<=n ;i++)
        {
            if(map.count(add[i]-k))ans+=map[add[i]-k];
            map[add[i]]++;

        }
        return ans;
    }
};

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n=nums.size();
        unordered_map<int,int> map;
        int pre = 0;
        int count =0;
        map[0] = 1;
        for(int i=0; i<n ;i++)
        {
            pre =pre+nums[i];
            if(map.count(pre-k)==1)
            {
                count+=map[pre-k];
            }
               map[pre]++;

        }
        return count;
    }
};

[leetcode刷题]HOT100_第62张图片

617合并二叉树(简单)(可以复习)

[leetcode刷题]HOT100_第63张图片

  • 想要沿用对称二叉树传入两个跟节点和构建二叉树返回值为节点的想法,如果一边为空 那就直接返回另外一个树的节点 后面 就不用遍历了。
  • 看了题解和自己想的一样,没有任何区别,前序+后续,前序不断创建新的节点,后续不断从后向前把节点串起来,注意看注释。

[leetcode刷题]HOT100_第64张图片

/**
 * 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* dfs(TreeNode* root1, TreeNode* root2)
    {
        //终止条件 这种写法包含了 两边为空返回为null 和 只有一边为空的情况
        if(root1 == nullptr)
        {
            return root2;
        }
        if(root2 ==nullptr)
        {
            return root1;
        }
        //处理当前层的逻辑 
        //构建新节点 挂上下一层传递上来的值 把当前层返回给上一层
        TreeNode* new_node = new TreeNode(root1->val + root2->val);
        new_node->left = mergeTrees(root1->left, root2->left);
        new_node->right = mergeTrees(root1->right, root2->right);
        return new_node;    
    }
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) 
    {
        TreeNode* root = dfs(root1,root2);
        return root;

    }
};
  • 第二遍复习
    • 毫无疑问 递归 传入两个树 想象三个树(两个存在的 一个还没构建) 构建节点所以返回值是节点
    • 先序 想想需要根据当前两个数的节点值构建新的节点 然后递归 后序把递归的返回结果 挂上去 最后返回当前层构建的给上一层
    • 两件事先序创建新节点 后序挂上去 代码一遍过
class Solution {
public:
    TreeNode* dfs(TreeNode* root1, TreeNode* root2)
    {
        //终止条件 这种写法包含了 两边为空返回为null 和 只有一边为空的情况
        if(root1 == nullptr)
        {
            return root2;
        }
        if(root2 ==nullptr)
        {
            return root1;
        }
        //处理当前层的逻辑 
        //构建新节点 挂上下一层传递上来的值 把当前层返回给上一层
        TreeNode* new_node = new TreeNode(root1->val + root2->val);
        TreeNode* left = dfs(root1->left, root2->left);
        TreeNode* right = dfs(root1->right, root2->right);
        //后序处理
        new_node->left=left;
        new_node->right=right;
        return new_node;    
    }
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) 
    {
        TreeNode* root = dfs(root1,root2);
        return root;

    }
};

416 分割等和子集(中等)(背包问题 )

[leetcode刷题]HOT100_第65张图片

  • 在背包专栏总结了

538把二叉树转换成累加树(中等)(技巧)

[leetcode刷题]HOT100_第66张图片

  • 第一眼看题目都提示了二叉搜索树,那直接中序,更换一下顺序 右中左不就好了 累加,没啥问题,一遍过了
/**
 * 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:
    int sum =0;
    void dfs(TreeNode* root)
    {
        if(root==nullptr) return;
        dfs(root->right);
      
        sum=sum+root->val;
        root->val=sum;
        dfs(root->left);

    }
    TreeNode* convertBST(TreeNode* root) 
    {

        dfs(root);
        return root;
    }
};

581最短无序连续子数组(中等)(规律)

[leetcode刷题]HOT100_第67张图片

  • 如果就是找到规律,简单的说如果把一个完全升序的序列修改中间一段无序就很容易找到规律
  • 12345678 12563478 例子一
  • 方法一:双指针遍历,两个指针都需要从头遍历到尾,左指针不断后移保存当前的最大值,找到最后一个比最大值小的。右指针向左移动,不断保存最小值,找到最后一个比最小值大的数。0(n)的时间复杂度
  • 方法二:对整个序列排序 找到第一个和最后一个不同的元素
class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) 
    {
        int n =nums.size();
        int right_max = INT_MIN;
        int left_min =INT_MAX;
        int left=0;
        int right=0;
        for(int i =0 ; i < n ; i++)
        {
            if(nums[i] >= right_max) right_max=nums[i];//正常情况
            else
            {
                right=i;
            }
            if(nums[n-i-1] <= left_min) left_min=nums[n-i-1]; //正常情况
            else
            {
                left=n-i-1;
            }
        }
        if (right==0) return 0;
        else return right-left+1;

    }
};

437 路劲之和3(中等)(回溯 + 前缀和)

[leetcode刷题]HOT100_第68张图片

  • 题目分为4种

    • 从根结点到页节点
    • 从根节点到任意节点
    • 从任意节点到叶结点
    • 从父子节点到子节点
  • 第一眼看到这个题目,其实没有想到前缀和+回溯来解决。但是看了题解很容易理解,如果想到用前缀和来解题(单独思考一条路劲),然后再想到如果想到从左子树返回到父子节点再进入右子树需要修改当前前准和 和 map的值就可以想到用回溯

  • 和之前做的一个题很像,我们只要知道有几条路径,不需要知道具体的路劲,所以键是sum 值是数量

  • 老样子要么一开始定义路径为0的数量为1,这样是为了找到本身,之前讲过

  • 回溯什么东西么1 当你从下一层退回到上一层首先前缀和需要修改回去,第二个就是map对于当前层的前缀和减去认真思考

/**
 * 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:
    unordered_map<int,int> map;//键表示sum综合 值表示数量
    int res=0;//保存结果
    //先序处理
    void dfs(TreeNode* root,int& sum,int& cur_sum)
    {
        //递归终止条件
        if(root==nullptr) return;
        //处理当前层的逻辑
        //1更新当前层的前缀和 
        cur_sum=cur_sum+root->val;
        //2判断有没有符合条件的 有就统计结果 没有啥事没做
        if(map.count(cur_sum-sum)==1)
            res =res+map[cur_sum-sum];
        //3 不管有没有符合的 把当前前缀和 放入map
        map[cur_sum]++;
        //4递归
        dfs(root->left,sum,cur_sum);
        dfs(root->right,sum,cur_sum);
        //5回溯
        map[cur_sum]--;
        cur_sum=cur_sum-root->val;
        return;
    }

    int pathSum(TreeNode* root, int targetSum) 
    {
        map[0]=1;//表示本身为结果 不是差为结果
        int cur_sum=0;
        dfs(root,targetSum,cur_sum);
        return res;

    }
};

621任务调度器(中等)

[leetcode刷题]HOT100_第69张图片

  • 有种感觉就是一种排序好,第二种插入,然后第三种插入,但是如何选择哪一种先插入呢。直接把冷却最长的先放入,但是冷却时间一直那肯定想到就是任务多的哪一个
  • 看了题解有很像相似的想法,但是没有题解想的那么仔细
  • 题解链接
  • 主要理解下面这个图,概括起来就是创建一个桶,长度为任务最多的那个数量,宽度为冷却时间+1.桶有一个缺口就是最后一个任务后面不用等待了所以需要计算并列最多的任务数。当这个桶装满了(相当于没有空闲的时间)剩下的任务可以扩充桶的宽度然后放入任务(需要多少扩充多少,不存在剩余时间),此时时间就是任务的数量。所以最后返回值就是桶的原始大小和任务数的最大值

[leetcode刷题]HOT100_第70张图片

class Solution {
public:
    static bool cmp(int& p1,int& p2)
    {
        return p1>p2;
    } 
    int leastInterval(vector<char>& tasks, int n) 
    {
        int len =tasks.size();
        int same=1;//表示数量也表示下标 
        vector<int> vec(26);
        for(auto x:tasks)
        {
            vec[x-'A']++;
        }
        sort(vec.begin(),vec.end(),cmp);
        while(same<vec.size() && vec[same]==vec[0]) same++;//找到相同最大
        return max(len,same+(n+1)*(vec[0]-1));

        

    }
};

3 无重复字符的最长子串(中等)(滑动窗口)(代码可优化)(dp)

[leetcode刷题]HOT100_第71张图片

  • 下面的代码没有任何参考而且中间测试出现了很多错误,比如else中right要++ 指向下一个比较的值(否则map有这个right 一直走到else 死循环)所以while循环可以改成for循环 right是一直增加的不需要任何条件
  • 第二个就是当找到重复的值,需要把重复的值之前都给他删除,这边需要一个for循环,第一次写的时候忘记了
class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        int n=s.size();
        unordered_map<char,int> map;
        int sum=0;
        int left=0;
        int right=0;
        int result=0;
        //如果右边的数不在map
        while(right<=n-1 && left <= right)
        {
            if(map.count(s[right])==0)//不存在
            {
                //存入
                map[s[right]]=right;
                //计算值 
                sum=right-left+1;
                result=max(result,sum);
                //边界右移
                right++;//下一个待判断的点
            }
            else//表示存在
            {
                //修改左边界
                int k=map[s[right]]+1;//出现的位置+1
                for(int i=left;i<k;i++)
                {
                    map.erase(s[i]);
                }
                left=k;
                map[s[right]]=right;//修改为最后一次存在的值
                sum=right-left+1;//这边不加1 aba
                result=max(result,sum);
                right++;

            }
        }
        return result;

    }
};
class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        int n=s.size();
        unordered_map<char,int> map;
        int sum=0;
        int left=0;
        int right=0;
        int result=0;
        //如果右边的数不在map
        for(right;right<n;right++)
        {
            if(map.count(s[right])==0)//不存在
            {
                //存入
                map[s[right]]=right;
                //计算值 
                sum=right-left+1;
                result=max(result,sum);
                //边界右移
             
            }
            else//表示存在
            {
                //修改左边界
                int k=map[s[right]]+1;//出现的位置+1
                for(int i=left;i<k;i++)
                {
                    map.erase(s[i]);
                }
                left=k;
                map[s[right]]=right;//修改为最后一次存在的值
                sum=right-left+1;//这边不加1 aba
                result=max(result,sum);
                

            }
        }
        return result;

    }
};
  • 第二遍复习 这个和48一样可以dp思考 dp+hash表 (需要查找 所以想到hash) hash保存最近出现的 是否在范围内 不去做删除操作(举个例子不容易错)

337 打家劫舍3(中等)(递归+动态规划)

[leetcode刷题]HOT100_第72张图片
[leetcode刷题]HOT100_第73张图片

  • 其实第一次首先排除广度优先,不是一层一层的考虑的
  • 其次想明白为什么不是先序遍历,我一开始想把到当前节点偷和不偷的最大值传入下一层。但是这个存在一个问题,如果左节点和右节点判断的结果不同怎么办:如果左子树认为不偷父子节点到当前最大,右节点认为相反,没办法综合考量,所以用后续遍历,这样就可以综合考虑并且可以把结果汇总。
  • 综上所述,后续让当前节点根据子节点的情况综合判断自己偷还是不偷。
  • 步骤
    • 确定终止条件,if (cur == NULL) return vector{0, 0};
    • 确定传入的参数(节点),返回值为数组 包含偷和不偷两种状态的值(可以理解成dp【i-1】【0】 和dp【i-1】【1】)
    • 遍历顺序 后序遍历
    • 当前层的逻辑:计算出当前层偷和不偷两中结果(包含的递归)最后返回给上一层
    • dp递推方程为当前层逻辑
    • 注意返回值不要写反 不容易找
class Solution {
public:
     vector<int> dfs(TreeNode* cur)
     {
        // 终止条件
        if (cur == NULL) return vector<int>{0, 0};
        //后续遍历
        //递归
        vector<int> left =dfs(cur->left);
        vector<int> right =dfs(cur->right);
        //处理当前层的逻辑
        // 偷cur
        int val1 = cur->val + left[0] + right[0];//0表示不偷
        // 不偷cur 
        int val2 = max(left[0], left[1]) + max(right[0], right[1]);

        //注意这边返回值第一个表示不偷 第二个表示偷
        return {val2, val1};
     }
    int rob(TreeNode* root) 
    {
        vector<int> result = dfs(root);
        return max(result[0],result[1]);
    }
};

198 打家劫舍1(中等)

  • dp[i] dp表示考虑到第i间房子能偷到的最大金额

  • 确定递推公式,两个状态i房子偷或者不偷。当前房子偷那么dp[i]=dp[i-2]+nums[i] i-1的房子我们不考虑了 如果不偷 那么i-1的房子可以考虑,dp[i]=dp[i-1].dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);

  • 初始化就简单了

  • 方法二 是我们自己做的一个修改,如果dp【i】表示第i间房子一定偷那么递推公式就是 dp[i] = max(dp[i - 2] , dp[i - 3])+nums[i];可能存在间隔为1或者为2 但是不可能存在间隔为3. 最后返回值要么是最后一件偷了 要么最后一件没偷 两种可能。

  • 写的时候注意到 修改了递推公式 就要修改初始化 修改了初始化就要注意特判,如果长度为2 那么dp【2】越界了 需要注意

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < nums.size(); i++) {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[nums.size() - 1];
    }
};

class Solution {
public:
    int rob(vector<int>& nums) 
    {
        //如果dp【i】表示第i间屋子一定偷
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        if (nums.size()==2) return max(nums[0],nums[1]);
  
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        dp[1] = nums [1];
        dp[2]= nums[0]+nums[2];
        for (int i = 3; i < nums.size(); i++) 
        {
            dp[i] = max(dp[i - 2] , dp[i - 3])+nums[i];
        }
        return max(dp[nums.size()-1],dp[nums.size()-2]);
    }
};

213打家劫舍2(中等)

[leetcode刷题]HOT100_第74张图片

  • 主要就是成环的考虑,这个直接分成两部分就好了,如果长度为1 到n 分成 1到n-1 和 2到n 最后取最大值
// 注意注释中的情况二情况三,以及把198.打家劫舍的代码抽离出来了
class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        int result1 = robRange(nums, 0, nums.size() - 2); // 情况二
        int result2 = robRange(nums, 1, nums.size() - 1); // 情况三
        return max(result1, result2);
    }
    // 198.打家劫舍的逻辑
    int robRange(vector<int>& nums, int start, int end) {
        if (end == start) return nums[start];
        vector<int> dp(nums.size());
        dp[start] = nums[start];
        dp[start + 1] = max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[end];
    }
};

如有错误,欢迎指出,整理方便个人复习回顾。

你可能感兴趣的:(leetcode周记录,leetcode,链表,算法)