[leetcode刷题]剑指offer汇总

第一遍复习时间 08-17
第二遍复习时间 08-22

文章目录

  • 栈和队列
    • 09 用两个栈实现队列(简单)
    • 30 含有min函数的栈(简单)
    • 31栈的压入、弹出序列(中等)(模拟解决)
  • 数组 12题
    • 04 二维数组中的查找(中等)(规律)
    • 11 旋转数组的最小数字(困难)(二分查找)
    • 21 调整数组顺序使奇数位于偶数前面(简单)(快慢指针)
    • 29 顺时针打印矩阵(中等)(模拟)
    • 39 数组中出现次数超过一半的数字(简单)
    • 42连续子数组的最大和(简单)(动态规划)
    • 45把数组排成最小的数 (中等)(特别)
    • 51 数组中的逆序对 (困难)(待补充)
    • 56 数组中数字出现的次数(中等)(位运算)
    • 复习一下之前常用的x&(x-1)用来消除最低位的1
    • 070只出现一次的数字(简单)(hash)
    • 66 构建乘积数组(中等)(dp)
  • 字符串
    • 05 替换空格(简单)
    • 567字符串的排列(中等)(滑动窗口)
    • 50第一个只出现一次的字符(简单)
    • 58 左旋转字符串(简单)
    • 557 反转字符串中的单词3(简单)
    • 151翻转字符串里的单词(中等)
    • 8 字符串转换整数(中等)
    • 19正则表达式匹配(困难)(dp)
    • 20表示数值的字符串(中等)(待补充)
    • 54 字符流中第一个不重复的字符(待补充)
  • 链表
    • 06从尾到头打印链表(简单)
    • 22 链表中倒数第k个节点(简单)(三种做法)
    • 24 反转链表(简单)
    • 92反转链表2(中等)
    • 25 合并两个排序的链表(简单)
    • 35 复杂链表的复制(中等)
    • 52 两个链表的第一个公共节点(简单)(特殊解法)
    • 22 链表中环的入口节点(中等)
    • 83删除排序链表中的重复元素(简单)
  • 二叉树
    • 07 重建二叉树(中等)
    • 26 树的子结构(中等)(特别)
    • 27 二叉树的镜像(简单)
    • 32-1 从上往下打印二叉树(中等)
    • 32-3 从上到下打印二叉树(中等)
    • 33二叉树的后续遍历序列(中等)(特别)
    • 112路径总和(简单)
    • 113 路径总和二(中等)(重)(二叉树回溯)(二次!)
    • 36 二叉搜索树与双向链表(中等)
    • 55 二叉树的深度(简单)
    • 55 平衡二叉树(简单)
    • 54 二叉搜索树的倒数第k个节点(简单)
    • 101 对称的二叉树(简单)(重)
    • 59 按之字形顺序打印二叉树(中等)
    • 37 序列化二叉树(困难) (待补充)
  • 回溯法
    • 12 矩阵中的路径(中等)(特)
    • 13机器人的运动范围(中等)
  • 递归和循环
    • 10斐波拉数列(简单)
    • 10青蛙跳台阶问题(简单)
    • 变态跳台阶(中等)
    • 矩形覆盖(简单)
  • 数学
    • 16数值的整数次方
    • 64求1 + 2 + 3 + 4 + ..n(中等)
    • 65 不用加减乘除做加法(简单)
  • 其他
    • 15二进制中1个数(简单)
    • 40 最小的k个数(简单)(重)
    • 43 1~n整数中1出现的次数(困难)
    • 49丑数(中等)(特殊dp)
    • 57和为s的两个数字(简单)
    • 57 和为s的连续正数序列2(简单)(滑动窗口解决问题)
    • 61扑克牌中的顺子(简单)(技巧)
    • 62 圆圈中最后剩下的数字(简单)(特殊)
    • 41数据流中的中位数(困难)(优先队列)
    • 59滑动窗口的最大值 (困难)(单调队列)

栈和队列

09 用两个栈实现队列(简单)

  • 第一个写没啥问题 就是忘记了delete空直接返回
class CQueue {
public:
    stack<int> stack1;
    stack<int> stack2;
    CQueue() {

    }
    
    void appendTail(int value) {
        stack1.push(value);
    }
    
    int deleteHead() 
    {
        if (stack1.empty()) return -1;
        //1首先把stack1 循环取出放入第二个
        while(stack1.empty()!=true )
        {
            int temp = stack1.top();
            stack1.pop();
            stack2.push(temp);
        }
        //删除最久的元素 保存返回值
        int res = stack2.top();
        stack2.pop();
        //复原
        while(stack2.empty()!=true)
        {
            int temp = stack2.top();
            stack2.pop();
            stack1.push(temp);
        }
        return res;

    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */


  • 第二次复习
    • 没什么问题,一遍过,重写了代码和之前有一点点区别
class CQueue {
public:
    stack<int> stack1;
    stack<int> stack2;
    CQueue() {
    }
    //尾部添加数据
    void appendTail(int value) 
    {
        stack1.push(value);
    }
    //头部删除数据
    int deleteHead() 
    {
        int result=-1;
        while(stack1.empty()!=true)
        {
            stack2.push(stack1.top());
            stack1.pop();
        }
        if(stack2.empty()!=true)
        {
            result = stack2.top();
            stack2.pop();
           
        }
        else
        {
            return -1;
        }
        while(stack2.empty()!=true)
        {
            stack1.push(stack2.top());
            stack2.pop();
        }
         return result;

    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

30 含有min函数的栈(简单)

  • 没啥问题
    [leetcode刷题]剑指offer汇总_第1张图片
class MinStack {
public:
    stack<int> stack1;
    stack<int> stack2;//辅助
    /** initialize your data structure here. */
    MinStack() {
        stack2.push(INT_MAX);
    }
    
    void push(int x) {
        stack1.push(x);
        if(x < stack2.top()) stack2.push(x);
        else stack2.push(stack2.top());
    
    }
    
    void pop() {
        stack1.pop();
        stack2.pop();

    }
    
    int top() {
        int res =stack1.top();
        return res;

    }
    
    int min() {
        return stack2.top();

    }
};

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

31栈的压入、弹出序列(中等)(模拟解决)

[leetcode刷题]剑指offer汇总_第2张图片

  • 第一个想法 回溯…取出或者不取…
  • 看了题解 要么模拟 要么找规律…聪明的方法…
  • 内部while 要判断栈是否为空
  • 当然可以加上特判 两个长度不一致 直接返回false
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) 
    {
        stack<int> stk;
        int k = 0;
        for(int i=0;i<pushed.size();i++)
        {
            stk.push(pushed[i]);//入栈
            while(stk.empty()!= true && stk.top()==popped[k])
            {
                k++;//匹配上 移动到下一个待匹配的地方
                stk.pop();//删除这个
            }
        }
        bool res =stk.empty();//为空表示匹配 当前前提就是数量一致
        return res;


    }
};

第一次复习:内部的while循环为空忘记写了 根据报错找到了。补充:时间复杂度和空间复杂度都是0(n)
第二次复习:很明显需要一个for循环不断模拟放入,内部需要判断是不是弹出元素,并且可能一次性弹出多个 加个while 代码一次过

数组 12题

04 二维数组中的查找(中等)(规律)

[leetcode刷题]剑指offer汇总_第3张图片

  • 有一个地方写错了 常见的错误 if(matrix.size()==0) return false;先进行判断才能 matrix[0].size()-1
  • 这个题区别与一个二分查找的题 把二维转变成一维 利用 % 和/
class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        int i = 0;
        if(matrix.size()==0) return false;
        int j = matrix[0].size()-1;
        while(i < matrix.size() && j >= 0)
        {
            if(matrix[i][j] > target) j--;
            else if(matrix[i][j] < target) i++;
            else return true;
        }
        return false;
    }
};
class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        int i = matrix.size() - 1, j = 0;
        while(i >= 0 && j < matrix[0].size())
        {
            if(matrix[i][j] > target) i--;
            else if(matrix[i][j] < target) j++;
            else return true;
        }
        return false;
    }
};

第一次复习:没什么问题,补充:时间复杂度0(m+n)空间复杂度为0(1)
第二次复习:需要一个循环 移动不固定 用while 代码一次性过

11 旋转数组的最小数字(困难)(二分查找)

  • 这个要区别与 153 这题元素可以相同 这题不同 这题情况比较多包含重复值

[leetcode刷题]剑指offer汇总_第4张图片

  • 多加了一个判断 mid =rid 那就缩小边界
  • 有一个不同就是 返回值是l 不是r 很特别 回头理一理

class Solution {
public:
   int minArray(vector<int>& numbers) {
        int l = 0;
        int r = numbers.size() - 1;
        while (l <= r)
        {
            int mid = l + (r - l) / 2;
            //78012的情况
            if (numbers[mid] < numbers[r])//如果中间小于右边 说明在前半部分
            {
                r = mid;
            }
            else if(numbers[mid] > numbers[r]) //反之在后半部分
            {
                l = mid + 1;
            }
            else
            {
                r--;
            }
        }
        return numbers[l];
    }
};


第一遍复习没什么问题,时间复杂度0(n)空间复杂度为0(1)
第二遍复习…需要点点记忆 区别于 不含重复值的二分查找 和 旋转数组查好target 都有不同 在汇总复习有写过

21 调整数组顺序使奇数位于偶数前面(简单)(快慢指针)

  • 这个题很像那个 快慢指针 零后移 以及 取出重复数组 k 和 i
  • k 和 i都从 0开始 i遇到奇数就交换 偶数向后 (可能会造成 奇数换奇数 但是不要紧 不要求相对位置不变 如果要求相对位置不变 再说)
    [leetcode刷题]剑指offer汇总_第5张图片
class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int k = 0, i = 0;
        while (i < nums.size())
        {
            if (nums[i] & 1 == 1)//表示是奇数 
            {
                //一开始就是奇数 自己和自己交换 或者这边加一个if k != i
        
                swap(nums[k], nums[i]);
                k ++;//下一个待填入的位置
                i ++;//下一个判断的值
            }
            else //如果是偶数 不管他
            {
                i ++;
            }
           
        }
        return nums;
    }
};

  • 头尾指针的做法 就是把k设置到后面而已 遇到偶数 后移
class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        //双指针----->left指向待比较地址,right指向待放置地址
        //1.循环: left
        //   a.如果num[left]为奇数--->表示放置正确,left++
        //   b.如果num[left]为偶数--->表示放置错误,num[left] <<-->> num[right] swap-此时right一定放置正确,right--

        int left=0;
        int right=nums.size()-1;


        while(left<right)
        {
            //奇数
            if(nums[left]%2==1)
            {
                left++;
            }
            else
            {
                //swap
                int temp=nums[left];
                nums[left]=nums[right];
                nums[right]=temp;

                right--;
            }
        }

        return nums;
    }
};


第一遍复习:没啥问题,用for循环写了一遍,时间复杂度0(n)

class Solution {
public:
    vector<int> exchange(vector<int>& nums) 
    {
        int k=0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]%2==1)//遇到奇数 就前移
            {
                swap(nums[i],nums[k]);
                k++;
            }
            //遇到偶数啥都不做 等待后移就好了
        }
        return nums;

    }
};
  • 第二遍复习,没什么问题,因为需要一个不断指针不断后移数组 所以还是用for循环 习惯一点 代码一遍过如下
class Solution {
public:
    vector<int> exchange(vector<int>& nums) 
    {
        int k=0;//待填入的位置
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]%2==1)
            {
                swap(nums[k++],nums[i]);
            }
        }
        return nums;

    }
};

29 顺时针打印矩阵(中等)(模拟)

[leetcode刷题]剑指offer汇总_第6张图片

[leetcode刷题]剑指offer汇总_第7张图片

  • 一开始确实没想到, 可以像上面分成四个状态,一个while循环嵌套四个unhuan1并且缩小边界
