剑指 Offer(第 2 版)完整题解笔记 & C++代码实现(LeetCode版)

简介: 剑指 Offer(第 2 版)完整题解笔记 & C++代码实现(LeetCode版)

@[toc]

原书目录

在这里插入图片描述

剑指 Offer 03. 数组中重复的数字
剑指 Offer 04. 二维数组中的查找
剑指 Offer 05. 替换空格
剑指 Offer 06. 从尾到头打印链表
剑指 Offer 07. 重建二叉树
剑指 Offer 09. 用两个栈实现队列
剑指 Offer 10- I. 斐波那契数列
剑指 Offer 10- II. 青蛙跳台阶问题
剑指 Offer 11. 旋转数组的最小数字
剑指 Offer 12. 矩阵中的路径
剑指 Offer 13. 机器人的运动范围
剑指 Offer 14- I. 剪绳子
剑指 Offer 14- II. 剪绳子 II
剑指 Offer 15. 二进制中1的个数
剑指 Offer 16. 数值的整数次方
剑指 Offer 17. 打印从1到最大的n位数
剑指 Offer 18. 删除链表的节点
剑指 Offer 19. 正则表达式匹配
剑指 Offer 20. 表示数值的字符串
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
剑指 Offer 22. 链表中倒数第k个节点
剑指 Offer 24. 反转链表
剑指 Offer 25. 合并两个排序的链表
剑指 Offer 26. 树的子结构
剑指 Offer 27. 二叉树的镜像
剑指 Offer 28. 对称的二叉树
剑指 Offer 29. 顺时针打印矩阵
剑指 Offer 30. 包含min函数的栈
剑指 Offer 31. 栈的压入、弹出序列
剑指 Offer 32 - I. 从上到下打印二叉树
剑指 Offer 32 - II. 从上到下打印二叉树 II
剑指 Offer 32 - III. 从上到下打印二叉树 III
剑指 Offer 33. 二叉搜索树的后序遍历序列
剑指 Offer 34. 二叉树中和为某一值的路径
剑指 Offer 35. 复杂链表的复制
剑指 Offer 36. 二叉搜索树与双向链表
剑指 Offer 37. 序列化二叉树
剑指 Offer 38. 字符串的排列
剑指 Offer 39. 数组中出现次数超过一半的数字
剑指 Offer 40. 最小的k个数
剑指 Offer 41. 数据流中的中位数
剑指 Offer 42. 连续子数组的最大和
剑指 Offer 43. 1~n 整数中 1 出现的次数
剑指 Offer 44. 数字序列中某一位的数字
剑指 Offer 45. 把数组排成最小的数
剑指 Offer 46. 把数字翻译成字符串
剑指 Offer 47. 礼物的最大价值
剑指 Offer 48. 最长不含重复字符的子字符串
剑指 Offer 49. 丑数
剑指 Offer 50. 第一个只出现一次的字符
剑指 Offer 51. 数组中的逆序对
剑指 Offer 52. 两个链表的第一个公共节点
剑指 Offer 53 - I. 在排序数组中查找数字 I
剑指 Offer 53 - II. 0~n-1中缺失的数字
剑指 Offer 54. 二叉搜索树的第k大节点
剑指 Offer 55 - I. 二叉树的深度
剑指 Offer 55 - II. 平衡二叉树
剑指 Offer 56 - I. 数组中数字出现的次数
剑指 Offer 56 - II. 数组中数字出现的次数 II
剑指 Offer 57 - II. 和为s的连续正数序列
剑指 Offer 57. 和为s的两个数字
剑指 Offer 58 - I. 翻转单词顺序
剑指 Offer 58 - II. 左旋转字符串
剑指 Offer 59 - I. 滑动窗口的最大值
剑指 Offer 59 - II. 队列的最大值
剑指 Offer 60. n个骰子的点数
剑指 Offer 61. 扑克牌中的顺子
剑指 Offer 62. 圆圈中最后剩下的数字
剑指 Offer 63. 股票的最大利润
剑指 Offer 64. 求1+2+…+n
剑指 Offer 65. 不用加减乘除做加法
剑指 Offer 66. 构建乘积数组
剑指 Offer 67. 把字符串转换成整数
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
剑指 Offer 68 - II. 二叉树的最近公共祖先

剑指 Offer 03. 数组中重复的数字

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

限制:

2 <= n <= 100000

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        set<int>se;
        int ok = nums[0];
        for(int x : nums){
            if(se.count(x)){
                ok = x;
                break;
            }
            se.insert(x);
        }
        return ok;
    }
};

剑指 Offer 04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:

现有矩阵 matrix 如下:

[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。

给定 target = 20,返回 false。

限制:

0 <= n <= 1000

0 <= m <= 1000

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        for(const auto& row : matrix){
            auto it = lower_bound(row.begin(),row.end(),target);
            if(it!=row.end() && *it==target){
                return true;
            }
        }
        return false;
    }
};

剑指 Offer 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

限制:

0 <= s 的长度 <= 10000

class Solution {
public:
    string replaceSpace(string s) {
        string t = "";
        for(char ch : s){
            if(ch!=' ')t += ch;
            else t += "%20";
        }
        return t;
    }
};

剑指 Offer 06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

限制:

0 <= 链表长度 <= 10000

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int>res;
        ListNode* cur = head;
        while(cur){
            res.push_back(cur->val);
            cur = cur->next;
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

剑指 Offer 07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

示例 1:

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
示例 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]

限制:

0 <= 节点个数 <= 5000

