剑指offer (From Leetcode) 汇总

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

找出数组中重复的数字。

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

示例 1:

输入:

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

限制:

2 <= n <= 100000

思路一:

// 本题可采用set这一数据结构,遍历数组并加入set中,如果某个数在set中已经存在了,那么便可以判断为重复,
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        set<int> s;
        for(int i=0; i<nums.size(); i++){
            if(s.count(nums[i]) == 0){
                s.insert(nums[i]);
            }
            else{
                return nums[i];
            }
        }
        return nums[nums.size()-1];
    }
};

思路二:

// 我们仔细看题目,长度为n的数组的所有数字都在0~n-1范围内,所以数组中如果没有重复的数字,那么数组排序之后数字i将出现在下标为i的位置
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        int res = -1;
        for(int i=0; i<nums.size(); i++){
            while(nums[i] != i){
                if(nums[i] == nums[nums[i]]){
                    return nums[i];
                }
                int t = nums[i];
                nums[i] = nums[t];
                nums[t] = t;
            }
        }
        return res;
    }
};

剑指 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

//解题思路如下:
// 因为这一二维数组的特点是每一行每一列都是按照递增顺序排列,
// 因此,我们将要查找的数字t与查找范围内的右上角数字p进行比较。
// 若 t < p,说明t一定不在p所在的列,将p所在的列从查找范围中剔除
// 若 t > p,说明t一定不在p所在的行,将p所在的行从查找范围中剔除
// 若 t == p, 返回True
class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(matrix.size() == 0){
            return false;
        }
        int n = matrix.size();
        int m = matrix[0].size();
        int r = 0, c = m-1;
        while(r < n && c >= 0){
            int p = matrix[r][c];
            if(target < p){
                c--;
            }
            else if(target > p){
                r++;
            }
            else{
                return true;
            }
        }
        return false;
    }
};

剑指 Offer 05. 替换空格

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

示例 1:

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

限制:

0 <= s 的长度 <= 10000

// 思路比较自然,用一个空字符串,在遍历字符串过程中,遇到空格就添加%20,否则就添加原字符。
class Solution {
public:
    string replaceSpace(string s) {
        string res = "";
        for(int i=0; i<s.length(); i++){
            if(s[i] == ' '){
                res += "%20";
            }
            else{
                res += s[i];
            }
        }
        return res;
    }
};

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

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

示例 1:

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

限制:

0 <= 链表长度 <= 10000

 // 简单的思路:
 // 从头至尾遍历链表,将每个节点的值存到栈中
 // 最后将栈取出存到vector即可
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> v;
        stack<int> s;
        while(head!=NULL){
            s.push(head->val);
            head = head->next;
        }
        while(!s.empty()){
            v.push_back(s.top());
            s.pop();
        }
        return v;
    }
};

剑指 Offer 07. 重建二叉树

【经典题】

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

在这里插入图片描述

限制:

0 <= 节点个数 <= 5000

// 这类题(已知前序和中序 或者 已知后序和中序 来重建二叉树)做过好多好多遍了,但是有时还是会记不得怎样递归重建,所以一定要巩固巩固巩固!
// 思路就是自己先演算一下,把原理理清楚。我们知道前序遍历中第一个一定是根节点,比如例子中是3,那么放到中序可以知道,根节点3的左子树是[9],右子树是[15, 20, 7]
// [9]在前序遍历中单独为一个根节点,[15,20,7]在前序遍历中第一个是20,所以20是这个子树的根节点,再放到中序可知,根节点20的左子树是[15],右子树是[7],它们各自独立成根节点。
// 递归的边界是?

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        // 边界条件
        if(preorder.size() == 0 || inorder.size() == 0){
            return NULL;
        }
        // 先定义初始前序和中序遍历子树左右范围
        int lp = 0, rp = preorder.size() - 1;
        int li = 0, ri = preorder.size() - 1;
        return construct(preorder, inorder, lp, rp, li, ri);
    }
    TreeNode* construct(vector<int>& preorder, vector<int>& inorder, int p_l, int p_r, int i_l, int i_r){
        // 边界条件
        if(p_l > p_r || i_l > i_r){
            return NULL;
        }
        int root = preorder[p_l];
        TreeNode *r = new TreeNode(root);
        // 找到子树根节点在中序遍历的位置k
        int k = i_l;
        while(k <= i_r && inorder[k] != root){
            k++;
        }
        r->left = construct(preorder, inorder, p_l+1, p_l+k-i_l, i_l, k-1);
        r->right = construct(preorder, inorder, p_l+k-i_l+1, p_r, k+1, i_r);
        return r;
    }   
};

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

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

F ( 0 ) = 0 , F ( 1 ) = 1 F(0) = 0, F(1) = 1 F(0)=0,F(1)=1
F ( N ) = F ( N − 1 ) + F ( N − 2 ) , 其 中 N > 1. F(N) = F(N - 1) + F(N - 2), 其中 N > 1. F(N)=F(N1)+F(N2),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) {
        const int r = 1e9+7;
        vector<int> F;
        F.push_back(0);
        F.push_back(1);
        for(int i=2; i<=n; i++){
            F.push_back((F[i-1] + F[i-2]) % r);
        }
        return F[n];
    }
};


剑指 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 numWays(int n) {
        const int r = 1e9+7;
        int res[3] = {1, 1, 2};
        if(n <= 2){
            return res[n];
        }
        int a = 1, b = 2;
        int k;
        for(int i=3; i<=n; i++){
            k = (a + b) % r;
            a = b;
            b = k;
        }
        return k;
    }
};

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

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

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

示例 2:

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

// // 1.暴力解法:
// // 顺序判断最简单,但是时间复杂度O(n)
// class Solution {
// public:
//     int minArray(vector<int>& numbers) {
//         int res = numbers[0];
//         for(int i=1; i<numbers.size(); i++){
//             if(numbers[i] < numbers[i-1]){
//                 res = numbers[i];
//                 break;
//             }
//         }
//         return res;
//     }
// };
// 2.二分搜索:
// 因为它是部分有序的,那么我们可以尝试使用二分法
class Solution {
public:
    int minArray(vector<int>& numbers) {
        int l=0, r=numbers.size()-1;
        int res = numbers[l];
        while(l < r){
            int mid = (l + r) / 2;
            // 位于前面的递增子数组
            if(numbers[mid] > numbers[r]){
                l = mid+1;
            }
            // 位于后面的递增子数组
            else if(numbers[mid] < numbers[r]){
                r = mid;
            }
            else{
                r -= 1;
            }
        }
        return numbers[l];
    }
};