class Solution 
{
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) 
    {
        if (matrix.empty()) return {};
        vector<int> res;
        int l = 0;                      //左边界
        int r = matrix[0].size() - 1;   //右边界
        int t = 0;                      //上边界
        int b = matrix.size() - 1;      //下边界
        while (true)
        {
            //left -> right
            for (int i = l; i <= r; i++) res.push_back(matrix[t][i]);
            if (++t > b) break;
            //top -> bottom
            for (int i = t; i <= b; i++) res.push_back(matrix[i][r]);
            if (--r < l) break;
            //right -> left
            for (int i = r; i >= l; i--) res.push_back(matrix[b][i]);
            if (--b < t) break;
            //bottom -> top
            for (int i = b; i >= t; i--) res.push_back(matrix[i][l]);
            if (++l > r) break;
        }
        return res;
    }
};
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.empty()==true) return {};
        vector<int> result;
        int l = 0; //左边界
        int r = matrix[0].size()-1;//右边界
        int t = 0;//上边界
        int b = matrix.size()-1;//下边界
        while(true)
        {
            //一阶段 left -> right
            for (int i =l;i<= r;i++) result.push_back(matrix[t][i]);
            t++; //收缩上边界
            if(t  > b) break;
            //二阶段  top -> bottom
            for(int i = t;i <= b;i++) result.push_back(matrix[i][r]);
            r--;
            if(l > r) break;
            //三阶段
            for(int i = r;i >= l;i-- ) result.push_back(matrix[b][i]);
            b--;
            if(t > b) break;
            for (int i = b; i >= t; i--) result.push_back(matrix[i][l]);
            l++;
            if (l > r) break;
        }
    return result;
    }
};

没什么问题,时间复杂度为0(m*n) 空间复杂度为0(1);
第二遍复习 没啥问题

39 数组中出现次数超过一半的数字(简单)

  • 这个题做过
  • 要么排序 要么用hash来做 要么用分治(后序)如果返回的众数的不想等 分别统计,分治的时间复杂度比较低
    在这里插入图片描述
    [leetcode刷题]剑指offer汇总_第8张图片
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};


  • 第二遍复习
    • 刚在汇总做过一遍 没啥问题

42连续子数组的最大和(简单)(动态规划)

[leetcode刷题]剑指offer汇总_第9张图片

  • 写了一遍 没啥问题 很容易想到动态规划的问题 空间可以节省
  • 写了两个 第二个节省空间的写法
class Solution {
public:
    int maxSubArray(vector<int>& nums) 
    {
        vector<int> dp(nums.size());//初始化大小 因为后面要用下标
        dp[0] = nums[0];
        int max_ = nums[0];
        for(int i = 1 ;i<nums.size();i++)
        {
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            max_=max(dp[i],max_);
        }
        return max_;

    }
};
class Solution {
public:
    int maxSubArray(vector<int>& nums) 
    {
        //vector dp(nums.size());//初始化大小 因为后面要用下标
        int dp = nums[0];
        int max_ = nums[0];
        for(int i = 1 ;i<nums.size();i++)
        {
            dp=max(dp+nums[i],nums[i]);
            max_=max(dp,max_);
        }
        return max_;

    }
};

没什么问题,时间复杂为0(n)空间复杂度为0(1)

  • 第二遍复习
    • 这题写了太多次了

45把数组排成最小的数 (中等)(特别)

[leetcode刷题]剑指offer汇总_第10张图片
[leetcode刷题]剑指offer汇总_第11张图片

  • 我想的是全排序 然后进行比较大小
  • 题解用的是类似数学证明的方式
  • 可以适用于快排 和 sort+内置排序规则
  • 学习一下sort的自定义排序
  • cmp 可以写成仿函数 也可以是普通函数 注意看差别
  • to_string c++ 把整形转换成字符串 补充

bool cmp(const string& x,const string& y)

{
    return x + y < y + x;
}

class Solution {
public:
    string minNumber(vector<int>& nums) {
        vector<string> strs;
        string res;
        for(int i = 0; i < nums.size(); i++)
            strs.push_back(to_string(nums[i]));
        sort(strs.begin(), strs.end(), cmp);
        for(int i = 0; i < strs.size(); i++)
            res.append(strs[i]);
        return res;
    }
};

};


class cmp
{
    public:
    bool operator()(const string& x,const string& y) const
    {

        return x + y < y + x;
    }

};


class Solution {
public:
    string minNumber(vector<int>& nums) {
        vector<string> strs;
        string res;
        for(int i = 0; i < nums.size(); i++)
            strs.push_back(to_string(nums[i]));
        sort(strs.begin(), strs.end(), cmp());
        for(int i = 0; i < strs.size(); i++)
            res.append(strs[i]);
        return res;
    }
};


复习仿函数的写法 时间复杂度nlogn 主要是排序 空间复杂度logn sort栈空间的需要

  • 第二遍复习
    • 没做过还真不知道…

51 数组中的逆序对 (困难)(待补充)

[leetcode刷题]剑指offer汇总_第12张图片

  • 有点眼熟

56 数组中数字出现的次数(中等)(位运算)

  • 这个题需要掌握内容
    • 异或运算 相同位为0 不同位为1
    • 两个相同的数进行异或 为0
    • 0和任何数进行异或 不变
    • 1和任何数进行异或 就是取反
    • 与运算 &1常用来得到最低位 &0 就是用来清空对应位 所以 所以与 << 配合使用能找到第一个出现的1(从末尾开始)
    • 复习一下之前常用的x&(x-1)用来消除最低位的1

while((n & m) == 0)          
            m <<= 1;
-   如果只有一个数出现一次 直接异或运算就可以得到结果
- 我们这个题就是要对他进行分组 ,利用两个数不同的时候全部数进行异或得到的就是x和y的异或 我们通过找最低位的1(就是知道这两个数的二进制再这个位置不同 一个是 0一个是1 那么我们就可以把他分成两组了 其他的数分在那一组无所谓 但是肯定的是相同数肯定分在了一边)

[leetcode刷题]剑指offer汇总_第13张图片

  • 修正的版本第二个
  • (nums[i] & m) == 0 这个地方一直写错 nums[i] & m == 0 这个还是有区别的 优先级的问题
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int x = 0, y = 0, n = 0, m = 1;
        for(int num : nums)         // 1. 遍历异或
            n ^= num;
        while((n & m) == 0)         // 2. 循环左移,计算 m
            m <<= 1;
        for(int num : nums) {       // 3. 遍历 nums 分组
            if(num & m) x ^= num;   // 4. 当 num & m != 0
            else y ^= num;          // 4. 当 num & m == 0
        }
        return vector<int> {x, y};  // 5. 返回出现一次的数字
    }
};

作者:jyd
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/solution/jian-zhi-offer-56-i-shu-zu-zhong-shu-zi-tykom/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int x = 0, y = 0, n = 0, m = 1;
        //全部异或
        for(int i =0;i<nums.size();i++)
        {
            //初始值为0 0和任何数异或都是本身
            n=n^nums[i];
        }
        //找到第一个不同的二进制 或者说修改m的值
        while((n & m) == 0)//如果是0 那就继续向前找
        {
            m=m<<1;
        }
        //分成两个组通过 和m与
        for(int i=0;i<nums.size();i++)
        {
            //其实就两种情况 00000 001000 这样的
            if((nums[i] & m) == 0) x=x^nums[i];
            else y=y^nums[i];
        }
    return {x,y};

    }
};

重新写了一遍没什么问题,中间一部分修改成这样
时间复杂度度是0(n)几个for循环遍历 空间复杂度0(1)主要就是几个变量

  while(true)
        {
            if((n&m) == 0)
            {
                m =m <<1;
            }
            else
            {
                break;
            }
        }
  • 第二遍复习
    • 位运算一定要注意优先级问题 ,不清楚 就加上()
    • 第二个就是 注意代码中m的使用很聪明 代码中是移动m
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) 
    {
        int n=0;//第一次异或放的结果
        int m=1;
        for(int i =0;i<nums.size();i++)
        {
            n=n^nums[i];
        }
        while(true)
        {
            if((n&m) == 0)
            {
                m =m <<1;
            }
            else
            {
                break;
            }
        }
        int x =0;
        int y=0;
        for(int i =0 ;i<nums.size();i++)
        {
            if((nums[i] & m) == 0) x=x^nums[i];
            else y=y^nums[i];
        }
        return {x,y};



    }
};

070只出现一次的数字(简单)(hash)

'- 如果可以使用额外的空间 无论是用hash 或者自带的sort排序都能做 如果不能的或要么自己写快排或者题解中的位运算

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

66 构建乘积数组(中等)(dp)

[leetcode刷题]剑指offer汇总_第14张图片

  • 下面的代码自己写了 就是超时了…说明之前的方法不行 要保存中间结果的感觉
  • 看了题解果然需要保存中间结果 分成两个三角形 第二个代码我自己修正后写的
  • dp【i】=dp【i-1】*num【i-1】正三角的dp

[leetcode刷题]剑指offer汇总_第15张图片

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        vector<int> result;
        for(int i=0;i<a.size();i++)//外层循环控制第几个格子
        {
            int sum = 1;//每次初始化为1
            for(int j = 0;j<a.size();j++)
            {
                if(i == j) continue;//自己不算呗
                sum = sum*a[j]; 
            }
            result.push_back(sum);
        }
        return result;

    }
};
class Solution {
public:
    vector<int> constructArr(vector<int>& a) 
    {
        int n = a.size();
        if(n == 0) return {};
        vector<int> b(n,1);//第一个正三角
        b[0] = 1;//第一行只有一个数
        for(int i =1;i<n;i++)
        {
            //注意这个方程 第二行 用第一个数 所以是i-1;
            b[i] =b[i-1] * a[i-1]; 
        }
        //处理第二个三角 为了保存中间结果 只能从倒数第二行开始
        int temp=1;
        for(int i = n-2 ; i >= 0; i--)
        {
            //需要一个类似b的单独一个数保存上一次的结果 (弄成一个数组浪费空间)        //倒数第二行 用倒数第一个数 所以+1
            temp = temp*a[i+1];//

            b[i] =b[i]*temp;
        }
        return b;

    }
};
class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        int len = a.size();
        vector<int> b(len, 1);
        if(len == 0) return b;
        int left = 1, right = 1;
        for(int i = 0; i < len; i++){
            b[i] *= left;
            left *= a[i];               // 持有左边的所有数的乘积

            b[len - i - 1] *= right;
            right *= a[len -i - 1];     // 持有右边的所有数的乘积
        }
        return b;
    }
};

复习:太粗心了 把*写成了+号找了半天