/**
 * 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> index;
    TreeNode * build(vector<int>& preorder, vector<int>& inorder, int pl, int pr, int il, int ir){
        if(pl > pr)return nullptr;
        //根节点
        int prt = pl;
        int irt = index[preorder[prt]];
        //
        TreeNode* root = new TreeNode(preorder[prt]);
        int lsz = irt-il;
        root->left = build(preorder, inorder, pl+1,pl+lsz, il, irt-1);
        root->right = build(preorder, inorder, pl+lsz+1,pr, irt+1,ir);
        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 build(preorder, inorder, 0,n-1, 0, n-1);
    }
};

剑指 Offer 09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:

1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用

class CQueue {
public:
    stack<int>in, out;

    CQueue() {}
    
    void appendTail(int value) {
        in.push(value);
    }
    
    int deleteHead() {
        if(out.empty()){
            if(in.empty())return -1;
            else{
                while(!in.empty()){
                    out.push(in.top());  in.pop();
                }
            }
        }
        int x = out.top();
        out.pop();
        return x;
    }
};

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

剑指 Offer 10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1
示例 2:

输入:n = 5
输出:5

提示:

0 <= n <= 100

class Solution {
public:
    int fib(int n) {
        if(n==0)return 0;
        if(n==1)return 1;
        int a = 0, b = 1, mod = 1e9+7;
        for(int i = 2; i <= n; i++){
            int c = b;
            b = (a+b)%mod;
            a = c;
        }
        return b;
    }
};

剑指 Offer 10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2
示例 2:

输入:n = 7
输出:21
示例 3:

输入:n = 0
输出:1
提示:

0 <= n <= 100

class Solution {
public:
    int mod = 1e9+7;
    int numWays(int n) {
        if(n<=1)return 1;
        vector<int>f(n+1,0);
        f[0] = 1;  f[1] = 1;
        for(int i = 2; i <= n; i++){
            f[i] = (f[i-1]+f[i-2])%mod;
        }
        return f[n];
    }
};

剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

示例 1:

输入:numbers = [3,4,5,1,2]
输出:1
示例 2:

输入:numbers = [2,2,2,0,1]
输出:0

提示:

n == numbers.length
1 <= n <= 5000
-5000 <= numbers[i] <= 5000
numbers 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int l = 0, r = numbers.size()-1;
        while(l < r){
            int mid = l+r>>1;
            if(numbers[r]>numbers[mid]){
                r = mid;
            }else if(numbers[r]<numbers[mid]){
                l = mid+1;
            }else r--;
        }
        return numbers[l];
    }
};

剑指 Offer 12. 矩阵中的路径

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:

输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false

提示:

m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board 和 word 仅由大小写英文字母组成

class Solution {
public:
    //check(i,j,k)表示从(i,j)出发,能否搜索到单词word[k..n]
    bool check(vector<vector<char>>&board, string& word, vector<vector<int>>&vis, int i, int j, int k){
        int n = board.size(), m = board[0].size();
        if(board[i][j] != word[k])return false;
        else if(k==word.size()-1)return true;
        vis[i][j] = true;
        vector<pair<int,int> >d{{0,1},{0,-1},{1,0},{-1,0}};
        bool ok = false;
        for(auto dd : d){
            int ni = i+dd.first, nj = j+dd.second;
            if(ni>=0&&ni<n&&nj>=0&&nj<m&&!vis[ni][nj]){
                if(check(board,word,vis,ni,nj,k+1)){
                    ok = true;
                    break;
                }
            }
        }
        vis[i][j] = false;
        return ok;
    }
    bool exist(vector<vector<char>>& board, string word) {
        int n = board.size(), m = board[0].size();
        vector<vector<int>> vis(n, vector<int>(m));
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(check(board,word,vis,i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }
};

剑指 Offer 13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:

输入:m = 2, n = 3, k = 1
输出:3
示例 2:

输入:m = 3, n = 1, k = 0
输出:1
提示:

1 <= n,m <= 100
0 <= k <= 20

class Solution {
public:
    int get(int x){
        int res = 0;
        while(x){ res += x%10;  x /= 10; }
        return res;
    }
    int movingCount(int m, int n, int k) {
        if(!k)return 1;
        vector<vector<int>>vis(m,vector<int>(n,0));
        vis[0][0] = 1;
        int ans = 1;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(i==0&&j==0)continue;
                if(get(i)+get(j)>k)continue;
                if(i-1>=0)vis[i][j] |= vis[i-1][j];
                if(j-1>=0)vis[i][j] |= vis[i][j-1];
                ans += vis[i][j];
            }
        }
        return ans;
    }
};

剑指 Offer 14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]k[1]...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:

2 <= n <= 58

class Solution {
public:
    int mod = 1e9+7;
    long long pows(long long a, long long b){long long res = 1; while(b){if(b&1)res=res*a%mod;a=a*a%mod; b>>=1; } return res; }
    //结论题
    int cuttingRope(int n) {
        if(n<=3)return n-1;
        int x = n/3, y = n%3;
        if(y==0)return (int)pows(3,x);
        if(y==1)return (int)(pows(3,x-1)*4%mod);
        return (int)(pows(3,x)*2%mod);
    }
};

剑指 Offer 14- II. 剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]k[1]...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

提示:

2 <= n <= 1000

class Solution {
public:
    int mod = 1e9+7;
    long long pows(long long a, long long b){long long res = 1; while(b){if(b&1)res=res*a%mod;a=a*a%mod; b>>=1; } return res; }
    //结论题
    int cuttingRope(int n) {
        if(n<=3)return n-1;
        int x = n/3, y = n%3;
        if(y==0)return (int)pows(3,x);
        if(y==1)return (int)(pows(3,x-1)*4%mod);
        return (int)(pows(3,x)*2%mod);
    }
};

剑指 Offer 15. 二进制中1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)。

提示:

请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用 二进制补码 记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。

示例 1:

输入:n = 11 (控制台输入 00000000000000000000000000001011)
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:

输入:n = 128 (控制台输入 00000000000000000000000010000000)
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:

输入:n = 4294967293 (控制台输入 11111111111111111111111111111101,部分语言中 n = -3)
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。

提示:

输入必须是长度为 32 的 二进制串 。

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res = 0;
        while(n){
            if(n&1)res++;
            n>>=1;
        }
        return res;
    }
};

剑指 Offer 16. 数值的整数次方

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:

输入:x = 2.10000, n = 3
输出:9.26100
示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

提示:

-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= xn <= 104

class Solution {
public:
    double myPow(double x, int n) {
        if(n==0)return 1;
        if(n==1)return x;
        if(n==-1)return 1/x;
        double y = myPow(x,n/2);
        double yy = myPow(x,n%2);
        return y*y*yy;
    }
};

剑指 Offer 17. 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

示例 1:

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]

说明:

用返回一个整数列表来代替打印
n 为正整数

class Solution {
public:
    vector<int> printNumbers(int n) {
        vector<int>res;
        int ed = stoi(string(n,'9'));
        for(int i = 1; i <= ed; i++)res.push_back(i);
        return res;
    }
};

剑指 Offer 18. 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

注意:此题对比原题有改动

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

说明:

题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur){
            if(cur->val == val){
                if(pre!=NULL)pre->next = cur->next;
                else return head->next;
            }else{
                pre = cur;
            }
            cur = cur->next;
        }
        return head;
    }
};

剑指 Offer 19. 正则表达式匹配

请实现一个函数用来匹配包含'. '和''的正则表达式。模式中的字符'.'表示任意一个字符,而''表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:

输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:

输入:
s = "ab"
p = ".*"
输出: true
解释: "." 表示可匹配零个或多个('')任意字符('.')。
示例 4:

输入:
s = "aab"
p = "cab"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:

输入:
s = "mississippi"
p = "misisp*."
输出: false
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 ,无连续的 ''。

//f[i][j]表示s的前i个能否被p的前j个匹配。
//转移若s[i]==p[j]则f[i][j]=f[i-1][j-1]. 否则若p[j]=.也可以匹配,若=*则再判断p[j-1]与s[i]的关系
class Solution {
public:
    bool isMatch(string s, string p) {
        s = " "+s;  p = " "+p;                          //s,p可能为空
        int n = s.size(), m = p.size();
        bool f[n+1][m+1];
        memset(f,false,(n+1)*(m+1));
        f[0][0] = true;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(s[i-1]==p[j-1] || p[j-1]=='.'){
                    f[i][j] = f[i-1][j-1];
                }
                else if(p[j-1]=='*'){
                    if(p[j-2]!=s[i-1] && p[j-2]!='.'){//*找前一个,前一个匹配不上,再往前找
                        f[i][j] = f[i][j-2];
                    }else{  //匹配上了
                        f[i][j] = (f[i-1][j] || f[i][j-1] || f[i][j-2]);//多个字符,单个字符,没有匹配
                    }
                }
            }
        }
        return f[n][m];
    }
};

剑指 Offer 20. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

数值(按顺序)可以分成以下几个部分:

若干空格
一个 小数 或者 整数
(可选)一个 'e' 或 'E' ,后面跟着一个 整数
若干空格
小数(按顺序)可以分成以下几个部分:

(可选)一个符号字符('+' 或 '-')
下述格式之一:
至少一位数字,后面跟着一个点 '.'
至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
一个点 '.' ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:

(可选)一个符号字符('+' 或 '-')
至少一位数字
部分数值列举如下:

["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]
部分非数值列举如下:

["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]

示例 1:

输入:s = "0"
输出:true
示例 2:

输入:s = "e"
输出:false
示例 3:

输入:s = "."
输出:false
示例 4:

输入:s = " .1 "
输出:true

提示:

1 <= s.length <= 20
s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。

class Solution {
public:
    bool isNumber(string s) {
        //是否存在E, 小数点, 数字,符号,结束。
        int have_e = 0, have_p = 0, have_n = 0, have_s = 0, ed = 0;
        for(int i = 0; i < s.size(); i++){
            int isnull = have_e||have_p||have_n||have_s;
            if(ed && s[i]!=' ')return false;
            if(have_n && (s[i]=='+' || s[i]=='-'))return false;
            if(!have_n && (s[i]=='E' || s[i]=='e'))return false;
            if(have_p && !have_e && (s[i]=='+' || s[i]=='-'))return false;
            if((s[i]=='+' || s[i]=='-') && have_s)return false;
            else if((s[i]=='+' || s[i]=='-') && !have_s)have_s = 1;
            else if(s[i] == '.' && have_p)return false;
            else if(s[i] == '.' && !have_p)have_p = 1;
            else if((s[i]== 'E' || s[i]=='e') && have_e)return false;
            else if((s[i]== 'E' || s[i]=='e') && !have_e){
                have_e = 1;  have_s = 0;  have_p = 1;  have_n = 0;
                if(i+1>=s.size())return false;
            }else if(s[i]-'0'>=0 && s[i]-'0'<=9)have_n = 1;
            else if(s[i] == ' ' && !isnull)continue;
            else if(s[i] == ' ' && isnull)ed = 1;
            else return false;
        }
        if(!have_n)return false;
        return true; 
    }
};

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。

提示:

0 <= nums.length <= 50000
0 <= nums[i] <= 10000

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {   
        vector<int>res(nums.size());
        int l = 0, r = nums.size()-1;
        for(int x : nums){
            if(x%2==1)res[l] = x, l++;
            else res[r] = x, r--;
        }
        return res;
    }
};

剑指 Offer 22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

/**
 * 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* fast = head;
        ListNode* slow = head;
        while(fast!=NULL && k > 0){
            fast = fast->next;
            k--;
        }
        while(fast!=NULL && slow != NULL){
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

剑指 Offer 24. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

限制:

0 <= 节点个数 <= 5000

/**
 * 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 = NULL;
        ListNode *cur = head;
        while(cur){
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

剑指 Offer 25. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:

0 <= 链表长度 <= 1000

/**
 * 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode ans;
        ListNode* pre = &ans;
        while(l1 != nullptr && l2 != nullptr){
            if(l1->val <= l2->val){
                pre->next = l1;
                pre = pre->next;
                l1 = l1->next;
            }else{
                pre->next = l2;
                pre = pre->next;
                l2 = l2->next;
            }
        }
        if(l1 != nullptr){
            pre->next = l1;
        }
        if(l2 != nullptr){
            pre->next = l2;
        }
        return ans.next;
    }
};

剑指 Offer 26. 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

 3
/ \

4 5
/ \
1 2
给定的树 B:

4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:

0 <= 节点个数 <= 10000

/**
 * 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(A==NULL || B==NULL)return false;
        return issame(A,B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }
    //判断树B是否与以A为根的子树相同
    bool issame(TreeNode* A, TreeNode* B){
        if(B==NULL)return true; 
        if(A==NULL)return false;
        return (A->val)==(B->val) && issame(A->left,B->left) && issame(A->right, B->right);
    }
};

剑指 Offer 27. 二叉树的镜像

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

例如输入:

 4

/ \
2 7
/ \ / \
1 3 6 9
镜像输出:

 4

/ \
7 2
/ \ / \
9 6 3 1

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

限制:

0 <= 节点个数 <= 1000

/**
 * 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* mirrorTree(TreeNode* root) {
        if (root == nullptr) return nullptr;
        if (root->left == nullptr && root->right == nullptr) {
            return root;
        }

        TreeNode *left = mirrorTree(root->left);
        TreeNode *right = mirrorTree(root->right);

        root->right = left;
        root->left = right;

        return root;
    }
};

剑指 Offer 28. 对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

1

/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

1

/ \
2 2
\ \
3 3

示例 1:

输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:

输入:root = [1,2,2,null,3,null,3]
输出:false

限制:

0 <= 节点个数 <= 1000

/**
 * 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 check(TreeNode *p, TreeNode *q){//双指针
        if(p==nullptr && q==nullptr)return true;
        if(p==nullptr || q==nullptr)return false;
        return p->val==q->val && check(p->left,q->right) && check(p->right,q->left);
    }
    bool isSymmetric(TreeNode* root) {
        return check(root,root);
    }
};

剑指 Offer 29. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

限制:

0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.size() == 0 || matrix[0].size() == 0)return {};
        int n = matrix.size(), m = matrix[0].size();
        int x = 0, y = 0, tot = n*m-1;
        vector<int>res;  res.push_back(matrix[0][0]);  matrix[0][0] = -999;
        while(tot){
            while(y+1<m && matrix[x][y+1]!=-999){y++; res.push_back(matrix[x][y]); matrix[x][y] = -999; tot--;}
            while(x+1<n && matrix[x+1][y]!=-999){ x++; res.push_back(matrix[x][y]); matrix[x][y] = -999; tot--;}
            while(y-1>=0 && matrix[x][y-1]!=-999){y--; res.push_back(matrix[x][y]); matrix[x][y] = -999; tot--;}
            while(x-1>=0 && matrix[x-1][y]!=-999){x--; res.push_back(matrix[x][y]); matrix[x][y] = -999; tot--;}
        }
        return res;
    }
};

剑指 Offer 30. 包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.

提示:

各函数的调用总次数不超过 20000 次

class MinStack {
public:
    /** initialize your data structure here. */
    int mi = INT_MAX;
    stack<int>st;
    MinStack() {

    }

    //对于push,放入x和加入x前的最小值
    void push(int x) {
        st.push(mi);    //加入前的最小值
        if(x<mi)mi=x;   //加入后的最小值
        st.push(x);
    }
    
    void pop() {
        st.pop();       //去掉当前值
        mi = st.top();  //拿出加入前的最小值
        st.pop();       //去掉当前值
    }
    
    int top() {
        return st.top();
    }
    
    int min() {
        return mi;
    }
};

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