剑指 Offer 12. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

示例 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

提示:

1 <= board.length <= 200
1 <= board[i].length <= 200

class Solution {
public:
    int dirs[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
    bool exist(vector<vector<char>>& board, string word) {
        int nr = board.size(), nc = board[0].size();
        for(int i=0; i<nr; i++){
            for(int j=0; j<nc; j++){
                if(board[i][j] == word[0]){
                    bool visited[205][205] = {false};
                    // for(int i=0; i<3; i++){
                    //     for(int j=0; j<3; j++){
                    //         cout<<visited[i][j];
                    //     }
                    // }
                    visited[i][j] = true;
                    if(dfs(i, j, board, word, visited, 1)){
                        return true;
                    }
                }
            }
        }
        return false;
    }
    bool dfs(int x, int y, vector<vector<char>> board, string word, bool visited[][205], int cur){
        
        bool suc = false;
        if(cur == word.length()){
            // cout<<1;
            return true;
        }
        for(int i=0; i<4; i++){
            int dx = x + dirs[i][0];
            int dy = y + dirs[i][1];
            if(dx < 0 || dy < 0 || dx >= board.size() || dy>=board[0].size() || visited[dx][dy] == true){
                continue;
            }
            if(board[dx][dy]==word[cur]){
                // cout<<board[dx][dy]<<word[cur];
                visited[dx][dy] = true;
                suc = dfs(dx, dy, board, word, visited, cur+1);
                visited[dx][dy] = false;
                if(suc) return true;
            }
        }
        return suc;
    }
};


剑指 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 dir[4][2] = {{0, 1},{0, -1},{1, 0},{-1, 0}};
    int movingCount(int m, int n, int k) {
        int res=0;
        bool visited[105][105] = {false};
        dfs(m, n, 0, 0, res, k, visited);
        return res;
    }
    void dfs(int m, int n, int x, int y, int &res, int k, bool visited[][105]){
        res++;
        visited[x][y] = true;
        for(int i=0; i<4; i++){
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];
            if(dx < 0 || dx >= m || dy < 0 || dy >= n || visited[dx][dy] ||  (dx % 10 + dx / 10 + dy % 10 + dy / 10) > k) continue;
            dfs(m, n, dx, dy, res, k, visited);
        }
    }
};

剑指 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 cuttingRope(int n) {
//         if(n < 2) return 0; // m > 1
//         if(n == 2) return 1; // 1*1
//         if(n == 3) return 2; // 1*2

//         vector<int> f(n+1); 
//         f[1] = 1;
//         f[2] = 2;
//         f[3] = 3;
//         int max = 0;
//         for(int i=4; i<=n; i++){
//             max = 0;
//             for(int j=1; j<=i/2; j++){
//                 int t = f[j] * f[i-j];
//                 if(max < t){
//                     max = t;
//                 }
//                 f[i] = max;
//             }
//         }
//         return f[n];
//     }
// };

// 方法二:贪心
// 当n>=5时,2(n-2)>n并且3(n-3)>n,即n>=5时,就可以把它剪成长度为3或2的绳子段
// 而当n>=5时,3(n-3) >= 2(n-2),所以应尽量剪长度为3的绳子段
class Solution {
public:
    int cuttingRope(int n) {
        if(n < 2) return 0; // m > 1
        if(n == 2) return 1; // 1*1
        if(n == 3) return 2; // 1*2

        int k = n / 3;
        if(n - k * 3 == 1){
            k -= 1;
        }
        int k2 = (n - k * 3) / 2;
        return (int)(pow(3, k)) * (int)(pow(2, k2));
    }
};

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

请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。

示例 2:

输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。

示例 3:

输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 ‘1’。

提示:

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

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

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

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入: 2.00000, 10
输出: 1024.00000

示例 2:

输入: 2.10000, 3
输出: 9.26100

示例 3:

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

说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。

// 递归
// x^n = x^(n/2) * x^(n/2) (n为偶数)
// x^n = x^((n-1)/2) * x^((n-1)/2) * x (n为奇数)

// 需考虑的边界情况:
// 1. n == 0
// 2. x == 0 and n != 0
// 3. x != 0 and n < 0

class Solution {
public:
    double myPow(double x, int n) {
        if(n == 0) return 1;
        if(x == 0 && n != 0) return 0; 
        if(n == -1) return 1/x;
        if(n&1) return myPow(x*x, n>>1)*x;
        else return myPow(x*x, n>>1);
    }
};

剑指 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 maxm = pow(10, n) - 1;
        for(int i=1; i<=maxm; i++){
            res.push_back(i);
        }
        return res;
    }
};

// // 剑指offer上的思路是要考虑大数情况,所以本题在leetcode上过于简单
// // 大数打印很容易想到使用字符串
// // 书上采用递归的方式进行全排列
// void Print1ToMaxOfNDigits(int n){
//     if(n <= 0) return;
//     char *number = new char[n + 1];
//     number[n] = '\0';
//     for(int i=0; i<10; i++){
//         number[0] = i + '0';
//         Print1ToMaxOfNDigitsRecursively(number, n, 0);
//     }
//     delete[] number;
// }

// void Print1ToMaxOfNDigitsRecursively(char* number, int length, int index){
//     if(index == length-1){
//         PrintNumber(number);
//         return;
//     }
//     for(int i=0; i<10; i++){
//         number[index+1] = i + '0';
//         Print1ToMaxOfNDigitsRecursively(number, length, index+1);
//     }
// }

剑指 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.