class Solution {
public:
    vector<int> constructArr(vector<int>& a) 
    {
    //n行n列的矩阵 初始化dp[0] 
    //dp[i]=dp[i-1]*num[i-1];
    int n = a.size();
    if(n == 0) return {};
    if(n == 0) return {};
    vector<int> dp(n,1);
    dp[0]=1;
    //求了上三角
    for(int i=1;i<n;i++)
    {
        dp[i]=dp[i-1]*a[i-1];
    }
    //开始求下三角 从倒数第二行开始计算 倒数第一个数 下标是n-1
    int temp =1;
    for(int i = n-2;i>=0;i--)
    {
        temp=temp*a[i+1];
        dp[i]=dp[i]*temp;
    }
    return dp;

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

字符串

05 替换空格(简单)

[leetcode刷题]剑指offer汇总_第16张图片

  • 使用额外的空间 比较简单
class Solution {
public:
    string replaceSpace(string s) {
        string result;
        for(int i =0;i<s.length();i++)
        {
            if(s[i]==' ')
            {
                result.push_back('%');
                result.push_back('2');
                result.push_back('0');
            }
            else
            {
                result.push_back(s[i]);
            }
        }
        return result;

    }
};

这次复习补充了一个不需要额外空间的写法,快慢指针k表示待填入的空格,i表示判断的元素

class Solution {
public:
    string replaceSpace(string s) 
    {
        int count=0;
        int n=s.size();
        for(int i=0;i<s.size();i++)
        {
            if(s[i]==' ') count++;
        }
        s.resize(s.size()+count*2);
        //快慢指针
        int k = s.size()-1;//指向最后一个下标 
        for(int i=n-1;i>=0;i--)
        {
            if(s[i] == ' ')
            {
                s[k--]='0';
                s[k--]='2';
                s[k--]='%';
            }
            else
            {
                s[k--]=s[i];
            }
        }
        return s;
    }
};
  • 第二遍复习
    • 主要复习一下不需要额外空间的写法

567字符串的排列(中等)(滑动窗口)

[leetcode刷题]剑指offer汇总_第17张图片

  • 这个题有点眼熟 不就是判断子串么 滑动窗口+map 或者 自定义的数组不就好了不就好了 严格来说是子序列 如果是子串也一样 sub_str 切出来比较就好了
  • 第一次写用map答案错误。。。估计unordered 无序存储 不能用== 比较把 修改成数组没啥问题,这个有一个前提就是都是小写 如果没说加一个转换大小写就好了
class Solution {
public:
    bool checkInclusion(string s1, string s2) 
    {
        unordered_map<char,int> map1;
        unordered_map<char,int> map2;
        //用来比较的
        int n = s1.size();
        int m = s2.size();
        for(int i=0;i<s1.size();i++)
        {
            map1[s1[i]]++;
        }
        for(int i=0;i<n-1;i++)//如果长度为3 就先放2个 固定的
        {
             map2[s2[i]]++;
        }
        for(int i=n-1;i<m;i++)
        {
            map2[s2[i]]++;
            if(map1 == map2) return true;
            map2[s2[i-n+1]]--;// n =3 2-3+1

        }
        return false;

    }
};
class Solution {
public:
    bool checkInclusion(string s1, string s2) 
    {
        vector<int> map1(26,0);
        vector<int> map2(26,0);
        //unordered_map map1;
        //unordered_map map2;
        //用来比较的
        int n = s1.size();
        int m = s2.size();
        if(n > m) return false;
        for(int i=0;i<s1.size();i++)
        {
            map1[s1[i]-'a']++;
        }
        for(int i=0;i<n-1;i++)//如果长度为3 就先放2个 固定的
        {
             map2[s2[i]-'a']++;
        }
        for(int i=n-1;i<m;i++)
        {
            map2[s2[i]-'a']++;
            if(map1 == map2) return true;
            map2[s2[i-n+1]-'a']--;// n =3 2-3+1

        }
        return false;

    }
};
  • 第二遍复习
    • 没有去重新写了,但是第一眼看确实能反映做法

50第一个只出现一次的字符(简单)

  • 这个之前好像做过类似的,这种统计频率的我觉的都可以用自定义的数组计数(模拟map)
  • 自己写了一遍 没啥问题 就是第二个循环是根据原字符串的顺序查找的
class Solution {
public:
    char firstUniqChar(string s) {
        vector<int> a(26,0);
        for(int i=0;i<s.length();i++)
        {
            a[s[i]-'a']++;
        }
        for(int i =0;i<s.length();i++)
        {
            if(a[s[i]-'a']==1) return s[i];
        }
        return  ' ';

    }
};
  • 第二遍复习
    • 做了很多遍了

58 左旋转字符串(简单)

[leetcode刷题]剑指offer汇总_第18张图片

  • 第一个想法不就是分成两个子串拼接一下不就好了…
  • 补充一个思路 之前做过了一个方法 翻转3次 就可以不使用额外的空间了
  • 复习一下substr(下标,长度) 我都忘记了…
  • 两种方法都没啥问题…
class Solution {
public:
    string reverseLeftWords(string s, int n) {
        int size = s.size();
        string a= s.substr(0,n);
        string b= s.substr(n,size-n);
        return b+a;

    }
};
class Solution {
public:
    string reverseLeftWords(string s, int n) {
        
        reverse(s.begin(),s.begin()+n);
        reverse(s.begin()+n,s.end());
        reverse(s.begin(),s.end());
        return s;

    }
};
  • 没啥问题 一遍过

557 反转字符串中的单词3(简单)

  • leetcode找不到在这里插入图片描述
  • 只有这个,我们不使用额外的空间。有点想移动0 重复数的改进版 需要快慢指针 还需要点改进 毕竟要反转单词

[leetcode刷题]剑指offer汇总_第19张图片

  • 有一些需要注意的细节
  • 1 遇到空格跳过
  • 2 i赋值给j
  • 3 whilej不断后移找到结尾(此时j指向的是空格)
  • 4 翻转
  • 5 拷贝(注意条件 i
  • 6 补上空格
  • 7 修改i的值 j赋值给i
  • 8 erase 删除多余部分
class Solution {
public:
    string reverseWords(string s) {
        //reverse(s.begin(), s.end());//首先翻转整个列表 

        int n = s.size();//求一下藏毒
        int k = 0;//用来向前移动的
       
        for (int i = 0; i < n; i ++ ) //一次循环就是翻转一个单词
        {
           
            if (s[i] == ' ') continue;//如果是空格后移
            int j = i;//去除出空格后 就可以开始把i复制给j了
            //如果没到结尾 那就一直j 这边的j你想想是不是可以表示个数 他就是最后一个字母的后一位
            while (j < n && s[j] != ' ') j++;
            reverse(s.begin() + i, s.begin() + j);//翻转 是j不是j-1 因为要表示最后有一个字母的后一个
            while( i < j) s[k++] = s[i++];//(易错)开始前移动 这边很巧妙 并且是i
            if (k != 0) s[k ++ ] = ' ';//用来填充一个空格 一开始不用填充,移动后 马上填充一个空格
            i = j;//修改初始点
        }

        s.erase(s.begin() + k-1, s.end());//删除后面的空格
        return s;
    }
};


  • 第二遍复习
    • 没有去重新写了 做了很多次了

151翻转字符串里的单词(中等)

  • 之前写过 主要区别于上一题
  • 第二遍复习
    • 没啥问题

8 字符串转换整数(中等)

  • 之前做过 ,并不好做
class Solution {
public:

	/* 辅助函数: 
		- 返回整数是否超过整数范围
	*/
	bool tooLarge(long long res) {
		return res >= INT_MAX || res <= INT_MIN;
	}

	/*
		函数功能: 输入字符串, 输出对应的整数.
		逻辑:
			1. 去除前置空格
			2. 检查下一个字符是正还是负, 若两者都不存在, 假设为正.
			3. 往后读字符, 直到到达非数字字符, 或是到达字符串的末尾. 
			4. 把读入的字符转为带符号整数
			5. 如果整数超过整数范围, 做截断.
	*/
	int myAtoi(string s) {
		int i = 0;
		int len = s.length();
		if (len == 0) return 0;

		// 1. 去除前置空格
		while (i < len && s[i] == ' ')++i;

		// 2. 检查下一个字符是正还是负, 若两者都不存在, 假设为正
        if(isdigit(s[i]) == false && s[i] != '-' && s[i] != '+') return 0;
		int poitiveSign = (s[i] != '-') ? 1 : -1;
		if (isdigit(s[i]) == false) ++i;

		// 3. 往后读字符, 直到到达非数字字符, 或是到达字符串的末尾.
		long long res = 0;
		bool beginPos = true;
		while (i < len && isdigit(s[i])) {
			int digit = s[i] - '0';
			// 4. 把读入的字符转为整数
			res = res * 10 + digit;
			bool stop = tooLarge(res * poitiveSign);
			if (stop) return poitiveSign == 1 ? INT_MAX : INT_MIN;     // 数字的绝对值已经很大了, 后面的数不用再考虑. 
			++i;
		}
		return (int)(res * poitiveSign);
	}
};
  • 第二遍复习
    • 就不重新做了

19正则表达式匹配(困难)(dp)

[leetcode刷题]剑指offer汇总_第20张图片

[leetcode刷题]剑指offer汇总_第21张图片

[leetcode刷题]剑指offer汇总_第22张图片

  • 代码注释写的比较清楚
  • 参考1
  • 参考2
class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m+1, vector<bool>(n+1, false));
        dp[0][0] = true;
        // 初始化首行 注意题目所说的不可能出现连续的* 也就是说 *  或者**都是不符合的 所以当j=1 不存在if的可能 不会出现数组越界
        for(int j = 2; j <= n; j += 2)//只需要确定偶数位置为* 并且 j-2的位置为true即可
            dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
        // 状态转移
        for(int i = 1; i <= m; i++) 
        {
            for(int j = 1; j <= n; j++) 
            {
                if(p[j - 1] == '*') 
                {
                    //其实下面这个代码 写成 三个||更好理解只要有一个匹配上 那就匹配上
                    //其实就是只要下面三种情况满足一种 那就是为true 表示能匹配上
                    if(dp[i][j - 2]) dp[i][j] = true;                              // 1.匹配0次 
                    else if(dp[i - 1][j] && s[i - 1] == p[j - 2]) dp[i][j] = true; // 2.匹配1次或者多次
                    else if(dp[i - 1][j] && p[j - 2] == '.') dp[i][j] = true;      // 3.其实和第二种情况是一样的
                } 
                else 
                {
                    if(dp[i - 1][j - 1] && s[i - 1] == p[j - 1]) dp[i][j] = true;  // 1.
                    else if(dp[i - 1][j - 1] && p[j - 1] == '.') dp[i][j] = true;  // 2.
                }
            }
        }
        return dp[m][n];
    }
};



class Solution {
public:
    bool isMatch(string s, string p) 
    {
        int m = s.size();
        int n = p.size();
        vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
        dp[0][0]=true;
        //初始化 唯一的只有一种可能是true 偶数为* 
        for(int j=2;j<=n;j+=2)
        {
            if(dp[0][j-2] == true && p[j-1] == '*') dp[0][j]=true;
            else
            {
                break;//遇到不是的 直接退出了 后面的无需判断 都是false
            }
        }


        //状态转移 双循环
        for(int i = 1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                //大情况就分两种 p的最后一个字母是不是*
                if(p[j-1]=='*')
                {
                    if(dp[i][j-2]==true)//匹配0个单词 那就看s少掉一个 p少掉2个的匹配情况(准确的说 如果是false不能决定这边)
                    {
                        dp[i][j]=true;
                    }
                    //情况二 就是把匹配一个 或者匹配多个的情况写在了一起 
                    //dp[i-1][j-2] &&s[i-1] == p[j-2]这是匹配一个的
                    //dp[i-2][j-2] &&...这是匹配两个的
                    //我们推理 其实可以想想 dp[i-1][j-2] 和 dp[i-1][j]是等价的(如果原先的为true 那么后面这个也一定为true 因为变换为匹配0个) 这样的好处在于 我们保留了* 就可以多次复制单词进行匹配 
                    else if(dp[i-1][j]&&s[i-1]==p[j-2])
                    {
                        dp[i][j]=true;
                    }
                    else if(dp[i-1][j]&&p[j-2]=='.')
                    {
                        dp[i][j]=true;
                    }
                }
                else//表示是字母或者是.
                {
                    if(dp[i-1][j-1] && s[i-1]==p[j-1] || dp[i - 1][j - 1] && p[j - 1] == '.') dp[i][j]=true;
                }
            }
        }
        return dp[m][n];


    }
};

时间复杂度和空间复杂度都是o(mn)第二遍写出现一个错误 把赋值 写成判断 == 第二个错误就是初始化j+=2 写错了

  • 第二遍复习
    • 分析一下思路
    • 首先考虑长度为0 所以dp的下标表示长度 初始化全部为false dp表示 长度分别为i j 的能否匹配上
    • 我们要分多种情况 多个递推方程
    • 大的我们分成 最后一个是* 和不是*两种情况
    • 在是**的情况细分小情况 匹配0个 和配合任意多个 以及倒数第二个是. 情况。主要是主义匹配任意多个 我们保存* 在匹配的一个基础上修改了 注释很全

20表示数值的字符串(中等)(待补充)

[leetcode刷题]剑指offer汇总_第23张图片

[leetcode刷题]剑指offer汇总_第24张图片

class Solution {
private:
    // 整数的格式可以用[+|-]B表示, 其中B为无符号整数
    bool scanInteger(const string s, int& index){

        if(s[index] == '+' || s[index] == '-')
            ++index;

        return scanUnsignedInteger(s, index);
    }
    