剑指 Offer 31. 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

示例 1:

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。

提示:

0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列。

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int>st;
        int cur = 0;
        for(int i = 0; i < pushed.size(); i++){
            st.push(pushed[i]);
            while(!st.empty() && st.top()==popped[cur]){
                st.pop();  cur++;
            }
        }
        return st.empty();
    }
};

剑指 Offer 32 - I. 从上到下打印二叉树

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

例如:
给定二叉树: [3,9,20,null,null,15,7],

3

/ \
9 20

/  \

15 7
返回:

[3,9,20,15,7]

提示:

节点总数 <= 1000

/**
 * 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:
    vector<int> levelOrder(TreeNode* root) {
        if(root==NULL)return {};
        vector<int>res;
        queue<TreeNode*>q;
        q.push(root);
        while(q.size()){
            TreeNode* t = q.front();  q.pop();
            if(t->left)q.push(t->left);
            if(t->right)q.push(t->right);
            res.push_back(t->val);
        }
        return res;
    }
};

剑指 Offer 32 - II. 从上到下打印二叉树 II

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

例如:
给定二叉树: [3,9,20,null,null,15,7],

3

/ \
9 20

/  \

15 7
返回其层次遍历结果:

[
[3],
[9,20],
[15,7]
]

提示:

节点总数 <= 1000

//开个队列往里面丢就是了,简称bfs
/**
 * 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>>res;
        if(!root)return res;

        queue<TreeNode*>q;
        q.push(root);
        while(q.size()){
            int len = q.size();
            res.push_back(vector<int>());
            for(int i = 1; i <= len; i++){
                auto t = q.front();  q.pop();
                res.back().push_back(t->val);
                if(t->left)q.push(t->left);
                if(t->right)q.push(t->right);
            }
        }
        return res;
    }
};

剑指 Offer 32 - III. 从上到下打印二叉树 III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

例如:
给定二叉树: [3,9,20,null,null,15,7],

3

/ \
9 20

/  \

15 7
返回其层次遍历结果:

[
[3],
[20,9],
[15,7]
]

提示:

节点总数 <= 1000

/**
 * 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:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>>ans;
        if(root==NULL)return ans;
        queue<TreeNode*>q;
        q.push(root);
        int isleft = 0;
        while(q.size()){
            int n = q.size();
            vector<int>tmp;
            for(int i = 0; i < n; i++){
                TreeNode* t = q.front();  q.pop();
                tmp.push_back(t->val);
                if(t->left)q.push(t->left);
                if(t->right)q.push(t->right);
            }
            isleft = !isleft;
            if(isleft)ans.push_back(tmp);
            else ans.push_back(vector<int>(tmp.rbegin(),tmp.rend()));
        }
        return ans;
    }
};

剑指 Offer 33. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

 5
/ \

2 6
/ \
1 3
示例 1:

输入: [1,6,3,2,5]
输出: false
示例 2:

输入: [1,3,2,6,5]
输出: true

提示:

数组长度 <= 1000

class Solution {
public:
    //二叉搜索树: 根节点大于左子树,小于右子树
    bool verifyPostorder(vector<int>& postorder) {
        return dfs(postorder, 0, postorder.size()-1);
    }
    bool dfs(vector<int>&p, int l, int r){
        if(l > r)return true;
        int rt = l;   while(p[rt]<p[r])rt++;   //左子树都小于根p[r]
        int rt2 = rt; while(p[rt2]>p[r])rt2++; //右子树都大于根p[r]
        return rt2==r && dfs(p,l,rt-1) && dfs(p, rt, r-1); //递归检查
    }
};

剑指 Offer 34. 二叉树中和为某一值的路径

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
示例 2:

输入:root = [1,2,3], targetSum = 5
输出:[]
示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

提示:

树中节点总数在范围 [0, 5000] 内
-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000

/**
 * 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>>res;
    vector<int>path;
    void dfs(TreeNode* p, int target){
        if(p==nullptr)return ;
        path.push_back(p->val);
        target -= p->val;
        if(p->left==nullptr && p->right==nullptr && target==0)res.push_back(path);
        dfs(p->left, target);
        dfs(p->right, target);
        path.pop_back();
    }
    vector<vector<int>> pathSum(TreeNode* root, int target) {
        dfs(root,target);
        return res;
    }
};

剑指 Offer 35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head==NULL)return NULL;
        //1. 复制节点,构建拼接链表
        Node* tmp;
        for(Node* cur = head; cur != NULL; cur = tmp->next){
            tmp = new Node(cur->val);
            tmp->next = cur->next;
            tmp->random = cur->random;
            cur->next = tmp;
        }
        //2. 构建新节点的random
        for(Node* cur = head; cur != NULL; cur = cur->next->next){
            if(cur->random == NULL)continue;
            cur->next->random = cur->random->next;
        }
        //3. 拆分链表
        Node *res = head->next;
        Node *pre = head, *cur = head->next;
        for( ; cur->next != NULL; pre = pre->next, cur = cur->next){
            pre->next = pre->next->next;
            cur->next = cur->next->next;
        }
        pre->next = NULL;
        return res;
    }
};

剑指 Offer 36. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

为了让您更好地理解问题,以下面的二叉搜索树为例:

我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
public:
    Node* pre, *head;
    Node* treeToDoublyList(Node* root) {
        if(root==NULL)return NULL;
        dfs(root);
        head->left = pre;  //头尾相连
        pre->right = head;
        return head;
    }
    //中序遍历树
    void dfs(Node* cur){
        if(cur==NULL)return ;
        dfs(cur->left);
        if(pre==NULL)head = cur; //第1个头结点, 存到head
        else pre->right = cur;   //上一个节点->right=当前
        cur->left = pre;         //当前->left = 上一个节点
        pre = cur;               //更新上一个节点
        dfs(cur->right);
    }
};

剑指 Offer 37. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

提示:输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

示例:

输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    void enc(TreeNode* u, string &res){
        if(u==nullptr)res += "None,";
        else{
            res += to_string(u->val)+",";
            enc(u->left,res);
            enc(u->right,res);
        }
    }
    string serialize(TreeNode* root) {
        string res;
        enc(root,res);
        return res;
    }

    // Decodes your encoded data to tree.
    TreeNode* dec(list<string>& li){
        if(li.front()=="None"){
            li.erase(li.begin());
            return nullptr;
        }
        TreeNode* u = new TreeNode(stoi(li.front()));
        li.erase(li.begin());
        u->left = dec(li);
        u->right = dec(li);
        return u;
    }
    TreeNode* deserialize(string data) {
        list<string>li;
        string tmp="";
        for(char ch : data){
            if(ch==','){
                li.push_back(tmp);
                tmp.clear();
            }else{
                tmp.push_back(ch);
            }
        }
        if(!tmp.empty())li.push_back(tmp);
        return dec(li);
    }
};

// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));

剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]

限制:

1 <= s 的长度 <= 8

class Solution {
public:
    vector<string> permutation(string s) {
        sort(s.begin(), s.end());
        vector<string>res;
        do res.push_back(s);
        while(next_permutation(s.begin(),s.end()));
        return res;
    }
};

剑指 Offer 39. 数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

限制:

1 <= 数组长度 <= 50000

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int x = -1, cc = 0;
        for(int num : nums){
            if(num==x)cc++;
            else cc--;
            if(cc < 0){
                x = num;
                cc = 1;
            }
        }
        return x;
    }
};

剑指 Offer 40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:

0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        sort(arr.begin(),arr.end());
        return vector<int>(arr.begin(),arr.begin()+k);
    }
};

剑指 Offer 41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制:

最多会对 addNum、findMedian 进行 50000 次调用。

class MedianFinder {
public:
    MedianFinder() {}
    priority_queue<int, vector<int>, less<int> >qmi;   //小于中位数,大根堆
    priority_queue<int, vector<int>, greater<int> >qmx;//大于中位数,小根堆
    
    void addNum(int num) {
        if(qmi.empty() || num<=qmi.top()){
            qmi.push(num);
            if(qmi.size()>qmx.size()+1)qmx.push(qmi.top()), qmi.pop();
        }else{
            qmx.push(num);
            if(qmx.size()>qmi.size())qmi.push(qmx.top()), qmx.pop();
        }
    }
    
    double findMedian() {
        if(qmi.size() > qmx.size())return qmi.top(); //qmi多一个
        return (qmi.top()+qmx.top())/2.0;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

剑指 Offer 42. 连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

提示:

1 <= arr.length <= 10^5
-100 <= arr[i] <= 100

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //以i结尾的连续子数组的最大和,f[i] = max{a[i], f[i-1]+a[i]}, res取max{f[i]}即可
        int f = 0, res = nums[0];
        for(int x : nums){
            f = max(f+x, x);
            res = max(res, f);
        }
        return res;
    }
};

剑指 Offer 43. 1~n 整数中 1 出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

示例 1:

输入:n = 12
输出:5
示例 2:

输入:n = 13
输出:6

限制:

1 <= n < 2^31

class Solution {
public:
    int countDigitOne(int n) {
        //数位dp
        long long res = 0, dp = 0;       //到当前位为止能取到的数的个数
        for(long long i = 1; i <= n; i*=10){
            int a = n%i, b = (n%(i*10))/i;
            if(b>1)res += b*dp+i;  //当前位>1
            if(b==1)res += dp+a+1; //当前位=1
            dp = dp*10+i;
        }
        return (int)res;
    }
};

剑指 Offer 44. 数字序列中某一位的数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

请写一个函数,求任意第n位对应的数字。

示例 1:

输入:n = 3
输出:3
示例 2:

输入:n = 11
输出:0

限制:

0 <= n < 2^31

class Solution {
public:
    int findNthDigit(int n) {
        if(n<10)return n;
        vector<vector<int>>mp{
            {0,0}, {0,9}, {10,189}, {190,2889}, {2890,38889}, {38890,488889}, {488890,5888889}, 
            {5888890,68888889}, {68888890,788888889}, {788888890,2147483647}
        };
        int i = 1;
        for(i = 2; i <= 9; i++){
            if(n >= mp[i][0] && n <= mp[i][1]){
                n -= mp[i][0];  break;
            }
        }
        int mi = pow(10,i-1);
        return to_string(mi+(n/i))[n%i]-'0';
    }
};

剑指 Offer 45. 把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

示例 1:

输入: [10,2]
输出: "102"
示例 2:

输入: [3,30,34,5,9]
输出: "3033459"

提示:

0 < nums.length <= 100
说明:

输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0

class Solution {
public:
    static bool cmp(string a, string b){ return a+b<b+a; }
    string minNumber(vector<int>& nums) {
        vector<string>str;
        for(int x : nums)str.push_back(to_string(x));
        sort(str.begin(),str.end(),cmp);
        string res = "";
        for(string x : str)res += x;
        return res; 
    }
};

剑指 Offer 46. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"

提示:

0 <= num < 231

class Solution {
public:
    int translateNum(int num) {
        if(num<=9)return 1;
        int r = num%100;
        if(r<=9 || r>=26)return translateNum(num/10);  
        else return translateNum(num/10)+translateNum(num/100); //[10-25]
    }
};

剑指 Offer 47. 礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

提示:

0 < grid.length <= 200
0 < grid[0].length <= 200

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<int>dp(m+1);
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                dp[j] = max(dp[j], dp[j-1])+grid[i-1][j-1];
            }
        }
        return dp[m];
    }
};

剑指 Offer 48. 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 1:

输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

s.length <= 40000

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int l = 0, r = 0, res = 0;
        unordered_set<char>se;
        se.insert(s[0]);
        while(r < s.size()){
            res = max(res, (int)se.size());
            r++;
            if(se.count(s[r])){
                while(s[l]!=s[r]){ se.erase(s[l]); l++; }
                se.erase(s[l]);  l++; 
            }
            se.insert(s[r]);
        }
        return res;
    }
};

剑指 Offer 49. 丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:

1 是丑数。
n 不超过1690。

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int>vc = {2,3,5};
        unordered_set<long>se;  se.insert(1L);
        priority_queue<long, vector<long>, greater<long>>q;  q.push(1L);
        for(int i = 0; i < n-1; i++){
            long t = q.top();  q.pop();
            for(int x : vc){
                long nx = x*t;
                if(!se.count(nx)){se.insert(nx); q.push(nx); }
            }
        }
        return (int)q.top();
    }
};

剑指 Offer 50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例 1:

输入:s = "abaccdeff"
输出:'b'
示例 2:

输入:s = ""
输出:' '

限制:

0 <= s 的长度 <= 50000

class Solution {
public:
    char firstUniqChar(string s) {
        vector<int>z(27);
        for(char ch : s)z[ch-'a']++;
        for(char ch : s){
            if(z[ch-'a']==1)return ch;
        }
        return ' ';
    }
};

剑指 Offer 51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000

class BIT {
    private: int n; vector<int> tree;
    public: BIT(int _n): n(_n), tree(_n + 1) {}
    int query(int x){int res=0; while(x){res+=tree[x];x-=x&(-x);} return res;}
    void add(int x, int v){ while(x<=n){tree[x]+=v; x+=x&(-x);} }
};
class Solution {
public:
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        vector<int>tmp = nums;
        //离散化
        sort(tmp.begin(), tmp.end());
        for(int& num : nums){
            num = lower_bound(tmp.begin(),tmp.end(), num)-tmp.begin()+1;
        }
        //树状数组
        BIT bit(n);
        int ans = 0;
        for (int i = n-1; i >= 0; i--) {
            ans += bit.query(nums[i]-1);
            bit.add(nums[i], 1);
        }
        return ans;
    }
};

剑指 Offer 52. 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。

如下面的两个链表:

在节点 c1 开始相交。

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:

如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

/**
 * 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) {
        unordered_set<ListNode *> se;
        ListNode* tmp = headA;
        while(tmp != nullptr){
            se.insert(tmp);
            tmp = tmp->next;
        }
        tmp = headB;
        while(tmp != nullptr){
            if(se.count(tmp))return tmp;
            tmp = tmp->next;
        }
        return nullptr;
    }
};

剑指 Offer 53 - I. 在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: 0

提示:

0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.size()==0)return 0;
        int p = upper_bound(nums.begin(),nums.end(), target)-nums.begin()-1;
        int cnt = 0;
        while(p>=0 && nums[p]==target)cnt++, p--;
        return cnt;
    }
};

剑指 Offer 53 - II. 0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

示例 1:

输入: [0,1,3]
输出: 2
示例 2:

输入: [0,1,2,3,4,5,6,7,9]
输出: 8

限制:

1 <= 数组长度 <= 10000

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int l = 0, r = nums.size()-1;
        while(l <= r){
            int mid = l+r>>1;
            if(nums[mid]==mid)l = mid+1;
            else r = mid-1;
        }
        return l;
    }
};

剑指 Offer 54. 二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第 k 大的节点的值。

示例 1:

输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3

   5
  / \
 3   6
/ \

2 4
/
1
输出: 4

限制:

1 ≤ 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:
    //二叉搜索树:根小于左子树, 大于右子树。 中序遍历输出第k个即可
    vector<int>vc;
    void dfs(TreeNode *p){
        if(p==NULL)return ;
        dfs(p->right);
        vc.push_back(p->val);
        dfs(p->left);
    }
    int kthLargest(TreeNode* root, int k) {
        dfs(root);
        return vc[k-1];
    }
};

剑指 Offer 55 - I. 二叉树的深度

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

例如:

给定二叉树 [3,9,20,null,null,15,7],

3

/ \
9 20

/  \

15 7
返回它的最大深度 3 。

提示:

节点总数 <= 10000

/**
 * 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 maxDepth(TreeNode* root) {
        if(root==nullptr)return 0;
        return max(maxDepth(root->left), maxDepth(root->right))+1;
    }
};

剑指 Offer 55 - II. 平衡二叉树

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

示例 1:

给定二叉树 [3,9,20,null,null,15,7]

3

/ \
9 20

/  \

15 7
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

   1
  / \
 2   2
/ \

3 3
/ \
4 4
返回 false 。

限制:

0 <= 树的结点个数 <= 10000

/**
 * 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 dfs(TreeNode *p){  //返回左右子树的最大深度
        if(p==NULL)return 0;
        int l = dfs(p->left), r = dfs(p->right); 
        if(l==-1 || r==-1 || abs(l-r)>1)return -1;
        return max(l,r)+1;
    }
    bool isBalanced(TreeNode* root) {
        return dfs(root) != -1;
    }
};

剑指 Offer 56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

限制:

2 <= nums.length <= 10000

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int x = 0;
        for(int y : nums)x ^= y;
        x = x&(-x);
        int a = 0, b = 0;
        for(int y : nums){
            if(x&y)a ^= y;
            else b ^= y;
        }
        return vector<int>{a,b};
    }
};

剑指 Offer 56 - II. 数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入:nums = [3,4,3,3]
输出:4
示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

限制:

1 <= nums.length <= 10000
1 <= nums[i] < 2^31

class Solution {
public:
    int mp[32];
    int singleNumber(vector<int>& nums) {
        for(int x : nums){
            for(int j = 30; j >= 0; j--){
                if((x>>j)&1)mp[j]++;
            }
        }
        int res = 0;
        for(int i = 30; i >= 0 ; i--){
            if(mp[i]%3!=0){
                res |= (1<<i);
            }
        }
        return res;
    }
};

剑指 Offer 57 - II. 和为s的连续正数序列

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

限制:

1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_set<int>se;
        for(int x : nums){
            if(se.count(target-x)){
                return {x, target-x};
            }
            se.insert(x);
        }
        return {};
    }
};

剑指 Offer 57. 和为s的两个数字

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

限制:

1 <= target <= 10^5

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>>vec;
        vector<int> res;
        for (int l = 1, r = 2; l < r;){
            int sum = (l + r) * (r - l + 1) / 2;
            if (sum == target) {
                res.clear();
                for (int i = l; i <= r; ++i) {
                    res.push_back(i);
                }
                vec.push_back(res);
                l++;
            } else if (sum < target) {
                r++;
            } else {
                l++;
            }
        }
        return vec;
    }
};

剑指 Offer 58 - I. 翻转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。

示例 1:

输入: "the sky is blue"
输出: "blue is sky the"
示例 2:

输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:

输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

说明:

无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

class Solution {
public:
    string reverseWords(string s) {
        stringstream ss(s);
        string res = "", t;
        while(ss>>t){
            res = t+" "+res;
        }
        return res.substr(0,res.size()-1);
    }
};

剑指 Offer 58 - II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:

输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

限制:

1 <= k < s.length <= 10000

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        return s.substr(n,s.size()-n)+s.substr(0,n);
    }
};

剑指 Offer 59 - I. 滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

提示:

你可以假设 k 总是有效的,在输入数组 不为空 的情况下,1 ≤ k ≤ nums.length。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        priority_queue<pair<int,int> >q;
        for(int i = 0; i < k; i++){
            q.push(make_pair(nums[i],i));
        }
        vector<int>ans = {q.top().first};
        for(int i = k; i < n; i++){
            q.push(make_pair(nums[i],i));
            while(q.top().second<=i-k)q.pop();
            ans.push_back(q.top().first);
        }
        return ans;
    }
};

剑指 Offer 59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:

输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]

限制:

1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5

class MaxQueue {
public:
    MaxQueue() {
        
    }
    queue<int>q;
    deque<int>d; //单调递减队列

    int max_value() {
        if(d.empty())return -1;
        return d.front();
    }
    
    void push_back(int value) {
        q.push(value);
        while(!d.empty() && d.back()<value)d.pop_back();
        d.push_back(value);
    }
    
    int pop_front() {
        if(q.empty())return -1;
        int x = q.front();  q.pop();
        if(x==d.front())d.pop_front();
        return x;
    }
};

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue* obj = new MaxQueue();
 * int param_1 = obj->max_value();
 * obj->push_back(value);
 * int param_3 = obj->pop_front();
 */