/**
 * 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* newHead = new ListNode(1);
        newHead->next = head;
        ListNode* p = newHead;
        while(p != nullptr && p->next != nullptr){
            if(p->next->val == val){
                p->next = p->next->next;
                break;
            }
            p = p->next;
        }
        return newHead->next;
    }
};

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

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

示例 1:

输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。

解题思路如下:
https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/solution/zhu-xing-xiang-xi-jiang-jie-you-qian-ru-shen-by-je/

class Solution {
public:
    bool isMatch(string s, string p) {
        int n = s.length();
        int m = p.length();
        bool f[100][100] = {false};
        for(int i=0; i<=n; i++){
            for(int j=0; j<=m; j++){
                if(j == 0){
                    f[i][j] = (i == 0)?true:false;
                }else{
                    if(p[j-1] != '*'){
                        if(i > 0 && (s[i-1] == p[j-1] || p[j-1] == '.')){
                            f[i][j] = f[i-1][j-1];
                        }
                    }
                    else{
                        if(j >= 2){
                            if(f[i][j] == true || f[i][j-2] == true)
                                f[i][j] = true;
                        }
                        if(i >= 1 && j >= 2 && (s[i-1] == p[j-2] || p[j-2] == '.')){
                            if(f[i][j] == true || f[i-1][j] == true)
                                f[i][j] = true;
                        }

                    }
                }
            }
        }
        return f[n][m];
    }
};

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

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。

// 按照常规思路来判断
// 表示数值的字符串遵循 A[.[B]][e|EC] 或 .B[e|EC] 这两个模式
// 1. A或C可能是以'+'或'-'为开头的0~9的数位串,B前面没正负号。
// 2. A部分不是必须的,所以有以上两种模式。
 
class Solution {
public:
    bool isNumber(string s) {
        if(s.empty()) return false;
        int index = 0;
        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;
        return numeric && index == s.size();
    }

    // 整数的格式可以用[+|-]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 before = index;
        while(index != s.size() && s[index] >= '0' && s[index] <= '9')
            index ++;
        return index > before;
    }
};

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

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

示例:

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

提示:

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

//使用两个队列即可
class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        queue<int> p, o;
        for(int i=0; i<nums.size(); i++){
            if(nums[i] % 2 == 1){
                p.push(nums[i]);
            }
            else o.push(nums[i]);
        }
        vector<int> res;
        while(!p.empty()){
            res.push_back(p.front());
            p.pop();
        }
        while(!o.empty()){
            res.push_back(o.front());
            o.pop();
        }
        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* p = head, *q = head;
        while(k && p!=nullptr){
            p = p->next;
            k--;
        }
        while(p != nullptr){
            p = p->next;
            q = q->next;
        }
        return q;
    }
};

剑指 Offer 24. 反转链表

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

示例:

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

有两种方式,第一种的迭代方式比较简单:

// 使用三个指针分别指向当前遍历到的节点、它的前一个节点
/**
 * 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* reverseHead = nullptr;
        ListNode* p = nullptr;
        ListNode* q = head;
        while(q!= nullptr){
            ListNode *u = q->next;
            if(u == nullptr){
                reverseHead = q;
            }
            q->next = p;
            p = q;
            q = u;
        }
        return reverseHead;
    }
};

其次,可以通过递归方式进行反转链表

// 递归
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;
        ListNode *p = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return p;
    }
};

剑指 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(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *newHead = new ListNode(0);
        ListNode *p = newHead;
        while(l1 != nullptr && l2 != nullptr){
            if(l1->val < l2->val){
                p->next = l1;
                p = p->next;
                l1 = l1->next;
            }
            else{
                p->next = l2;
                p = p->next;
                l2 = l2->next;
            }
        }
        if(l1 != nullptr){
            p->next = l1;
        }
        if(l2 != nullptr){
            p->next = l2;
        }
        return newHead->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 == nullptr || B == nullptr){
            return false;
        }
        return dfs(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }

    bool dfs(TreeNode* A, TreeNode* B){
        if(B == nullptr) return true;
        if(A == nullptr) return false;
        return A->val == B->val && dfs(A->left, B->left) && dfs(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) {
        TreeNode* mT = root;
        dfs(mT);
        return root;
    }
    void dfs(TreeNode* root){
        if(root == nullptr) return;
        if(root->left == nullptr && root->right == nullptr) return;
        TreeNode *t = root->left;
        root->left = root->right;
        root->right = t;
        mirrorTree(root->left);
        mirrorTree(root->right);
    }
};

剑指 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

/**
 * 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:

    // 中序遍历是否对称(此思路是错误的,输入[1,2,2,2,null,2],但我这个结果是ture,实际是false)
    // bool isSymmetric(TreeNode* root) {
    //     vector<int> inOrder;
    //     dfs(root, inOrder);
    //     for(int i=0; i<inOrder.size()/2; i++){
    //         if(inOrder[i] != inOrder[inOrder.size()-i-1]) return false;
    //     }
    //     return true;
    // }
    // void dfs(TreeNode* root, vector<int> &inOrder){
    //     if(root == nullptr) return;
    //     dfs(root->left, inOrder);
    //     inOrder.push_back(root->val);
    //     dfs(root->right, inOrder);
    // }
    //
    

    // 先序遍历和对称的先序遍历是否一致,递归解决
    bool isSymmetric(TreeNode* root) {
        return dfs(root, root);
    }
    bool dfs(TreeNode* root1, TreeNode* root2){
        if(root1 == nullptr && root2 == nullptr) return true;
        if(root1 == nullptr || root2 == nullptr) return false;
        if(root1->val != root2->val) return false;
        return dfs(root1->left, root2->right) && dfs(root1->right, root2->left);
    }
};

剑指 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 {};
        }
        vector<int> res;
        int r_n = matrix.size(), c_n = matrix[0].size();
        // 先找一圈的初始位置
        int start = 0;
        while(r_n > (start * 2) && c_n > (start * 2)){
            // 转圈
            int end_r = r_n - start - 1;
            int end_c = c_n - start - 1;
            for(int i=start; i<=end_c; i++){
                res.push_back(matrix[start][i]); // 向右走
            }
            
            for(int i=start+1; i<=end_r; i++){
                res.push_back(matrix[i][end_c]); // 向下走
            }
            
            if(start < end_r){
                for(int i=end_c-1; i>=start; i--){
                    res.push_back(matrix[end_r][i]); // 向左走
                }
            }
            if(start < end_c){
                for(int i=end_r-1; i>=start+1; i--){
                    res.push_back(matrix[i][start]); // 向上走
                }
            }
            start++;
        }
        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.

// 思路:使用一个单调的辅助栈来存储当前最小的元素
class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> s1, s2;
    MinStack() {
        
    }
    
    void push(int x) {
        s1.push(x);
        if(s2.empty() || s2.top() >= x){
            s2.push(x);
        }
        
    }
    
    void pop() {    
        if(s1.top() == s2.top()){
            s2.pop();
        }
        s1.pop();
    }
    
    int top() {
        return s1.top();
    }
    
    int min() {
        // int res = s[0];
        // for(int i=1; i<s.size(); i++){
        //     if(res > s[i]){
        //         res = s[i];
        //     }
        // }
        // return res;
        return s2.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();
 */