    bool scanUnsignedInteger(const string s, int& index){

        int befor = index;
        while(index != s.size() && s[index] >= '0' && s[index] <= '9')
            index ++;

        return index > befor;
    }
public:
    // 数字的格式可以用A[.[B]][e|EC]或者.B[e|EC]表示,
    // 其中A和C都是整数(可以有正负号,也可以没有),而B是一个无符号整数
    bool isNumber(string s) {

        if(s.size() == 0)
            return false;
        int index = 0;

        //字符串开始有空格,可以返回true
        while(s[index] == ' ')  //书中代码没有该项测试
            ++index;

        bool numeric = scanInteger(s, index);

        // 如果出现'.',接下来是数字的小数部分
        if(s[index] == '.'){

            ++index;

            // 下面一行代码用||的原因:
            // 1. 小数可以没有整数部分,例如.123等于0.123;
            // 2. 小数点后面可以没有数字,例如233.等于233.0;
            // 3. 当然小数点前面和后面可以有数字,例如233.666
            numeric = scanUnsignedInteger(s, index) || numeric;
        }

        // 如果出现'e'或者'E',接下来跟着的是数字的指数部分
        if(s[index] == 'e' || s[index] == 'E'){

            ++index;

            // 下面一行代码用&&的原因:
            // 1. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
            // 2. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4
            numeric = numeric && scanInteger(s ,index);
        }

        //字符串结尾有空格,可以返回true
        while(s[index] == ' ')
            ++index;
        cout << s.size() << " " << index;   //调试用

        return numeric && index == s.size();
    }
};

54 字符流中第一个不重复的字符(待补充)

链表

06从尾到头打印链表(简单)

[leetcode刷题]剑指offer汇总_第25张图片

  • 第一个想法放到栈里面 再取出啦不就号了
  • 题解确实就是我想的那种方法,但是有一个不需要额外空间的我修改放在下面了 递归遍历(后续 不断深入 从后返回 很聪明的想法)
  • 栈的代码也放在下面了
class Solution {
public:
    vector<int> reversePrint(ListNode* head) 
    {
        vector<int> result;
        dfs(head,result);
        return result;
    }
    void dfs(ListNode* head,vector<int>& result)
    {
        //终止条件
        if(head==NULL)
        {
            return ;
        }
        //进入下一层
        dfs(head->next,result);
        //后序 处理逻辑
        result.push_back(head->val);
    }
};
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int> stack;
        vector<int>ans;
        while(head!=NULL)
        {
            stack.push(head->val);
            head = head->next;
        }
        while(stack.empty()!=true)
        {
            ans.push_back(stack.top());
            stack.pop();
        }
        return ans;

    }
};

都写了一遍没什么问题,时间复杂度和空间复杂度两个都是0(n)递归也需要消耗0(n)的栈空间

  • 第二遍复习
    • 从尾到头首先要想到这是栈的特性
    • 第二个方法其实可以取出最后reverse
    • 第三个 做过就知道 想象一个子节点的树 虽然不需要吧结果返回给上一层 但是可以及时把值存起来

22 链表中倒数第k个节点(简单)(三种做法)

[leetcode刷题]剑指offer汇总_第26张图片

  • 第一个解法 很聪明 双指针解法

[leetcode刷题]剑指offer汇总_第27张图片

  • 第二个解法 就是栈的方式
  • 第三种方式 就是递归的方式
  • 三种方法都是自己写的 没啥问题。第三种方法我把存储的作为成员变量。如果这边作为参数传递 也需要引用 因为 这一层为空 传入下一层是进行了变量拷贝 然后他把变量对应的地址换了 这边的地址就是值 值是临时的 切记切记
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode* first = head;
        ListNode* second = head;
        for(int i=0;i<k;i++)
        {
            second =second->next;
        }
        while(second!=NULL)
        {
            first=first->next;
            second=second->next;
        }
        return first;

    }
};
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        stack<ListNode*> stack;
        ListNode* result;
        while(head!=NULL)
        {
            stack.push(head);
            head = head->next;
        }
        for(int i=0;i<k;i++)
        {
            if(i == k-1)  result = stack.top(); 
            else
            {
                stack.pop();
            }

        }
        return result;
    }
};
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
   // 1 2 3 4  倒数1
   ListNode* result=NULL;
    int dfs(ListNode* head,int k)
    {
        //终止条件
        if(head==NULL)
        {
            return 1;
        }
        //进入下一层
        int res = dfs(head->next,k);
        //后序 处理逻辑
        if(res == k)
        {
            result= head;//保存一下结果
        }
        return res+1;


    }
    ListNode* getKthFromEnd(ListNode* head, int k) 
    {

        int res = dfs(head,k);
        return result;
        

    }
};

补充说明:双指针写法时间复杂度是0(n)空间复杂度是0(1) 栈的递归时间复杂度一样主要需要额外的空间

  • 第二遍复习
    • 主要是复习多种写法
    • 栈的那个k的判断 举个一个节点的例子 判断是不是要 res ==k就好了
    • 双指针法很巧妙

24 反转链表(简单)

  • 就是3个一组
  • 比较简单
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
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;

    }
};

没什么问题,while的判断条件可以先写循环体再做判断好理解一

  • 第二遍复习
    • 在汇总复习了一遍了 顺便复习一个二个翻转的注释

92反转链表2(中等)

[leetcode刷题]剑指offer汇总_第28张图片

  • 做这个题 一定要举个例子 比如right =2 left =4 要不边界分不清楚 特别麻烦 这个还设置了岗哨
  • 有个东西 要非常清楚 就是 ListNode* temp =** 然后temp 1= temp temp = *****。 这个意思是temp这个指针(变量)保存了一个地址然后 temp1 也来保存这个地址 随后temp1这个指针不保存这个地址了 这个地址就只有temp知道了 这个好容易混掉…
  • 修改要清楚 temp指针 更换保存的地址 不影响temp1 保存的内容 就是这个地址
  • 然后temp1 这个地址内一个成员是head的地址 所以这个也是固定的 不变的 永远是第一个
  • 为什么设置岗哨( )呢

[leetcode刷题]剑指offer汇总_第29张图片

  • 1需要注意几个变量的意思
  • 2 index从1开始
  • 3 l保存初始的2
  • 4cur!=NULL && index <= right 防止right同时表示链表的最后一个
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        // 有心情记得delete或者用智能指针,没空就算了
        ListNode* temp = new ListNode(-1);
        temp->next = head;
        ListNode* temp1 = temp;//保存temp的地址 temp这个地址有head的地址
        int index = 1;//
        //假设 left是2 right是4
        while (index < left && temp->next!=NULL) 
        {
            temp = temp->next; //我这边移动到 1的位置退出了
            index++;//此时为2
        }
        ListNode* pre = nullptr;//前为空
        
        ListNode* cur = temp->next;//指向第一个待修改的值
        ListNode* l = temp->next;//这个就是保存一下2这个节点 现在是头 待会是尾
        while (cur!=NULL && index <= right)//为了防止right指向最后一个 
        {
            //举个例子  234反转 需要指向3次next index 初始为2 right为4 符合要求 《=
            ListNode* next = cur->next;//1 保存下一个节点
            cur->next = pre;//2 修改指向
            pre = cur;//3 更换前一个节点
            cur = next;//4 更换当前节点
            index++;
        }

        //此时 pre是4这个节点 cur是5这个节点(想想正常的反转链表此时cur为空)
        
        temp->next = pre;
        if (l)
        {
            l->next = cur;
        }
        return temp1->next;
        //return temp;


    }
};


第一遍复习:自己写代码想要全部考虑到确实有点难,
首先要明确几个变量的意思 index表示下标从1开始移动因为我们要和right和left比对找到开始的节点和结束的节点 left和right从0开始的。 pre 和 cur不用说了 temp1 表示岗哨 temp表示前驱。还有一个就是特殊情况,left=1 和 right=最后一个,所以这边对应两个cur!=nullptr if(l!=nullptr)容易忘记

  • 第二遍复习
    • 重新看一遍还是学到了很多

25 合并两个排序的链表(简单)

  • 我只用了哨兵 和普通的判断方式 挂载新的节点上。
  • 好像还可以用递归的方式 可以回头看看
  • 哨兵 temp 在 进行 temp->next=l1 地址空间就保存第一个节点的值了 然后 temp=temp->next temp不存储这个地址了 只有temp1 还有 ;

[leetcode刷题]剑指offer汇总_第30张图片

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) 
    {
        ListNode* temp =new ListNode(-1);
        ListNode* temp1 =temp;
        //因为 l1表示下一个待处理的值
        while(l1!=NULL && l2!= NULL)
        {
            if(l1->val < l2->val)
            {
                temp->next=l1;
                l1 =l1->next;//移动到下一个节点
            }
            else
            {
                temp->next=l2;
                l2=l2->next;
            }
            temp=temp->next;
        }
        if(l1!=NULL)
        {
            temp->next = l1;//全部挂上去
        }
        if(l2!=NULL)
        {
            temp->next = l2;
        }
    return temp1->next;
    }
};
  • 第二遍复习 没什么问题

35 复杂链表的复制(中等)

  • 题目要求的是深拷贝
    [leetcode刷题]剑指offer汇总_第31张图片
  • 这种查找的 和 那个缓存lru好像
  • 用hash来做时间复杂度是o(n) 空间复杂度也是o(n)
  • 其实就是构建一个hash 键是原节点 值是神拷贝的新节点(只有val一个信息 没有next 和 random两个的信息 此时都为空 )
  • map[cur]->next 这个表示当前这个节点的复制体
  • map[cur->next] 这个表示下一个节点分复制体
  • map[cur]->next = map[cur->next];合起来就表示复制体连接
  • map[cur]->random = map[cur->random];同理
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        Node* cur = head;
        unordered_map<Node*, Node*> map;
        // 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while(cur != nullptr) {
            map[cur] = new Node(cur->val);
            cur = cur->next;
        }
        cur = head;//从头开始建立关系 
        // 4. 构建新链表的 next 和 random 指向
        while(cur != nullptr) {
            map[cur]->next = map[cur->next];
            map[cur]->random = map[cur->random];
            cur = cur->next;
        }
        // 5. 返回新链表的头节点
        return map[head];
    }
};


  • 第二遍复习
    • 没啥问题 就是相当于如何实现链表的深拷贝
    • 这边用map就是实现o1的查询

52 两个链表的第一个公共节点(简单)(特殊解法)

  • 第一个想法就是hash,没啥问题 就是有点慢…
  • 第二个方法有点巧妙,循环为 两个节点不想等 就一起后移,当谁为空 就去走另外一个人的陆 因为路径一样一定会相遇 如果不想交的两条路 最后一样能退出循环 因为都走到了空 文字解释如下
  • 太6了,我的理解: 两个链表长度分别为L1+C、L2+C, C为公共部分的长度,按照楼主的做法: 第一个人走了L1+C步后,回到第二个人起点走L2步;第2个人走了L2+C步后,回到第一个人起点走L1步。 当两个人走的步数都为L1+L2+C时就两个家伙就相爱了