剑指 Offer 60. n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

限制:

1 <= n <= 11

class Solution {
public:
    vector<double> dicesProbability(int n) {
        vector<double>f(6, 1.0/6);
        for(int i = 2; i <= n; i++){        //前i个骰子和为j的概率
            vector<double>tmp(5*i+1, 0);    //和的范围为[n,6n], 共5n+1
            for(int j = 0; j < f.size(); j++){
                for(int k = 0; k < 6; k++){ //本次摇出k
                    tmp[j+k] += f[j]/6.0;
                }
            }
            f = tmp;
        }
        return f;
    }
};

剑指 Offer 61. 扑克牌中的顺子

从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

示例 1:

输入: [1,2,3,4,5]
输出: True

示例 2:

输入: [0,0,1,2,5]
输出: True

限制:

数组长度为 5

数组的数取值为 [0, 13] .

class Solution {
public:
    //排序后差的和<5(不算万能牌0,不接受对子)
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int sum = 0;
        for(int i = 1; i < 5; i++){
            if(nums[i-1]==0)continue;
            if(nums[i]==nums[i-1])return false;
            sum += nums[i]-nums[i-1];
        }
        return sum < 5;
    }
};

剑指 Offer 62. 圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

示例 1:

输入: n = 5, m = 3
输出: 3
示例 2:

输入: n = 10, m = 17
输出: 2

限制:

1 <= n <= 10^5
1 <= m <= 10^6

class Solution {
public:
    int lastRemaining(int n, int m) {
        int ans = 0;
        for(int i = 2; i <= n; i++){
            ans = (ans+m)%i;
        }
        return ans;
    }
};

剑指 Offer 63. 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。

 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

限制:

0 <= 数组长度 <= 10^5

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int mi = 1e9, mx = 0;
        for(int x : prices){
            mx = max(mx, x-mi);
            mi = min(mi, x);
        }
        return mx;
    }
};

剑指 Offer 64. 求1+2+…+n

求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

示例 1:

输入: n = 3
输出: 6
示例 2:

输入: n = 9
输出: 45

限制:

1 <= n <= 10000

class Solution {
public:
    int sumNums(int n) {
        bool a[n][n+1];
        return sizeof(a)>>1;
    }
};

剑指 Offer 65. 不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

示例:

输入: a = 1, b = 1
输出: 2

提示:

a, b 均可能是负数或 0
结果不会溢出 32 位整数

class Solution {
public:
    //两个简易的位运算结论
    int add(int a, int b) {
        return a+b;
        return (a&b)+(a|b);
        return (a^b)+(a&b)+(a&b);
    }
};