剑指 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 之前弹出。

// 模拟
// 使用一个栈,顺序插入第一个序列的数
// 若栈非空,则判断栈顶元素是否等于第二个序列的当前元素
// 如果等于,则取出栈顶元素,第二个序列下标++。
// 最后判断栈是否为空,如果为空,说明全部弹出且符合
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> s;
        int idx = 0;
        for (const auto &x : pushed) {
            s.push(x);
            while (!s.empty() && s.top() == popped[idx]) {
                s.pop();
                idx++;
            }
        }
        return s.empty();
    }
};

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

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

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

3
/ \
9 20
/ \
15 7

返回:

[3,9,20,15,7]

// 模板题,使用队列实现层序遍历
class Solution {
public:
    vector<int> levelOrder(TreeNode* root) {
        if(root == nullptr) return {};
        queue<TreeNode*> treeQueue;
        vector<int> res;
        treeQueue.push(root);
        while(!treeQueue.empty()){
            TreeNode *u = treeQueue.front();
            treeQueue.pop();
            res.push_back(u->val);
            if(u->left != nullptr){
                treeQueue.push(u->left);
            }
            if(u->right != nullptr){
                treeQueue.push(u->right);
            }
        }
        return res;
    }
};

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

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

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

3
/ \
9 20
/ \
15 7

返回其层次遍历结果:

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


// 本题在之前的基础上,需要判断当前层是否遍历完,如果遍历完就插入
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root == nullptr) return {};
        queue<TreeNode*> treeQueue;
        vector<vector<int>> res;
        vector<int> subRes;
        treeQueue.push(root);
        int curLeftCnt = 1, nextCnt = 0;
        while(!treeQueue.empty()){
            TreeNode *u = treeQueue.front();
            treeQueue.pop();
            curLeftCnt--;
            subRes.push_back(u->val);
            if(u->left != nullptr){
                treeQueue.push(u->left);
                nextCnt++;
            }
            if(u->right != nullptr){
                treeQueue.push(u->right);
                nextCnt++;
            }

            if(curLeftCnt == 0){
                res.push_back(subRes);
                subRes.clear();
                curLeftCnt = nextCnt;
                nextCnt=0;
            }
        }
        return res;
    }
};

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

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

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

5
/ \
2 6
/ \
1 3

示例 1:

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

示例 2:

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

// 根节点为后序遍历最后一个结点。比如例子中[1,3,2,6,5]的根节点为5,
// 而二叉搜索树的特点是,左子树所有结点都比根节点小, 右子树所有结点都比根节点大
// 因此[1,3,2]为5的左子树,[6]为5的右子树,之后子树也同样按照这样去构造
// 而在[1,6,3,2,5]例子中,可以发现[1]为5的左子树,但[6,3,2]中存在比5小的树,所以不是它的后序遍历
class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        if(postorder.size()==0) return true;
        return verify(postorder, postorder.size());
    }
    bool verify(vector<int>& postorder, int n){
        
        int root = postorder[n-1];
        int i;
        for(i=0; i<n-1; i++){
            if(postorder[i] > root){
                break;
            }
        }
        int j = i;
        for(; j<n-1; j++){
            if(postorder[j] < root){
                return false;
            }
        }
        bool left = true;
        if(i > 0){
            left = verify(postorder, i);
        }
        bool right = true;
        if(i < n-1){
            vector<int> rightPO(postorder.begin()+i, postorder.end());
            right = verify(rightPO, n-i-1);
        }
        return left && right;
    }
};

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

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

示例:
给定如下二叉树,以及目标和 sum = 22,
在这里插入图片描述
返回:

[
[5,4,11,2],
[5,8,4,5]
]

// 回溯与树相结合经典题

class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        if(root == nullptr) return {};
        vector<vector<int>> res;
        vector<int> v;
        dfs(res, v, root, 0, sum);
        return res;
    }

    void dfs(vector<vector<int>> &res, vector<int> &v, TreeNode *root, int cur, int sum){
        cur += root->val;
        v.push_back(root->val);
        if(root->left == nullptr && root->right == nullptr && cur == sum){
            res.push_back(v);
            return;
        }
        if(root->left != nullptr){
           
            dfs(res, v, root->left, cur, sum);
            v.pop_back();
        }
        if(root->right != nullptr){

            dfs(res, v, root->right, cur, sum);
            v.pop_back();
        }
    }
};

剑指 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 = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

  1. -10000 <= Node.val <= 10000
  2. Node.random 为空(null)或指向链表中的节点。
  3. 节点数目不超过 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) {
        cloneNodes(head);
        connectRandom(head);
        return reconnectNodes(head);
    }

    // 复制原始链表的任意节点N并创建新节点N',再将N'链接到N的后面。
    void cloneNodes(Node* head){
        Node *p = head;
        while(p != nullptr){
            Node *q = new Node(p->val);
            q->next = p->next;
            q->random = nullptr;
            p->next = q;
            p = q->next;
        }
    }

    // 复制N'的random指针 
    void connectRandom(Node* head){
        Node *p = head;
        while(p!=nullptr){
            Node *q = p->next;
            if(p->random != nullptr){
                q->random = p->random->next;
            }
            p = q->next;
        }
    }

    // 将第二步得到的链表拆分成两个链表
    Node* reconnectNodes(Node *head){
        Node *p = head;
        Node *q = nullptr;
        Node *u = nullptr;

        if(p != nullptr){
            q = u = p->next;
            p->next = q->next;
            p = p->next;
        }
        while(p != nullptr){
            u->next = p->next;
            u = u->next;
            p->next = u->next;
            p = p->next;
        }
        return q;
    }
};

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

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
在这里插入图片描述
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

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

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

// 二叉搜索树的中序遍历序列就是其排序的结果,因此中序遍历改变指针
class Solution {
public:
    Node *head, *p;
    Node* treeToDoublyList(Node* root){
        if(root == nullptr) return nullptr;
        dfs(root);
        head->left = p;
        p->right = head;
        return head;
    }
    void dfs(Node* root){
        if(root==nullptr) return;
        dfs(root->left);
        if(p != nullptr) p->right = root;
        else head = root;
        root->left = p;
        p = root;
        dfs(root->right);
    }
};