/**
 * 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) {
        ListNode* result = NULL;
        unordered_map<ListNode*,int> map;
        while(headA!=NULL)
        {
            map[headA] =1;
            headA=headA->next;
        }
         while(headB!=NULL)
         {
             if(map.count(headB)==1)
             {
                 result = headB;
                 break;
             }
             headB=headB->next;
         }
         
         return result;
    }
};

[leetcode刷题]剑指offer汇总_第32张图片



class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) 
        {
            return nullptr;
        }
        ListNode *pA = headA, *pB = headB;
        //一定不会死循环 两种情况
        //1 相遇了
        //2 同时为空退出了 
        //他们走过的路径是一样的 
        while (pA != pB) 
        {
            if(pA == nullptr) pA = headB;
            else pA = pA->next;

            if(pB == nullptr) pB = headA;
            else pB = pB->next;
        }
        return pA;
    }
};



  • 第二遍复习
    • 其实就一个地方容易错 就是当pa==null 更换为b的地址已经相当于走一步 不要再 next

22 链表中环的入口节点(中等)

[leetcode刷题]剑指offer汇总_第33张图片

  • 写了一遍没啥问题 就是 错误的地方在于忘记一开始的特判
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
   if(head==nullptr||head->next==nullptr)
        {   
            return nullptr;
        }
        ListNode* slow=head;
        ListNode* quick=head;
        while(quick->next!=nullptr&&quick->next->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;
    }
};

时间复杂度为0(n)+0(n)=0(n),补充一种需要额外空间的做法。就是用hash表…差点忘记了还有这岔

  • 第二遍复习
    • 别忘了 hash也可以做 判别重复好像确实还可以

83删除排序链表中的重复元素(简单)

在这里插入图片描述

  • 第一个想法就是沿用数组重复元素删除的方式,看了下题解 一样 唯一的区别在于数组重复的 我们使用快慢指针的方式 把第一个不重复的数放在重复格子上
/**
 * 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* deleteDuplicates(ListNode* head) 
    {
        if(head==nullptr)
        {
            return head;
        }
        ListNode* cur = head;
        //不为空就可以比较
        while(cur->next!=NULL)
        {
            if(cur->val == cur->next->val)
            {
                cur->next=cur->next->next;
            }
            else
            {
                cur = cur->next;
            }
        }
        return head;

    }
};


没什么问题 时间复杂度0(n)

  • 第二遍复习
    • 做了点点修改
    • 我们先写 while的内部情况 再写while的判断 出现了 cur->next->next 所以cur-》next不为空 同理加上一个 cur!=null
/**
 * 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* deleteDuplicates(ListNode* head) 
    {
        if(head==nullptr)
        {
            return head;
        }
        ListNode* cur = head;
        //不为空就可以比较
        while(cur!=NULL && cur->next!=NULL)
        {
            if(cur->val == cur->next->val)
            {
                cur->next=cur->next->next;
            }
            else
            {
                cur = cur->next;
            }
        }
        return head;

    }
};

二叉树

07 重建二叉树(中等)

[leetcode刷题]剑指offer汇总_第34张图片

  • 这个题做过了 ,回头复习再说
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) {
            return nullptr;
        }
        
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
       
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < n; ++i) {
            index[inorder[i]] = i;
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 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
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    //先序遍历 
    TreeNode* dfs(const vector<int>& preorder,vector<int>& inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right)
    {
        //递归终止条件
        if(preorder_left > preorder_right) return NULL;
        //分别找到前序和中序中的跟节点下标
        int preorder_root=preorder_left;
        int inorder_root=index[preorder[preorder_root]];
        TreeNode* root = new TreeNode(preorder[preorder_root]);

        //计算左子树的数量 左边界-右边界+1 但是取出上一层的跟节点
        int size_left=inorder_root-inorder_left+1-1;
        //右边界 =左边界+长度-1
        root->left=dfs(preorder,inorder,preorder_left+1,preorder_left+1+size_left-1,inorder_left,inorder_root-1);
        root->right=dfs(preorder,inorder,preorder_left+size_left+1,preorder_right,inorder_root+1,inorder_right);
        return root;
    }
    unordered_map<int,int> index;//存放值 下标
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) 
    {
        int n =preorder.size();
        for(int i =0;i< n;i++)
        {
            index[inorder[i]]=i;

        }
        //返回值肯定是节点 要挂上去 参数有两个序列 还有4个左和右
        TreeNode* result = dfs(preorder,inorder,0,n-1,0,n-1);
        return result;

    }
};

26 树的子结构(中等)(特别)

[leetcode刷题]剑指offer汇总_第35张图片

  • 第一个想法就是中序遍历完 或者先序 判断一个是不是另外一个子串 优化就像在递归的时候就进行判断。

  • 这个代码比较难理解因为递归的嵌套,因为我需要递归出所有的子结构 并且所有子结构递归得判断是否相同

class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if(B == NULL || A == NULL)
            return false;
        //遍历A中每个节点,A树中任一节点包含B就能返回true
        return iscontain(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }
    //包含:以A为根的数是否包含B(必须从A开始)
    bool iscontain(TreeNode* A, TreeNode* B){
        if(B == NULL)
            return true;
        if(A == NULL || A->val != B->val)
            return false;
        return iscontain(A->left, B->left) && iscontain(A->right, B->right);
    }
};
  • 第二遍复习
    • 看了一遍可以一遍过
    • 简单的说就是首先必须要有一个递归遍历A树的所有节点,还需要一个递归在上一个递归的每一层中以当前节点为基础做递归判断
    • 递归判断我们正常喜欢用void 但是其实用bool也可以 先序判断 后序把结果汇总,最终第一层得到结果
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) 
    {
        if(B==NULL || A==NULL) return false;
        
        int res2=dfs(A,B);
        int res1=isSubStructure(A->left,B) || isSubStructure(A->right,B);
        return res1 || res2;
    }
    bool dfs(TreeNode* A, TreeNode* B) 
    {
        //先序判断当前值
        //后续把判断结果返回给上一层 最后在第一个节点得到消息
        if(B==NULL)
        {
            return true;
        }
        if(A==NULL)
        {

        }
        if(A->val!=B->val) return false;
       return dfs(A->left,B->left) && dfs(B->left,A->left);
    }
};

27 二叉树的镜像(简单)

  • 这个题做过了 很容易想到 先序递归 交换两个节点
  • 想到一个点 就是返回值 你可以单独吧dfs做成一个函数 void 最后返回root节点就好 或者就像本题这样 没有去接收下面返回上来的返回值 只做了先序 最后只有第一个函数返回当前层的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:
    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(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
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* mirrorTree(TreeNode* root) {
        dfs(root);
        return root;
    }
};
  • 第二遍复习
    • 做过很多次了 没什么问题

32-1 从上往下打印二叉树(中等)

[leetcode刷题]剑指offer汇总_第36张图片

  • 层次遍历 用队列就好了 不重复了while 队列不为空
/**
 * 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<vector<int>> levelOrder(TreeNode* root)
     {
        vector<vector<int>> ret;
        queue<TreeNode*> que;
        vector<int> vec;
        int flat=1;
        if(root!=NULL)
        {
            que.push(root);
        }
        while(!que.empty())
        {
            TreeNode* node = que.front();//每次取出来一个
            que.pop();
            vec.push_back(node->val);//把值放入
            if (node->left) que.push(node->left);
            if (node->right) que.push(node->right);
            flat--;
            if(flat==0)
            {
                ret.push_back(vec);//二维存入一维
                flat=que.size();
                vec.clear();
            }

            
            
        }
        return ret;
    }
};

32-3 从上到下打印二叉树(中等)

[leetcode刷题]剑指offer汇总_第37张图片

  • 先放入一个错误的写法在下面可以自己模拟一下就知道为什么呢
  • 题解中最多的是使用双端队列 我觉的太麻烦了 发现一种比较好的思路就是 正常处理 翻转那一层的数据不就好了 有点点聪明

[leetcode刷题]剑指offer汇总_第38张图片

/**
 * 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<vector<int>> levelOrder(TreeNode* root)
     {
        vector<vector<int>> ret;
        queue<TreeNode*> que;
        vector<int> vec;
        int flat=1;
        int flat_=1;
        if(root!=NULL)
        {
            que.push(root);
        }
        while(!que.empty())
        {
            TreeNode* node = que.front();//每次取出来一个
            que.pop();
            vec.push_back(node->val);//把值放入
            if(flat_ % 2  ==0)
            {
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            else
            {
                if (node->right) que.push(node->right);
                if (node->left) que.push(node->left);
            }
            flat--;
            if(flat==0)
            {
                ret.push_back(vec);//二维存入一维
                flat=que.size();
                vec.clear();
                flat_++;
            }

            
            
        }
        return ret;
    }
};
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root)
     {
        vector<vector<int>> ret;
        queue<TreeNode*> que;
        vector<int> vec;
        int flat=1;
        int flat_=1;
        if(root!=NULL)
        {
            que.push(root);
        }
        while(!que.empty())
        {
            TreeNode* node = que.front();//每次取出来一个
            que.pop();
            vec.push_back(node->val);//把值放入
            if (node->left) que.push(node->left);
            if (node->right) que.push(node->right);
            flat--;
            if(flat==0)
            {
                if(flat_%2 == 1)
                {
                    ret.push_back(vec);//二维存入一维
                }
                else
                {
                    reverse(vec.begin(),vec.end());
                    ret.push_back(vec);//二维存入一维
                }
                flat_++;
                //ret.push_back(vec);//二维存入一维
                flat=que.size();
                vec.clear();
            }

            
            
        }
        return ret;
    }
};

33二叉树的后续遍历序列(中等)(特别)

[leetcode刷题]剑指offer汇总_第39张图片

[leetcode刷题]剑指offer汇总_第40张图片

  • 这个题用的是先序递归 一层一层往下 同时在返回值为bool类型 在发现不符合在逻辑处理就直return没有继续深入
  • 当然如果一直符合 就需要从后往前 一层一层返回true
  • 第二个代码为我自己写的 出现了一个错误 dfs(k,r-1) 不要写成dfs(k,r) `
  • 第二个错误就是l >= r 终止条件是>= 不要忽略大于 因为 举个例子 3 9两个元素 k就会=1 r=1 递归传入 就会dfs(1,0)
class Solution {
public:
    vector<int> res;
    bool verifyPostorder(vector<int>& postorder) {
        res = postorder;
        return dfs(0, postorder.size() - 1);
    }
    bool dfs(int l, int r)
    {
        if(l >= r) return true;//退出条件
        int root = res[r];//最后一个点是根结点
        int k = l;//从最左边开始
        while(k < r && res[k] < root) k++;//符合左子树的点
        for(int i = k; i < r; i++)//此时的k是右子树的第一个点
        {
            if(res[i] < root)//如果右子树小于根,说明不符合
            return false;
        }
        return dfs(l, k - 1) && dfs(k, r - 1);//逐步缩小范围
    }
};

作者:feng-sheng-he-li
链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/solution/jian-zhi-offerdi-33ti-ti-jie-er-cha-sou-qrz3z/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
    vector<int> res;//设置成全局数组 就不用传入了
    bool verifyPostorder(vector<int>& postorder) 
    {
        res = postorder;
        int n = postorder.size();
        bool result = dfs(0,n-1);
        return result;


    }
    //返回值 bool 参数左右边界
    bool dfs(int l, int r)
    {
        //1终止条件
        if(l >= r) return true; //只有一个元素
    
        // 2先序 当前层的逻辑
        int k = l;
        while(k < r && res[k] < res[r]) 
        k++;//指向下一个待比较的下标 跳出说明大于了
        for(int i =k;i<r;i++)
        {
            if(res[i] < res[r])
            return false;
        }
        //3进入下一层
        int check1 = dfs(l,k-1);
        int check2 = dfs(k,r-1);
        return check1 && check2;
    }
};

有返回值的也算后续遍历,就好像左子树已经return false没有继续深入了,右子树还在继续深入判断,代码其实还可以优化,加两个判断
res[i]这边的i总是容易写成k…

class Solution {
public:
    vector<int> res;
    bool dfs(int l ,int r)
    {
        if(l>=r) return true;
        int k =l;
        while(k < r && res[k]<res[r]) k++;
        for(int i=k;i<r;i++)
        {
            if(res[i]<res[r]) return false;
        }
        int check1 = dfs(l,k-1);
        if(check1 == false) return false;
        int cheac2 = dfs(k,r-1);
        if(cheac2 == false ) return false;
        return check1 && cheac2;

    }
    bool verifyPostorder(vector<int>& postorder) 
    {
        res= postorder;
        int n=postorder.size();
        bool result=dfs(0,n-1);
        return result;
    }
};
  • 第二遍复习
    • 大结构判断 小结构也要判断很容易想到递归
    • 递归判断所以返回值 bool 和 void+flat 都可以
class Solution {
public:
    vector<int> res;
    int flat=1;
    void dfs(int l ,int r)
    {
        if(l>=r) return;
        if(flat==0) return;
        int k = l;
        int root=res[r];
        for(int i=l;i<r;i++)
        {
            if(res[i]<root)
            {
                k++;//最后指向第一个大于的
            }
        }
        for(int i=k;i<r;i++)
        {
            if(res[i]<root) 
            {
                flat=0;
                return;
            }
        }
        dfs(l,k-1);
        dfs(k,r-1);
        

    }
    bool verifyPostorder(vector<int>& postorder) 
    {
        res= postorder;
        int n=postorder.size();
        dfs(0,n-1);
        return flat;
    }
};

112路径总和(简单)

  • 代码随想录

[leetcode刷题]剑指offer汇总_第41张图片

[leetcode刷题]剑指offer汇总_第42张图片
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?

[leetcode刷题]剑指offer汇总_第43张图片
[leetcode刷题]剑指offer汇总_第44张图片
[leetcode刷题]剑指offer汇总_第45张图片

  • 这个题很容易想到回溯 因为不是选择左就是选择右,需要注意的是这个是找到一个路径所以有返回值 写法有两种 全部遍历 如第二个代码 或者判断直接返回 第三个代码(这个我写的比较好)
  • 还有一个需要注意的就是终止条件 主要看2 3 代码的注释
class Solution {
private:
    bool traversal(TreeNode* cur, int count) 
    {
        if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
        if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回

        if (cur->left) 
        { // 左
            count -= cur->left->val; // 递归,处理节点;
            if (traversal(cur->left, count)) return true;
            count += cur->left->val; // 回溯,撤销处理结果
        }
        if (cur->right) 
        { // 右
            count -= cur->right->val; // 递归,处理节点;
            if (traversal(cur->right, count)) return true;
            count += cur->right->val; // 回溯,撤销处理结果
        }
        return false;
    }

public:
    bool hasPathSum(TreeNode* root, int sum) {
        if (root == NULL) return false;
        return traversal(root, sum - root->val);
    }
};


class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) 
    {
        //1终止条件 如果当前节点为空 就直接返回false 
        //为什么不用判断 sum 因为走到底的下一个 还没有返回说明肯定不成立了 否则 早在前面就返回了 这个题是由返回值的  
        if (root == NULL ) return false;
        //2当前层的逻辑 如果左右子树都为空并且 要减去的值和当前节点的值相同 返回
        if (root->left==NULL && root->right==NULL && sum == root->val) 
        {
            return true;
        }
        //否则减去当前层的值进入下一层
        sum =sum -root->val;
        //进入下一层 左
        bool check1 =hasPathSum(root->left, sum);
        sum =sum +root->val;//回溯
        //进入下一层 右

        sum =sum -root->val;
        bool check2 = hasPathSum(root->right, sum);
        sum =sum +root->val;
        return check1 ||check2;
    }
};


class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) 
    {
        //1终止条件 如果当前节点为空 就直接返回false 
        //为什么不用判断 sum 因为走到底的下一个 还没有返回说明肯定不成立了 否则 早在前面就返回了 这个题是由返回值的  
        if (root == NULL ) return false;
        //2当前层的逻辑 如果左右子树都为空并且 要减去的值和当前节点的值相同 返回
        if (root->left==NULL && root->right==NULL && sum == root->val) 
        {
            return true;
        }
        //否则减去当前层的值进入下一层
        sum =sum -root->val;
        //进入下一层 左
        bool check1 =hasPathSum(root->left, sum);
        if(check1 == true) return true;//我这样写是为了立刻返回
 
        //进入下一层 右
        bool check2 = hasPathSum(root->right, sum);
        if(check2 == true) return true;
        sum =sum +root->val;
        return false;//如果
    }
};


复习 注意对比第二三种的写法区别。第二种是没有立刻返回,只要左右子树一个满足 所以用|| 第三种是遇到就立刻都返回,如果左右都没返回一定是false
为什么想到回溯呢,因为非左就是右,你走进去左不符合就要回溯路径和进入右子树。

  • 第二遍复习是
    • 现在这种单纯的判断我都用void 返回值 还有一个需要注意的就是节点不一定是正数 一开始写少了当前节点为空和判断 和多了 sum《0的判断
    • 传入的是引用

113 路径总和二(中等)(重)(二叉树回溯)(二次!)

  • 上一题进行了修改
  • 这个代码写的顺序有点乱就是了
  • 我修改成最后一个代码
  • 总结就三种 一个是for循环的回溯 一个是 if if左右类型 (注意不是if else) 然后两段代码一直 还有一种就是二叉树进入左右 不需要分成两部分写 回溯一次 就要 就像下面最后一次代码
/**
 * 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> ret;
    vector<vector<int>> result;

  void hasPathSum(TreeNode* root, int sum) 
    {
        //1终止条件 如果当前节点为空 就直接返回false 
        //为什么不用判断 sum 因为走到底的下一个 还没有返回说明肯定不成立了 否则 早在前面就返回了 这个题是由返回值的  
        if (root == NULL ) return;
        //2当前层的逻辑 如果左右子树都为空并且 要减去的值和当前节点的值相同 返回
        ret.push_back(root->val);
        if (root->left==NULL && root->right==NULL && sum == root->val) 
        {
            result.push_back(ret);
        }
        //否则减去当前层的值进入下一层
        sum =sum -root->val;
        //进入下一层 左
        hasPathSum(root->left, sum);
        sum =sum +root->val;//回溯
        //进入下一层 右

        sum =sum -root->val;
        hasPathSum(root->right, sum);
        sum =sum +root->val;
        ret.pop_back();
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        hasPathSum(root,targetSum);
        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> ret;
    vector<vector<int>> result;

  void hasPathSum(TreeNode* root, int sum) 
    {
        //1终止条件 如果当前节点为空 就直接返回false 
        //为什么不用判断 sum 因为走到底的下一个 还没有返回说明肯定不成立了 否则 早在前面就返回了 这个题是由返回值的  
        if (root == NULL ) return;
        //2当前层的逻辑 如果左右子树都为空并且 要减去的值和当前节点的值相同 返回

        sum =sum -root->val;
        ret.push_back(root->val);
        if (root->left==NULL && root->right==NULL && sum == 0)  result.push_back(ret);  
        hasPathSum(root->left, sum);
        hasPathSum(root->right, sum);
        sum =sum +root->val;
        ret.pop_back();
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        hasPathSum(root,targetSum);
        return result;
    }


};
  • 第二遍复习 一个地方写错 需要注意
    • 当我们找到符合条件 不能直接return 上一层 当前层的 sum和ret没有回溯 所以要么直接把return去掉(继续走完下面的回溯) 要么这边加上回溯
    • 第二个就是在终止条件前面可以做一些处理 看注释
    if(sum == 0 && root->right==NULL && root->left==NULL)
        {
            result.push_back(ret);    
            //return;  
        } 
/**
 * 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> ret;
    vector<vector<int>> result;
    void dfs(TreeNode* root,int& sum)
    {
        //递归终止条件
        if(root == NULL) return;
        //放在终止条件前是因为 如果符合再添加很奇怪 没必要固定
        ret.push_back(root->val);
        sum =sum-root->val;
        //递归终止符合条件
        if(sum == 0 && root->right==NULL && root->left==NULL)
        {
            result.push_back(ret);    
            //return;  
        } 

        dfs(root->left,sum);
        dfs(root->right,sum);
        sum = sum+root->val;
        ret.pop_back();
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) 
    {
        dfs(root,targetSum);
        return result;
    }
};

36 二叉搜索树与双向链表(中等)

[leetcode刷题]剑指offer汇总_第46张图片
[leetcode刷题]剑指offer汇总_第47张图片

  • 其实这个很容易想到用中序 ,然后需要这种前后挂钩需要保存前面一个值 所以有了变量pre这个不断变化的变量(之前有一个题需要不断前后比较也是这么做的)
  • 这个pre从代码看最后都会 pre =root 所以 最后退出一定指向最后一个节点
  • 最需要考虑的是:我们要保存一下头结点才能构成一个环 所以才有if else 如果pre为空 说明此时第一次处理 把root设为头节点
class Solution {
public:
    Node* pre = nullptr;// 这个是不断后移表示前一个数
    Node* head = nullptr;//这个用来保存头结点 就用了一次

    Node* treeToDoublyList(Node* root) 
    {
        if (root== NULL) return root;
        dfs(root);
        //最后退出 pre表示的是最后一个节点
        head->left = pre; //构成一个循环
        pre->right = head;
        return head;
    }
    void dfs(Node* root)
    {
        //递归终止条件
        if (root==NULL) return;// 递归边界: 叶子结点返回
        //中序遍历 进入左子树
        dfs(root->left);  //左子树
        //当前节点的逻辑处理
        if(pre == NULL) //表示进入第一个节点了 pre还没进行任何修改
        {
            head = root; //1 保存一下头节点
            pre = root;//2 前移pre那个数 或者理解成移到已经处理的最后一个节点
        }
        else//不是第一个点 就要两边挂钩
        {
            pre->right = root;
            root->left =pre; 
            pre = root;//理解成移到已经处理的最后一个节点
        }
        dfs(root->right); //右子树
    }
};

如果没有循环这个要求,就不用if这个判断了。

  • 第二遍复习
    • pre最后会保存最后一个存在的值,其他没什么

55 二叉树的深度(简单)

  • 做过…比较简单 后序
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;
    }
};

55 平衡二叉树(简单)

  • 这个我是根据最大深度的技巧 唯一不同我把遇到深度差大于1的 把结果存放在成员变量中 没有作为返回值返回。
  • 题解有的和我的方法一样

[leetcode刷题]剑指offer汇总_第48张图片

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool result = true;
    int dfs(TreeNode* root)
    {
        if(root == NULL) 
        {
            return 0;
        }
        if(result == false)
        {
            return 0;//提前返回
        }
        int res1 = dfs(root->left);
        int res2 = dfs(root->right);
        if(res1 - res2>1 ||res2 - res1>1)
        {
            result =false;
        }
        
        return max(res1 ,res2)+1;
         
    }
    bool isBalanced(TreeNode* root) {
        dfs(root);
        return result;


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

54 二叉搜索树的倒数第k个节点(简单)

[leetcode刷题]剑指offer汇总_第49张图片

  • 第一个想法就是中序遍历== 中序遍历的倒数第k个节点,有一个想法就是用栈存放最后倒数第几个取出。主要一开始不知道元素的个数 要不 顺序的就知道了
  • 傻了 左中右是递增 右中左就是递减 不就可以了 还是要多想想 忘记了还可以这样子
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public: 
    int res;
    void dfs(TreeNode* root ,int &k) //传引用 这里需要保证所有dfs函数共用一个k 
    {
        if(!root) return;
        dfs(root->right,k); //右
        k--;//假如 k=1 遇到第一个节点-- =0 合理
        if(k == 0) res = root->val; //保存结果
        dfs(root->left,k); //左
    }
    int kthLargest(TreeNode* root, int k) {
        dfs(root,k);
        return res;
    }

};


重新写了一遍 出现了错误 ,传入的k值必须是引用(或者放入成员变量),否则从左子树返回中序节点 k又回溯了

  • 第二遍复习 递归传参 注意传入是不是引用

101 对称的二叉树(简单)(重)

[leetcode刷题]剑指offer汇总_第50张图片

  • 第一个反映就是层次遍历 然后每一层判断是不是回文子串 reverse 然后看看是否相同 可能时间会有点长 但是其他估计还好吧
  • 题解 解](https://leetcode-cn.com/problems/symmetric-tree/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-er-ch-hnjo/)
  • 本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。为啥我觉的先序也可以呢 奇怪
  • 正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中 但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。

补充 终止条件有多个 而且可以存在true 和 false 我平时喜欢把true 归为当前层的逻辑

  • 注意终止条件是 if else 注意使用情况
  • 可以看做同时在两个树上进行操作 一个点的移动无法完成比对
    [leetcode刷题]剑指offer汇总_第51张图片
    [leetcode刷题]剑指offer汇总_第52张图片
class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        // 1终止条件首先排除空节点的情况
        if (left == NULL && right != NULL) return false;//1一边为空
        else if (left != NULL && right == NULL) return false;//2一边为空
        else if (left == NULL && right == NULL) return true;//3两边为空
        // 4(我觉的这边也可以看做先序的逻辑)
        else if (left->val != right->val) return false;

        // 此时就是:左右节点都不为空,且数值相同的情况
        // 此时才做递归,做下一层的判断
        bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
        bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
        bool isSame = outside && inside;                    // 左子树:中、 右子树:中 (逻辑处理)
        return isSame;//这个表示的是内侧 和 外侧同时成立

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }
};




class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        else if (left->val != right->val) return false;
        else return compare(left->left, right->right) && compare(left->right, right->left);

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }
};


  • 第二遍复习
    • 有印象的题目
    • 有个地方写错了 不是遇到正确的立刻返回 而是遇到错误的立刻立刻返回 所以我们标记的是错误的
class Solution {
public:
    int flat = 0;
    void compare(TreeNode* left, TreeNode* right) 
    {
        //终止条件
        if(flat == 1 ) return;
        if (left == NULL && right != NULL) 
        {
            flat=1;
            return;
        }
        else if (left != NULL && right == NULL) 
        {
            flat=1;
            return;
        }
        else if (left == NULL && right == NULL) 
        {
            return;
        }
        else if (left->val != right->val) 
        {
            flat=1;
            return;
        }

        //其余的就是两边相等的情况
        compare(left->left,right->right);
        compare(left->right,right->left);
        
       

    }
    bool isSymmetric(TreeNode* root) 
    {
        if (root == NULL) return true;
        compare(root->left, root->right);
        return !flat;
    }
};

59 按之字形顺序打印二叉树(中等)

  • 这个题如果在层次遍历进行改进 方法确实比较简单 前面说明过这个题

37 序列化二叉树(困难) (待补充)

回溯法

12 矩阵中的路径(中等)(特)

[leetcode刷题]剑指offer汇总_第53张图片

  • 第一眼看就能想到回溯 其实这个题和回溯好像 感觉不同点在于岛屿就是深度搜索 这个题还需要回溯
  • 注意board需要进行回溯 但是 k 和 i 和 j 传入的是形参 下一层修改的不影响上一层 所以不需要回溯
  • 三种回溯 切记 1for 2 if if剪枝重复结构 3 本题这种 写在一起
  • 第二个代码把k换成引用 超过了时间限制 不知道是不是 + 1 浪费时间了
  • 终止条件本来遇到走过的也要退出的 但是我们设置为 空 所以在判断不想等的时候也会退出
class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        rows = board.size();
        cols = board[0].size();
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                if(dfs(board, word, i, j, 0)) return true;
            }
        }
        return false;
    }
private:
    int rows, cols;
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k) {
        if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
        if(k == word.size() - 1) return true;
        board[i][j] = '\0';
        bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 
                      dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j] = word[k];
        return res;
    }
};


class Solution {
public:
    int rows, cols;

      bool dfs(vector<vector<char>>& board, string& word, int i, int j, int& k) 
      {
        if(i > rows || i < 0 || j > cols || j < 0 ) return false;
        if (board[i][j] != word[k]) return false;
        if(k == word.size() - 1) return true;
        board[i][j] = '\0';
        k=k+1;
        bool res1 = dfs(board, word, i + 1, j, k); 
        bool res2 = dfs(board, word, i - 1, j, k);  
        bool res3 = dfs(board, word, i, j + 1, k);
        bool res4 = dfs(board, word, i , j - 1, k);
        k=k-1;
        board[i][j] = word[k];//回溯
        return res1 || res2|| res3 || res4;
    }
    bool exist(vector<vector<char>>& board, string word) {
        rows = board.size()-1;
        cols = board[0].size()-1;
        int k =0;
        for(int i = 0; i <= rows; i++) {
            for(int j = 0; j <= cols; j++) {
                if(dfs(board, word, i, j, k)) return true;
            }
        }
        return false;
    }

    
  
};

需要补充说明的是这个题终止条件的几种情况1 超过了边界 2word元素和走到的元素不一样3走到已经走过了路径4走到最后一个元素了 并且 2 3种情况是合并

补充 特别重要

重写代码老是出现超时的情况 最后发现下面两个代码是有区别的,第一个代码会把四个结果都进行计算最后返回,但是第二个代码如果出现true说明结果必定为true会直接返回不会余下进行计算。(相当于找到一个符合的结果就结束)

board[i][j] = '\0';
        int res1=dfs(board,word,i+1,j,k+1);
        int res2=dfs(board,word,i-1,j,k+1);
        int res3=dfs(board,word,i,j+1,k+1);
        int res4=dfs(board,word,i,j-1,k+1);
        board[i][j]=word[k];//回溯
        return res1||res2||res3||res4;
board[i][j] = '\0';
       bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j]=word[k];//回溯
        return res;

13机器人的运动范围(中等)

[leetcode刷题]剑指offer汇总_第54张图片

  • 其实这个题有体现回溯 并不明显 我觉的更像是岛屿的题目 看能扩散的最大面积,题目说的是能走过的所有格子 并不是走过最长的一条路径 我理解错了 这个题辅助的数组网格 因为这个题本身没有网格 如果有在网格进行修改 然后判断就好了
  • 就好像 之前的题是走向北 然后发现不对 推导起点走东边类似(只是计算一个路上的) 这边是走北边+走东边的值之和 统计了所有的值,或者说选择东西南北都体现了回溯 在i 和j
class Solution {
public:
    int ijsum(int i, int j){
        if (i < 10 && j < 10) return i + j;
        int sum = 0;
        while (i){
            sum += (i % 10);
            i /= 10;
        }
        while (j){
            sum += (j % 10);
            j /= 10;
        }
        return sum;
    }
    int dfs(int i, int j, int k, int m, int n, vector<vector<bool>> &flag)
    {
        //终止条件 1 超过边界 2坐标和不能大于k退出 3重要走过重复的flat退出 
        if (i > m-1 || j > n-1 || ijsum(i, j) > k || flag[i][j]||i < 0||j<0)
            return 0;
        flag[i][j] = true;//走过的格子都设置为true 
        // 自身的位置加上右边和下边符合的
        int res1 = dfs(i, j+1, k, m, n, flag); 
        int res2 = dfs(i+1, j, k, m, n, flag);
        int res3 = dfs(i+1, j, k, m, n, flag);
        int res4 = dfs(i+1, j, k, m, n, flag);

        return 1+res1+res2+res3+res4;
    }
    int movingCount(int m, int n, int k) {
        vector<vector<bool>> flag(m, vector<bool>(n, false));
        return dfs(0, 0, k, m, n, flag);        
    }
};
class Solution {
public:
    int rows;
    int cols;
    int k_new;
    int ijsum(int i, int j)
    {
        if (i < 10 && j < 10) return i + j;
        int sum = 0;
        while (i){
            sum += (i % 10);
            i /= 10;
        }
        while (j){
            sum += (j % 10);
            j /= 10;
        }
        return sum;
    }
    int dfs(int i, int j ,vector<vector<bool>>& flag)
    {
        //终止条件 1 超过边界 2坐标和不能大于k退出 3重要走过重复的flat退出 
        if (i > rows-1 || j > cols-1 || ijsum(i, j) > k_new || flag[i][j]||i < 0||j<0)
            return 0;
        flag[i][j] = true;//走过的格子都设置为true 
        // 自身的位置加上右边和下边符合的
        int res1 = dfs(i, j+1,flag); 
        int res2 = dfs(i+1, j,flag);
        int res3 = dfs(i+1, j,flag);
        int res4 = dfs(i+1, j,flag);

        return 1+res1+res2+res3+res4;
    }
    int movingCount(int m, int n, int k) 
    {
        rows =m;
        cols =n;
        k_new =k;
        vector<vector<bool>> flag(m, vector<bool>(n, false));
        return dfs(0, 0,flag);        
    }
};

时间复杂度是0(MN)空间复杂度0(MN)

递归和循环

10斐波拉数列(简单)

[leetcode刷题]剑指offer汇总_第55张图片

  • 简单的题目 就是注意看题目有什么需求 比如说取余
class Solution {
public:
    int fib(int n) 
    {
        if(n == 0 ) return 0;
        if(n == 1) return 1;
        int n1 = 0;
        int n2 =1;
        int n3;
        for(int i=2;i<=n;i++)
        {
            n3 = (n1+n2)% 1000000007;
            n1=n2;
            n2=n3;
        }
    return n3;
    }
};



10青蛙跳台阶问题(简单)

  • 这个题做过了 就是爬楼梯 bp动态规划 重复可找到规律
  • dp[i] =dp[i-1]+dp[i-2] 初始化 dp 0 dp 1 题目又说0是1种 不是0种
class Solution {
public:
    int numWays(int n) {
    // 1 1 2 3 4
    if(n == 0) return 1;
    if(n == 1) return 1;
    int pre = 1;
    int pre2 = 1;
    int now;
    for(int i = 2;i <= n;i++)
    {
        now =(pre+pre2)%1000000007;
        pre2 =pre;
        pre = now;
        
    }
    return now;
    }

};

变态跳台阶(中等)

  • 这题在题库没找到

  • 题目描述: 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

  • 其实在草稿值上就能找到规律 f(n-1)=f(0)+f(1)+···+f(n-2). 或者说f(n)=2*f(n-1)

 for(int i=2;i<=target;i++)
           res=2*res;

矩形覆盖(简单)

  • leetcode题库没找到 大概写一下
    [leetcode刷题]剑指offer汇总_第56张图片
    [leetcode刷题]剑指offer汇总_第57张图片

数学

16数值的整数次方

[leetcode刷题]剑指offer汇总_第58张图片

  • 这个题做过了 分治 或者说后续处理 分成小分的 逐步返回

[leetcode刷题]剑指offer汇总_第59张图片

  • 报错 修改 long long N=n
class Solution {
public:
    double quick(double x, int n)
    {
        //1 终止条件
        if(n == 0)
        {
            return 1.0;
        }
        double y = quick(x ,n/2);
        if(n % 2==0)
        {
            return y*y;
        }
        else
        {
            return y*y*x;
        }


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

64求1 + 2 + 3 + 4 + …n(中等)

[leetcode刷题]剑指offer汇总_第60张图片

  • 比较容易想到递归把 dp【i】 =dp【i-1】+i 但是不让用if 头大
class Solution {
public:
    int dfs(int n)
    {
        if(n == 1 ) return 1;
        int res = dfs(n-1)+n;
        return res;//后续 结果返回给上一层

    }
    int sumNums(int n) 
    {
       return  dfs(n);
    }
};


65 不用加减乘除做加法(简单)

[leetcode刷题]剑指offer汇总_第61张图片
加减乘除


class Solution {
public:
    int add(int a, int b) {
//因为不允许用+号,所以求出异或部分和进位部分依然不能用+ 号,所以只能循环到没有进位为止        
        while(b!=0)
        {
//保存进位值,下次循环用
            int c=(unsigned int)(a&b)<<1;//C++中负数不支持左移位,因为结果是不定的
//保存不进位值,下次循环用,
            a^=b;
//如果还有进位,再循环,如果没有,则直接输出没有进位部分即可。
            b=c;   
        }
        return a;
    }
};

其他

15二进制中1个数(简单)

  • 做过了
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int count =0;
        while(n!=0)
        {
            n=n&(n-1);
            count++;
        }
        return count;
        
    }
};

40 最小的k个数(简单)(重)

  • 第一个想法就是sort排序(时间复杂度是nlogn 空间复杂度是 logn)…
  • 通过这个题学习一下优先队列的使用链接
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        priority_queue<int, vector<int>, greater<int> > pq;
        vector<int> ans;
        for( int v : arr ) pq.push(v);
        while(k--){ 
            ans.push_back( pq.top() );
            pq.pop();
        }
    }
};

作者:jasonchiu
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/jian-zhi-offer-40zui-xiao-de-kge-shu-c-j-gus3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        sort( arr.begin(), arr.end());
        vector<int> ans;
        for(int i = 0 ; i < k; ++i) ans.push_back( arr[i] );
        return ans;
    }
};

作者:jasonchiu
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/jian-zhi-offer-40zui-xiao-de-kge-shu-c-j-gus3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> vec(k, 0);
        if (k == 0) { // 排除 0 的情况
            return vec;
        }
        priority_queue<int> Q;
        for (int i = 0; i < k; ++i) {
            Q.push(arr[i]);
        }
        for (int i = k; i < (int)arr.size(); ++i) {
            if (Q.top() > arr[i]) {
                Q.pop();
                Q.push(arr[i]);
            }
        }
        for (int i = 0; i < k; ++i) {
            vec[i] = Q.top();
            Q.pop();
        }
        return vec;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

43 1~n整数中1出现的次数(困难)

  • 输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
  • 看下面解释 总而言是就是把题目转换成统计不同位数上出现1的个数 ==等价为 在不同位出现1的可能性
  • 最后一段代码修改了点

添加链接描述

case 1: cur=0
     2  3   0  4
     千位和百位可以选00 01 02....22  十位可以取到1( 形如[00|01..|22]1[0-9] 都是<2304 ) 个位可以选0-9  共有 23 * 10 中排列
     当千位和百位取23,如果十位取1 那就是形如 231[0-9] > 2304,所以当千位和百位取23,十位只能能取0,个位取0-42300 2301 2302 2303 2304
     但是2301不应该算进来,这个1是 单独  出现在个位的(而11121,111这种可以被算多次)
     即 23*10
case 2: cur=1
   2  3  1  4
   千位和百位可以选00 01 02....22  十位可以取到1 个位可以选0-9  共有 23 * 10 中排列
   当千位和百位取23,十位取1,个位可以去0-42310-23145个
   即 23 *10 + 4 +1
case 3: cur>12-9
   2  3  2  4
   千位和百位可以选00 01 02....22  十位可以取到1(形如 [00|01...|22]1[0-9] 都是<2324) 个位可以选0-9  共有 23 * 10 中排列
   当千位和百位取23,十位取1,个位可以去0-92310-231910个 (其中2311,被计算了两次,分别是从个位和十位分析得到的1次)
   即 23 *10 + 10

总结 -当cur =0
high×digit
但cur=1
high×digit+low+1
当cur >1 
(high+1)×digit
class Solution {
public:
    int countDigitOne(int n) 
    {
        //
        int high = n;//只是初始值 第一次计算要除以10
        int low = 0;//初始低位就是0 啥都没有
        int cur = 0;//当前位的数字
        long long digit =1;//1 10 100 
        int count =0;//统计的数量

        while(high!=0 ||cur!=0)//因为我们统计的是当前位的1数量
        {
            cur =high%10;// 例如123 得到cur=3
            high = high/10; //123/10 =12
            //开始判断
            if(cur == 0) count = count+high*digit;
            else if(cur == 1) count = count + high*digit+low+1;
            else count=count+(high+1)*digit;
            //更换低位 就好像字符串变成整数 区别在于这边是从低位补到高位
            low = low+cur*digit;
            digit =digit*10;

        }
        return count;

    }
};

时间复杂度是:时间复杂度log(n): 循环内的计算操作使用O(1) 时间;循环次数为数字 n 的位数,即 log10n

49丑数(中等)(特殊dp)

[leetcode刷题]剑指offer汇总_第62张图片

  • 比较难想 就是根常规的dp不一样
  • 为什么需要索引 因为我们必须考虑每个数的三种可能(准确的说用到每个数乘后的三种可能) 所以用完的才能后移 才不会漏掉默写值
  • 还有一个我写错的地方 if 判断的地方 不能写成if else 如果 10是丑书 可以是 25 52 为了避免下一次10还被用到 所以索引都应该后移
class Solution {
public:
    int nthUglyNumber(int n) {
        //下标表示第几个丑书-1
        vector<int> dp(n,0);
        int index2=0;//初始化三个索引下标
        int index3=0;
        int index5=0;
        dp[0]=1;//初始化
        //从第二个数开始
        for(int i =1;i<n;i++)
        {
            int next2=dp[index2]*2;
            int next3=dp[index3]*3;
            int next5=dp[index5]*5;
            dp[i]=min(min(next2,next3),next5);
            // if(dp[i]==next2) index2++;
            // else if(dp[i]==next3) index3++;
            // else index5++;
            if (dp[i] == next2) index2++;
            if (dp[i] == next3) index3++;
            if (dp[i] == next5) index5++;
        }
        return dp[n-1];

    }
};
  • 第二遍复习
    • 记得比较清楚
    • 写的时候 记录一下/下标 0-》第一个 n-1-》第n个 要不很容易错
    • 确定下标对应的到底是什么 再写for循环
class Solution {
public:
    int nthUglyNumber(int n) 
    {
        vector<int> dp(n);//下标 0-》第一个   n-1-》第n个
        int index2=0;
        int index3=0;
        int index5=0;
        dp[0]=1; 
        for(int i =1;i<n;i++)
        {   
            dp[i]=min(min(dp[index2]*2,dp[index3]*3),dp[index5]*5);
            if(dp[i]==dp[index2]*2) index2++;
            if(dp[i]==dp[index3]*3) index3++;
            if(dp[i]==dp[index5]*5) index5++;
        }
        return dp[n-1];

    }
};


57和为s的两个数字(简单)

[leetcode刷题]剑指offer汇总_第63张图片

  • 比较简单 用hash做就好 时间复杂度和 空间复杂度都是0(n),而且这个返回是数字 所以用set就好了
  • 当然这个是递增序列 头尾指针也可以 o(n)的复杂度
  • 第二个代码 if 不用加 i < j 当然也可以换成while同时加上i
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) 
    {
     unordered_set<int> set;
     vector<int> result;
     for(int i=0;i<nums.size();i++)
     {
         if(set.find(target-nums[i])==set.end())
         {
             set.insert(nums[i]);
         }
         else
         {
             //只要找到一对 所以 break
             result.push_back(nums[i]);
             result.push_back(target- nums[i]);
             break;
         }
     }
     return result;

    }
};
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) 
    {
    
     vector<int> result;
     int i =0;
     int j=nums.size()-1;
     while(i<j)
     {
         if(nums[i]+nums[j]==target)
         {
             result.push_back(nums[i]);
             result.push_back(nums[j]);
             break;
         }
         else if(nums[i]+nums[j] < target)
         {
             i++;
         }
         else
         {
             j--;
         }
     }
     return result;
    }
};

57 和为s的连续正数序列2(简单)(滑动窗口解决问题)

[leetcode刷题]剑指offer汇总_第64张图片

  • 需要说明一
    • r和l只有前移
    • 如果遇到符合条件的 让那个 l(i)++ 并且sum和中去掉i
    • 还有就是i>=j为退出条件 因为 当i++才会大于j。++因为太大了 但是后移 后面的数都比前面的大 前面不符合 后面更不可能符合
class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) 
    {
        //初始窗口大小为2
        //题目要求长度最少为2
        int i = 1;//滑动窗口的左边界
        int j = 2;// 滑动窗口的右边界
        int sum=3;//初始为3
        vector<vector<int>> res;
        while(i < j)//如果滑动窗口的大小=1 退出
        {
            if(sum == target) 
            {
                vector<int> ans;
                for(int k = i; k <= j; k++)
                    ans.push_back(k);
                res.push_back(ans);
                sum=sum-i;//注意这边
                i++;

            }
            else if(sum > target)
            {
                sum=sum-i;//先减去
                i++;//再前移
            }
            else
            {
                j++;//先前移
                sum =sum+j;//在加

            }
        }
    return res;
    }
};

重新写了一遍 两个错误一个是ans在main里面定义 每次用完 需要clear 2sum记得减去l

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) 
    {
        int l=1;
        int r=2;
        int sum=3;
        vector<int> ans;
        vector<vector<int>> result;
        while(l < r)
        {
            if(sum <target) 
            {
                r++;
                sum=sum+r;
            }
            else if( sum > target)
            {
                sum =sum -l;
                l++;
            }
            else
            {
                   for(int i =l;i<=r;i++)
                   {
                       ans.push_back(i);
                   } 
                   result.push_back(ans);
                   ans.clear();
                   sum= sum -l;
                   l++;
            }
        }
return result;
    }
};
  • 第二遍复习
    • 切记是找到所有 所以找到一个 sum-l 并且l++

61扑克牌中的顺子(简单)(技巧)

[leetcode刷题]剑指offer汇总_第65张图片

  • 其实这个题最难想到的是利用除去大小王max−min<5就符合顺子的要求。问题就转换成判断了
  • 还有一个就是你需要进行判重
  • 方法一:利用set判重 + 两个变量循环比较存储最小值和最大值 时间复杂度和空间复杂度都是0(n)
  • 方法二:sort进行排序 然后for循环进行判重和统计0的个数 时间复杂度nlogn +n 空间复杂度 logn sort排序需要的栈大小
class Solution {
public:
    bool isStraight(vector<int>& nums) 
    {
        unordered_set<int> set;
        int min_=14;
        int max_=0;
        for(int i =0;i<nums.size();i++)
        {
            if(nums[i]==0) continue;
            if(set.find(nums[i])!=set.end())
            {
                return false;
            }
            else
            {
                set.insert(nums[i]);
                min_=min(min_,nums[i]);
                max_=max(max_,nums[i]);
            }
        } 
        int res = max_ - min_;
        if(res < 5) return true;
        else return false;


    }
};
class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int count=0;//存储0的个数
        //这个循环作用1 统计0的个数 判断重复
        for(int i=0;i<nums.size()-1;i++)
        {
            if(nums[i]==0) 
            {
                count++;
                continue;
            }
            if(nums[i]==nums[i+1]) return false;
        }
        if(nums[nums.size()-1]-nums[count] < 5) return true;
        return false;

    }
};

62 圆圈中最后剩下的数字(简单)(特殊)

  • 比较经典的题目
class Solution {
    int f(int n, int m) {
        if (n == 1) {
            return 0;
        }
        int x = f(n - 1, m);
        return (m + x) % n;
    }
public:
    int lastRemaining(int n, int m) {
        return f(n, m);
    }
};


41数据流中的中位数(困难)(优先队列)

[leetcode刷题]剑指offer汇总_第66张图片

  • 看了题解确实没想到
  • 想想出来哪个图就不难了
  • 定义规则
    • 左边为大顶堆 右边是小顶堆
    • 大顶堆的数小于等于小顶堆
    • 大顶堆每次返回最大值 小顶堆每次返回最小值
    • 当两个对个数相同 我们放入右边 然后从右边取出最小值放入左边。这样等价与左边的个数增多 右边不变
    • 同理我们可以推测左边数量大于右边的情况 不存在左边小于右边的数量的情况
    • 取出就简单了 相等取两个堆顶 不等取左边的
class MedianFinder {
public:
    // 最大堆,存储左边一半的数据,堆顶为最大值
    priority_queue<int, vector<int>, less<int>> maxHeap;
    // 最小堆, 存储右边一半的数据,堆顶为最小值
    priority_queue<int, vector<int>, greater<int>> minHeap;
    /** initialize your data structure here. */
    MedianFinder() {
    }

    // 维持堆数据平衡,并保证左边堆的最大值小于或等于右边堆的最小值
    void addNum(int num) {
        /*
         * 当两堆的数据个数相等时候,左边堆添加元素。
         * 采用的方法不是直接将数据插入左边堆,而是将数据先插入右边堆,算法调整后
         * 将堆顶的数据插入到左边堆,这样保证左边堆插入的元素始终是右边堆的最小值。
         * 同理左边数据多,往右边堆添加数据的时候,先将数据放入左边堆,选出最大值放到右边堆中。
         */
        if (maxHeap.size() == minHeap.size()) {
            minHeap.push(num);
            int top = minHeap.top();
            minHeap.pop();
            maxHeap.push(top);
        } else {
            maxHeap.push(num);
            int top = maxHeap.top();
            maxHeap.pop();
            minHeap.push(top);
        }
    }
    
    double findMedian() {
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.top()+minHeap.top())*1.0/2;
        } else {
            return maxHeap.top()*1.0;
        }
    }
};

没什么问题 写了一遍

59滑动窗口的最大值 (困难)(单调队列)

  • 之前做过
  • 构建单调队列 底层实现deque因为具体实现需要头尾都能删除。

[leetcode刷题]剑指offer汇总_第67张图片

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;
    }
};
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();
        }
};
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for(int i =0 ; i<k-1 ; i++)
        {
            que.push(nums[i]);
        }
        //记得把第一个加入进去 老是忘记
        //result.push_back(que.back());
        for(int i =k-1;i<nums.size();i++)
        {
            //这两个顺序无所谓吧
            que.push(nums[i]);
           
            result.push_back(que.back());
            que.pop(nums[i-k+1]);
        }        
        return result;
    }
};

第一遍复习 重新写了一遍换了一种写法 就是第一个加入k=3 第一个for循环放入2个,第二个for循环每次放入一个 取出一个 删除一个 唯一的不同在于 que.pop(nums[i-k+1])que.pop(nums[i-k]) 比较难发现
我们这边 时间复杂度是0(n)空间复杂度是0(k)
和题解不同的是 我把新的元素放在开头 旧的元素在尾部 队列是单调递增 每次取尾部的数据为当前窗口的最大值。

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