剑指 Offer 66. 构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

示例:

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

提示:

所有元素乘积之和不会溢出 32 位整数
a.length <= 100000

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        int sum = 1, havz = 0;
        for(int i = 0; i < a.size(); i++) if(a[i]!=0)sum *= a[i];else havz++;
        for(int i = 0; i < a.size(); i++) {
            if(havz){
                if(a[i]==0 && havz==1)a[i] = sum;
                else a[i] = 0;
            }else{
                a[i] = sum/a[i];
            }
        }
        return a;
    }
};

剑指 Offer 67. 把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 1:

输入: "42"
输出: 42
示例 2:

输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。

 我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:

输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。

 因此无法执行有效的转换。

示例 5:

输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。

 因此返回 INT_MIN (−231) 。
class Solution {
public:
    int strToInt(string str) {
        string s = str;
        long long f = 1, num = 0;
        int i = 0;
        while(s[i]==' ')i++;
        if(s[i]=='-')f = -1, i++;
        else if(s[i]=='+')i++;
        for( ; s[i]>='0'&&s[i]<='9'; i++){
            int x = s[i]-'0';
            num = num*10+x*f;
            if(num>=INT_MAX)return INT_MAX;
            if(num<=INT_MIN)return INT_MIN;
        }
        return (int)num;
    }
};

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。