剑指 Offer 38. 字符串的排列

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

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

示例:

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

// 求整个字符串的排列,可以看成:
// 1.将第一个字符与后面所有的字符交换(前提是要交换的两个字符不一样)
// 2.固定第一个字符,对之后所有的字符按照第一步方法进行交换

class Solution {
public:
    vector<string> res;
    vector<string> permutation(string s) {
        dfs(s, 0);
        return res;
    }

    void dfs(string &s, int cur){
        if(cur == s.length()){
            res.push_back(s);
        }
        for(int i=cur; i<s.length(); i++){
            int ok = 1;
            for(int j=cur; j<i; j++){
                if(s[j] == s[i]){
                    ok = 0;
                }
            }
            if(ok){
                swap(s[cur], s[i]);
                dfs(s, cur+1);
                swap(s[cur], s[i]);
            }
        }
    }
};

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

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

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

示例 1:

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

限制:

1 <= 数组长度 <= 50000

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        // 排序,取中间数
        // sort(nums.begin(), nums.end());
        // return nums[nums.size()/2];
        
        // 使用hash表
        // unordered_map<int, int> hash;
        // int res = 0, len = nums.size();
        // for(int i=0; i<len; i++){
        //     hash[nums[i]]++;
        //     if(hash[nums[i]] > len/2){
        //         res = nums[i];
        //         break;
        //     }
        // }
        // return res;

        // 数组中有一个数字出现的次数超过数组长度的一半,即它出现的次数比其他所有数字出现的次数的和还要多
        int res = nums[0];
        int cnt = 1;
        for(int i=1; i<nums.size(); i++){
            if(cnt == 0){
                res = nums[i];
                cnt = 1;
            }
            else if(nums[i] == res){
                cnt++;
            }
            else{
                cnt--;
            }
        }
        return res;
    }
};  

剑指 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
    vector<int> getLeastNumbers(vector<int>& arr, int k){
        if(k==0) return {};

        // 直接排序,然后取前k小的数字
        // vector<int> res;
        // sort(arr.begin(), arr.end());
        // for(int i=0; i<k; i++){
        //     res.push_back(arr[i]);
        // }
        // return res;

        // 使用最大堆 O(nlogk)
        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;

    }

剑指 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]

// class MedianFinder {
// public:
//     /** initialize your data structure here. */
//     vector<double> stream;
//     MedianFinder() {

//     }
// 每次排序会超时
//     void addNum(int num) {
//         stream.push_back(num);
//     }
    
//     double findMedian() {
//         sort(stream.begin(), stream.end());
//         if(stream.size() & 1){ // 奇数
//             return stream[stream.size()/2];
//         }
//         else{
//             return (stream[stream.size()/2] + stream[stream.size()/2 - 1])/2;;
//         }
//     }
// };

class MedianFinder {
public:
    /** initialize your data structure here. */
    priority_queue<int,vector<int>,less<int>> leftQue; //大顶堆
    priority_queue<int,vector<int>,greater<int>> rightQue; //小顶堆
    MedianFinder() {
        
    }
    void addNum(int num) {
         
         if(leftQue.size() == rightQue.size())
         {
            rightQue.push(num);
            leftQue.push(rightQue.top());
            rightQue.pop();   
         }
       
         else if(leftQue.size() == rightQue.size()+1)
         {
            leftQue.push(num);
            rightQue.push(leftQue.top());
            leftQue.pop(); 
         }
    }
    
    double findMedian() {
        double res = 0.0;
        if(leftQue.size() == rightQue.size())
        {
            res = ((double)leftQue.top() + (double)rightQue.top())/2;
        }
        else res = leftQue.top();
        return res;
    }
};

/**
 * 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。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int curSum = 0, maxm = 0x80000000;
        for(int i=0; i<nums.size(); i++){
            if(curSum <= 0){
                curSum = nums[i]; // 如果当前求和是小于等于0的,那么再加后面的数要么不变要么还变小了,所以舍弃之前的结果,从之后的数作为求和的开始
            }
            else{
                curSum += nums[i];
            }
            if(curSum > maxm) maxm = curSum; // 更新和的最大值
        }
        return maxm;
    }
     
};

剑指 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

// 需要通过找规律来分析。
// 假设我们对5014这个数字求解。
// (1)个位上1出现的个数:记高位为high=501,当前位为cur=4。
// 那么高位从0~500变化的过程中,每一个变化中1只出现1次,即(高位1)这样的数字;
// 高位是501时,因为当前位是4,所以1只能出现一次,即5011。
// 所以总共出现的次数为high*1+1=502。
// (2)十位1出现的个数:记高位high=50,当前位为cur=1,低位为low=4。
// 那么高位从0~ 49变化的过程中,每一个变化中1出现10次,即(高位10)~(高位19)这样的数字;
// 高位为50的时候,因为当前位是1,所以我们要看低位来决定出现的次数,因为低位为4,所以此时出现5次,即5010~5014这样的数字。
// 所以总共出现的次数为high*10+4=504。
// (3)百位1出现的个数:记高位high=5,当前位cur=0,低位为low=14。
// 那么高位从0~ 4的过程中,每一个变化1出现100次,即(高位100)~(高位199)这样的数字;
// 高位为5的时候,因为当前位为0,所以不存在出现1的可能性。
// 所以总共出现的次数为high*100+0=500。
// (4)千位1出现的次数:记高位high=0,当前位cur=5,低位low=014。
// 那么因为没有高位所以直接看当前位,因为当前位为5,所以1出现的次数为1000,即1000~1999这样的数字。
// 所以总共出现的次数为high*1000+1000=1000。
// 综上,最终的结果将每个位置出现1的次数累加即可。

// 结论
// 我们假设高位为high,当前位为cur,低位为low,i代表着需要统计的位置数(1对应个位,10对应十位,100对应百位),则对每一位的个数count有:
// cur=0,count = high*i;
// cur=1,count=high*i+low+1;
// cur>1,count=high*i+i
// 最终累加所有位置上的个数即最终答案。

class Solution {
public:
    int countDigitOne(int n) {
       int count = 0;
       long i = 1;//指向遍历的位数,如i=1即个位,i=10即十位,...,因为n可以有31位,所以10^31用long存储
       while(n/i!=0){
           //n/i控制遍历的次数,将所有的位数都遍历完毕
            long high = n/(10*i);//将当前位之前的所有高位都存在high中
            long cur = (n/i)%10;//将当前位记录在cur中,即我们每次都需要统计当前位上1出现的次数
            long low = n-(n/i)*i;
            if(cur == 0){
                count += high * i;
            } else if(cur == 1){
                count += high * i + low + 1;
            } else {
                count += high * i + i;
            }
            i = i * 10;//准备遍历下一位
       }
       return count;
    }
};


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

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

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

示例 1:

输入:n = 3
输出:3

示例 2:

输入:n = 11
输出:0

// 找规律,前10位是0~9,所以n>10便可以跳过这些数字,比如当n=1001,那么我们从之后的序列中找第991(1001-10)位数字
// 10~99是两位数字,其一共占180(99-10+1)*2位数字,991>180所以还是继续跳过,从之后的序列中找第811(991-180)位数字
// 100~999是三位数字,一共占2700(999-100+1)*2位数字,811<2700所以在这里,811//3=270---1,所以是370(100+270)的中间一位即7
class Solution {
public:
    int findNthDigit(int n) {
        int cur = 1;
        long long sumBits;
        if(cur == 1){
            sumBits = 10;
        }
        while(n > sumBits){
            cur++;
            n = n - sumBits ;
            sumBits = (pow(10, cur) - pow(10, cur-1)) * cur;
        }
        if(cur == 1 && n<10)
            return n;
        int res;
        int a = pow(10, cur-1) + n / cur;
        int b = cur - (n % cur);
        // cout<<n<<" "<<cur;
        while(b){
            res = a%10;
            a = a/10;
            b--;
        }
        return res;
    }
}; 

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

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

示例 1:

输入: [10,2]
输出: “102”

示例 2:

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


// 自定义排序
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(), [](string& x, string& y){ return x + y < y + x; });
        for(int i = 0; i < strs.size(); i++)
            res.append(strs[i]);
        return res;
    }
};

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

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

示例 1:

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

// 动态规划
// 令dp[i]表示从第i位数字开始的不同翻译的数目,
// 如果第i位和第i+1位两位数字拼接起来的数字在10~25范围之内,则dp[i] = dp[i+1] + dp[i+2], 否则 dp[i] = dp[i+1]
// 因为自上而下求解会存在重复子问题,所以采用自下而上的方式。
class Solution {
public:
    int translateNum(int num) {
        string num_str = to_string(num);
        int length = num_str.length();
        int *dp = new int[length];
        int count = 0;
        for(int i=length-1; i>=0; i--){
            count = 0;
            if(i < length-1){
                count = dp[i+1];
            }
            else{
                count = 1;
            }
            if(i < length - 1){
                int digit1 = num_str[i] - '0';
                int digit2 = num_str[i+1] - '0';
                int converted = digit1 * 10 + digit2;
                if(converted >= 10 && converted <= 25){
                    if(i < length - 2){
                        count += dp[i+2];
                    }
                    else{
                        count += 1;
                    }
                }
            }
            dp[i] = count;
        }
        return dp[0];
    }
};

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

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

示例 1:

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

// 动态规划
// dp[i][j]是从棋盘的左上角(0, 0)走到(i, j)获得的最大价值
// 转移方程:dp[i][j] = max(dp[i-1][j]+v[i-1][j], dp[i][j-1]+v[i][j-1]);
class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size(); // 行数、列数
        int dp[205][205];
        dp[0][0] = grid[0][0];
        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                if(i < 1 && j < 1){
                    continue;
                }
                if(i < 1 && j >= 1){
                    dp[i][j] = dp[i][j-1] + grid[i][j];
                }
                else if(i >= 1 && j < 1){
                    dp[i][j] = dp[i-1][j] + grid[i][j];
                } 
                else if(i >= 1 && j >= 1){
                    dp[i][j] = max(dp[i-1][j] + grid[i][j], dp[i][j-1] + grid[i][j]);
                }
            }
        }
        return dp[m-1][n-1];
    }
};

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

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

示例 1:

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

示例 2:

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

示例 3:

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

// 动态规划
// dp[j]: 以下标j为结尾的最长子字符串长度
// s[i] 表示字符s[j]左边距离最近相同字符
// 当 dp[j - 1] < j - i,说明字符 s[i]在子字符串 dp[j-1] 区间之外 ,则 dp[j] = dp[j - 1] + 1 ;
// 当 dp[j - 1] >= j - i,说明字符 s[i] 在子字符串 dp[j-1] 区间之中 ,则 dp[j] 的左边界由 s[i] 决定,即 dp[j] = j - i;

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int res = 0, tmp = 0;
        for(int j = 0; j < s.length(); j++) {
            int i = j - 1;
            while(i >= 0 && s[i] != s[j]) i--; // 线性查找 i
            tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
            res = max(res, tmp); // max(dp[j - 1], dp[j])
        }
        return res;
    }
};


// 滑动窗口+双指针
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char> oc;
        int res = 0;
        int end = -1;
        for(int i=0; i<s.length(); i++){
            while(end+1 < s.length() && !oc.count(s[end+1])){
                oc.insert(s[end+1]);
                end++;
            }
            res = max(res, end - i + 1);
            oc.erase(s[i]);
        }
        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. 1 是丑数。
  2. n 不超过1690。

// 第一个丑数为1 -> 乘 2, 3, 5 得 2, 3, 5 -> 取最小得2(第2个丑数为2),目前在第2,所以需乘2的数的下标++,
// 2, 1, 1, 乘 2, 3, 5 得 4, 3, 5 -> 取最小3(第三个丑数),所以需乘3的数的下标++;
class Solution {
public:
    int nthUglyNumber(int n) {
        long nums[n];
        nums[0] = 1;
        int i2 = 0;
        int i3 = 0;
        int i5 = 0;
        long cur = 0;
        for (int i = 1; i < n; ++i)
        {
            cur = min(min(nums[i2]*2, nums[i3]*3), nums[i5]*5);
            nums[i] = cur;
            if (cur == nums[i2]*2)
            {
                ++i2;
            }
            if (cur == nums[i3]*3)
            {
                ++i3;
            }
            if (cur == nums[i5]*5)
            {
                ++i5;
            }
        }
        return nums[n-1];
    }
};

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

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

示例:

s = “abaccdeff”
返回 “b”

s = “”
返回 " "

class Solution {
public:
    char firstUniqChar(string s) {
        unordered_map<char, int> map;
        for(int i=0; i<s.length(); i++){
            map[s[i]]++;
        }
        int res = ' ';
        unordered_map<char, int>::iterator it;
        for(it=map.begin(); it != map.end(); it++){
            if(it->second == 1){
                res = it->first; 
            }
        }
        return res;
    }
};

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

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

示例 1:

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

限制:

0 <= 数组长度 <= 50000

方法为归并排序

class Solution {
public:
    int mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {
        if (l >= r) {
            return 0;
        }

        int mid = (l + r) / 2;
        int inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r); 
        int i = l, j = mid + 1, pos = l;
        while (i <= mid && j <= r) {
            if (nums[i] <= nums[j]) {
                tmp[pos] = nums[i];
                ++i;
                inv_count += (j - (mid + 1));
            }
            else {
                tmp[pos] = nums[j];
                ++j;
            }
            ++pos;
        }
        for (int k = i; k <= mid; ++k) {
            tmp[pos++] = nums[k];
            inv_count += (j - (mid + 1));
        }
        for (int k = j; k <= r; ++k) {
            tmp[pos++] = nums[k];
        }
        copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);
        return inv_count;
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        vector<int> tmp(n);
        return mergeSort(nums, tmp, 0, n - 1);
    }
};

剑指 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 个节点。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *h1 = headA, *h2 = headB;
        while(h1 != h2){
            h1 = h1 == nullptr ? headB : h1->next;
            h2 = h2 == nullptr ? headA : h2->next;
        }
        return h1;
    }
};

剑指 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

//使用二分法分别找到 左边界 leftleft 和 右边界 rightright
class Solution {
public:
    int search(vector<int>& nums, int target) {
        // 搜索右边界 right
        int i = 0, j = nums.size() - 1;
        while(i <= j) {
            int m = (i + j) / 2;
            if(nums[m] <= target) i = m + 1;
            else j = m - 1;
        }
        int right = i;
        // 若数组中无 target ,则提前返回
        if(j >= 0 && nums[j] != target) return 0;
        // 搜索左边界 left
        i = 0; j = nums.size() - 1;
        while(i <= j) {
            int m = (i + j) / 2;
            if(nums[m] < target) i = m + 1;
            else j = m - 1;
        }
        int left = j;
        return right - left - 1;
    }
};

剑指 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

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

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

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

// 中序遍历
class Solution {
public:
    int cur = 0, res = 0;
    int kthLargest(TreeNode* root, int k) {
        dfs(root, k);
        return res;
    }
    void dfs(TreeNode *root, int k)
    {
        if(root == nullptr) return;
        dfs(root->right, k);
        cur++;
        if(cur == k){
            res = root->val;
            return;
        }
        dfs(root->left, k);
    }
};

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

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

例如:

给定二叉树 [3,9,20,null,null,15,7],最大深度为3。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        return dfs(root);
    }

    int dfs(TreeNode* root){
        if(root == nullptr) return 0;
        int leftDepth = dfs(root->left);
        int rightDepth = dfs(root->right);
        return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1; 
    }
};

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

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

class Solution {
public:
    bool isBalanced(TreeNode* root) {
        return dfs(root);
    }

    bool dfs(TreeNode* root){
        if(root == nullptr) return true;
        return abs(depth(root->right)-depth(root->left)) <= 1 && dfs(root->left) && dfs(root->right); 
    }

    int depth(TreeNode* root){
        if(root == nullptr) return 0;
        int leftDepth = depth(root->left);
        int rightDepth = depth(root->right);
        return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 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]

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int ret = 0;
        for (int n : nums)
            ret ^= n; // a与b异或的结果(其他成对出现的数字异或被抵消了)
        int div = 1;
        while ((div & ret) == 0)
            div <<= 1; // 找到数字中第一个为1的位的位置
        int a = 0, b = 0;
        // 通过第div位是不是1将数组分为两个数组,其中a和b在不同的子数组中
        for (int n : nums) 
            if (div & n) 
                a ^= n;
            else
                b ^= n;
        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

// 如果一个数字出现三次,那么它的二进制表示的每一位(0 或 1)也出现三次。
// 如果把所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位的和都能被3整除。
// 那么将数组中所有数字二进制表示的每一位都加起来,这样每位除3的余数,就是最后的结果。
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        vector<int> bits(32,0);
        for(auto num : nums) {
            for(int i=0;i<32;i++) {
                bits[i] += num & 1;
                num >>= 1;
            }
        }
        int res = 0;
        for(int i=31; i>=0; i--) {
            res <<= 1;
            res += bits[i] % 3;
        }
        return res;
    }
};

剑指 Offer 57. 和为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]

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // vector<int> tar(2, 0);
        // unordered_map<int, int> hash;
        // for(int i=0; i<nums.size(); i++){
        //     auto find = hash.find(target - nums[i]);
        //     if(find == hash.end()){
        //         hash.emplace(nums[i], i);
        //     }
        //     else{
        //         tar = {nums[i], find->first};
        //         return tar;
        //     }
        // }
        // return {};

        // 双指针
        vector<int> res(2, 0);
        int l = 0, r = nums.size()-1;
        int cur = nums[l] + nums[r];
        while(l < r){
            if(cur < target){
                l++;
            }
            else if (cur > target){
                r--;
            }
            else{
                res = {nums[l], nums[r]};
                break;
            }
            cur = nums[l] + nums[r];
        }
        return res;
    }
};

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

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

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

示例 1:

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

示例 2:

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

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> res;
        int small = 1, big = 2;
        int middle = (1 + target) / 2;
        int curSum = small + big;
        while(small < middle){
            if(curSum == target){
                vector<int> v_seq;
                for(int i=small; i<=big; i++){
                    v_seq.push_back(i);
                }
                res.push_back(v_seq);
            }
            while(curSum > target && small < middle){
                curSum -= small;
                small++;
                if(curSum == target){
                    vector<int> v_seq;
                    for(int i=small; i<=big; i++){
                        v_seq.push_back(i);
                    }
                    res.push_back(v_seq);
                }
            }
            big++;
            curSum += big;
        }
        return res;
    }
};

剑指 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) {
        string res;
        int n = s.size();
        if(n == 0) return res;
        int right = n - 1;
        while(right >= 0){
            //从后往前寻找第一字符
            while(right >= 0 && s[right] == ' ') right--;
            if(right < 0) break;

            //从后往前寻找第一个空格
            int left = right;
            while( left >= 0 && s[left] != ' ' ) left--;

            //添加单词到结果
            res += s.substr(left + 1, right - left);
            res += ' ';

            //继续往前分割单词
            right = left;
        }
        //去除最后一个字符空格
        if (!res.empty()) res.pop_back();
        return res;
    }
};

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

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

示例 1:

输入: s = “abcdefg”, k = 2
输出: “cdefgab”

示例 2:

输入: s = “lrloseumgh”, k = 6
输出: “umghlrlose”

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

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

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

示例:
在这里插入图片描述

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        if(nums.size() == 0) return res;
        deque<int> q; //单调递减队列 注意 需要双端队列
        q.push_back(0);//队列里存的是序号 一开始是第0个数字
        if(k == 1) res.push_back(nums[0]);
        for(int i=1; i<nums.size(); i++){              
            while(!q.empty() && nums[q.back()] < nums[i]){ //只要尾部比当前数字小则pop
                q.pop_back(); 
            }
            q.push_back(i);
            while(i >= k && q.front() <= i - k) q.pop_front(); // 必须保证队列里的元素都是滑动窗口里的元素
            if(i >= k - 1) res.push_back(nums[q.front()]); //形成滑动窗口后才开始计算最大值

        }
        return res;

    }
};

剑指 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]

class MaxQueue {
    queue<int> q;
    deque<int> d;
public:
    MaxQueue() {
    }
    
    int max_value() {
        if (d.empty())
            return -1;
        return d.front();
    }
    
    void push_back(int value) {
        while (!d.empty() && d.back() < value) {
            d.pop_back();
        }
        d.push_back(value);
        q.push(value);
    }
    
    int pop_front() {
        if (q.empty())
            return -1;
        int ans = q.front();
        if (ans == d.front()) {
            d.pop_front();
        }
        q.pop();
        return ans;
    }
};

剑指 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]

// dp[i][j]: 投掷第i个骰子之后,点数和为j的次数
// dp[i][j] += dp[i-1][j-k] (k = 1, 2, ..., 6)

class Solution {
public:
    vector<double> dicesProbability(int n) {                                                                                                           int dp[15][70];
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= 6; i ++) {
            dp[1][i] = 1;
        }
        for (int i = 2; i <= n; i ++) {
            for (int j = i; j <= 6*i; j ++) {
                for (int cur = 1; cur <= 6; cur ++) {
                    if (j - cur <= 0) {
                        break;
                    }
                    dp[i][j] += dp[i-1][j-cur];
                }
            }
        }
        int all = pow(6, n);
        vector<double> ret;
        for (int i = n; i <= 6 * n; i ++) {
            ret.push_back(dp[n][i] * 1.0 / all);
        }
        return ret;
    }
};

剑指 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

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int cnt0 = 0;
        int begin;
        for(int i = 0; i<nums.size(); i++){
            if(nums[i] == 0){
                cnt0++;
            }
            else{
                if(i+1 < nums.size() && (nums[i+1] - nums[i])>1){
                    cnt0 -= nums[i+1] - nums[i] - 1;
                }
                else if(i+1 < nums.size() && nums[i+1] == nums[i]){
                    return false;
                }
            }
        }
        if(cnt0 < 0) return false;
        return true;
    }
};

剑指 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

// 约瑟夫问题
//反推:(当前index + m) % 上一轮剩余数字的个数
class Solution {
public:
    int lastRemaining(int n, int m) {
        int ans = 0;
        // 最后一轮剩下2个人,所以从2开始反推
        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。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()==0) return 0;
        int minn=prices[0], res=0;
        for(int i=1; i<prices.size(); i++){
            minn = min(minn, prices[i-1]);
            res = max(res, prices[i] - minn);
        }
        return res;
    }
};

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

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

示例:

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

class Solution {
public:
    int add(int a, int b) {
        while(b != 0) { // 当进位为 0 时跳出
            int c =(unsigned int)(a & b) << 1;  // c = 进位
            a ^= b; // a = 非进位和
            b = c; // b = 进位
        }
        return a;
    }
};

剑指 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]

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        if(a.size()==0) return {};
        vector<int> B(a.size(), 0);
        B[0] = 1;
        int tmp = 1;
        //下三角
        for(int i=1; i<a.size(); i++){
            B[i] = B[i-1] * a[i-1];
        }
        //上三角
        for(int i=a.size()-2; i>=0; i--){
            tmp *= a[i+1];
            B[i] = B[i] * tmp;
        }
        return B;
    }
};

剑指 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) {
        if(str.length()==0) return 0;
        // 先找到第一个非空字符
        int k=0;
        long long res = 0;
        bool isNegative = false;
        while(str[k]==' '){
            k++;
        }
        // 如果第一个非空字符不为'+'或'-'且不是一个有效整数字符,返回0
        if(str[k] != '+' && str[k] != '-' && str[k] < '0' && str[k] > '9') return 0;
        // 如果第一个非空字符为'+'
        if(str[k] == '+'){
            k++;
        }
        // 如果第一个非空字符为'-'
        else if(str[k] == '-'){
            isNegative = true;
            k++;
        }
        while(isdigit(str[k])){
            res = res * 10 + str[k]-'0';
            if(!isNegative && res > INT_MAX)//判断是否越界
                return INT_MAX;
            else if(isNegative && -res < INT_MIN)
                return INT_MIN;
            k++;
        }
        return isNegative?-res:res;
    }
};

剑指 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, 因为根据定义最近公共祖先节点可以为节点本身。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(p->val > q->val) { // 保证 p.val < q.val
            TreeNode *tmp = p;
            p = q;
            q = tmp;
        }
        while(root != nullptr) {
            if(root->val < p->val) // p,q 都在 root 的右子树中
                root = root->right; // 遍历至右子节点
            else if(root->val > q->val) // p,q 都在 root 的左子树中
                root = root->left; // 遍历至左子节点
            else break;
        }
        return root;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值