/**
 * 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* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==NULL)return NULL;
        //都在 左树或右树
        if(p->val<root->val && q->val<root->val)return lowestCommonAncestor(root->left, p,q);
        if(p->val>root->val && q->val>root->val)return lowestCommonAncestor(root->right,p,q);
        return root;  //在两侧,root就是LCA
    }
};

剑指 Offer 68 - II. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

/**
 * 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* ans;
    int dfs(TreeNode *root, TreeNode *p, TreeNode *q){
        if(root==NULL)return NULL;
        int l = dfs(root->left, p, q);
        int r = dfs(root->right, p, q);
        if((l&&r) || ((root->val==p->val||root->val==q->val) && (l||r))){
            ans = root;
        }
        return l || r || root->val==p->val || root->val==q->val;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, p, q);
        return ans;
    }
};
目录
相关文章
|
6月前
|
算法 C++
算法笔记:递归(c++实现)
算法笔记:递归(c++实现)
|
6月前
|
编译器 C++
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
|
4月前
|
C++ 容器
【C/C++笔记】迭代器
【C/C++笔记】迭代器
29 1
|
4月前
|
存储 安全 程序员
【C/C++笔记】迭代器范围
【C/C++笔记】迭代器范围
73 0
|
5月前
|
C++ Windows
FFmpeg开发笔记(三十九)给Visual Studio的C++工程集成FFmpeg
在Windows上使用Visual Studio 2022进行FFmpeg和SDL2集成开发,首先安装FFmpeg至E:\msys64\usr\local\ffmpeg,然后新建C++控制台项目。在项目属性中,添加FFmpeg和SDL2的头文件及库文件目录。接着配置链接器的附加依赖项,包括多个FFmpeg及SDL2的lib文件。在代码中引入FFmpeg的`av_log`函数输出"Hello World",编译并运行,若看到"Hello World",即表示集成成功。详细步骤可参考《FFmpeg开发实战:从零基础到短视频上线》。
221 0
FFmpeg开发笔记(三十九)给Visual Studio的C++工程集成FFmpeg
|
6月前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-2
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
6月前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-1
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
6月前
|
机器学习/深度学习 程序员
老程序员分享:leetcode笔记201.BitwiseANDofNumbersRange
老程序员分享:leetcode笔记201.BitwiseANDofNumbersRange
29 0
|
6月前
|
编译器 C++
《Effective C++ 改善程序与设计的55个具体做法》 第二章 构造/析构/赋值运算 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第二章 构造/析构/赋值运算 笔记
|
6月前
|
存储 小程序 程序员
Essential C++ 第1章 C++编程基础 (笔记)
Essential C++ 第1章 C++编程基础 (笔记)