剑指offer(第2版)--解析和答案汇总(C++) --持续更新

目录

源代码:

面试题03. 数组中重复的数字

面试题04. 二维数组中的查找

面试题05. 字符串替换空格

面试题06. 从尾到头打印链表

面试题07. 重建二叉树

面试题09. 用两个栈实现队列

面试题10- I. 斐波那契数列

面试题10- II. 青蛙跳台阶问题

面试题11. 旋转数组的最小数字

面试题12. 矩阵中的路径

面试题13. 机器人的运动范围

面试题14- I. 剪绳子

面试题14- II. 剪绳子 II

面试题15. 二进制中1的个数

面试题16. 数值的整数次方

面试题17. 打印从1到最大的n位数

面试题18. 删除链表的节点

面试题19. 正则表达式匹配

面试题20. 表示数值的字符串

面试题21. 调整数组顺序使奇数位于偶数前面

面试题22. 链表中倒数第k个节点

面试题24. 反转链表

面试题25. 合并两个排序的链表

面试题26. 树的子结构

面试题27. 二叉树的镜像

面试题28. 对称的二叉树

面试题29. 顺时针打印矩阵

面试题30. 包含min函数的栈

面试题31. 栈的压入、弹出序列

面试题32 - I. 从上到下打印二叉树

面试题32 - II. 从上到下分行打印二叉树

面试题32 - III. 从上到下 之字型分行 打印二叉树

头文件

List.h

BinaryTree.h


源代码:

面试题03. 数组中重复的数字

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

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

//限制:
//2 <= n <= 100000

//解题思路:
//使用set记录数字,查到有重复的则返回

#include <iostream>
#include <vector>
#include <unordered_set> //无序的,比set效率更高
using namespace std;
int findRepeatNumber(vector<int>& nums) {
    if(nums.empty()){
        cout<<"nums is empty"<<endl;
        return 0;
    }
    unordered_set<int> tmp;//set只有键,map有键和值两个。键是唯一的 默认为升序  unordered_set表示不排序
    for(int num : nums){
        if(tmp.find(num)!=tmp.end()){
            return num;
        }
        tmp.insert(num);
    }
    cout<<"无重复数字"<<endl;
    return 0;
}

int main() {
    //测试用例1:正常用例
    vector<int> nums={2,3,1,0,2,5,3};
    //测试用例2:空数组
    //vector<int> nums;
    //测试用例3:数组中没有重复的数字
    //vector<int> nums={2,8,1,0,7,5,3};
    cout<<findRepeatNumber(nums);
    return 0;
}

面试题04. 二维数组中的查找

// Created by zzh on 2020/4/26.
//面试题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

//解题思路:
//从左下角或者右上角开始查找,会方便很多。
// 比如从左下角。比目标值小则右移,比目标值大则上移

#include <iostream>
#include <vector>
using namespace std;
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
    if(matrix.empty())
        return false;
    int m=matrix.size();
    int n=matrix[0].size();
    int row=m-1;//行
    int col=0;//列
    while(row>=0&&col<n){
        if(matrix[row][col]==target)
            return true;
        else if(matrix[row][col]>target)
            row--;
        else
            col++;
    }
    return false;
}

int main() {
    //测试用例1:正常用例
    vector<vector<int>> 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}};
    int target = 5;
    //int target = 20;
    //测试用例2:空数组
    //vector<vector<int>> matrix;
    cout<<findNumberIn2DArray(matrix, target);
    return 0;
}


面试题05. 字符串替换空格

// Created by zzh on 2020/4/26.
//面试题05. 替换空格
//请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

//示例 1:
//输入:s = "We are happy."
//输出:"We%20are%20happy."

//限制:
//0 <= s 的长度 <= 10000

//思路:
// 1. 如果允许重新生成一个字符串,问题则非常简单。
// 2. 如果是在原字符串上做修改,为了降低复杂度,先计算替换后的总长度,然后从后往前进行挪动修改会更好。

#include <iostream>
#include <string>
using namespace std;
/*
//先按思路1
string replaceSpace(const string& s) {
    if(s.empty()){
        cout<<"s is empty"<<endl;
        return s;
    }

    string res;
    for(char c:s){
        if(c==' ')
            res+="%20";
        else
            res+=c;
    }
    return res;
}
 */

//按思路2
void replaceSpace(string& s) {
    if(s.empty()){
        cout<<"s is empty"<<endl;
        return;
    }
    int cnt=0;
    for(char c:s){
        if(c==' ')
            cnt++;
    }
    int i=s.size()-1,j=i+2*cnt;
    while(i>=0 && i<j){
        if(s[i]==' '){
            s[j--]='0';
            s[j--]='2';
            s[j--]='%';
            i--;
        }
        else
            s[j--]=s[i--];
    }
}

int main() {
    //测试用例1:正常用例
    string s = "We are happy.";
    //s.resize(100);//保证后边有足够的空间
    //测试用例2:空字符串
    //string s="";

    replaceSpace(s);
    cout << s;
    return 0;
}

面试题06. 从尾到头打印链表

// Created by zzh on 2020/4/26.
//面试题06. 从尾到头打印链表
//输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
//
//示例 1:
//输入:head = [1,3,2]
//输出:[2,3,1]
//
//限制:
//0 <= 链表长度 <= 10000

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;

struct ListNode{
    int val;
    ListNode* next;
    explicit ListNode(int x):val(x),next(nullptr){}
};

/*
//方法一 使用reverse函数
vector<int> reversePrint(ListNode* head) {
    if(head == nullptr){
        cout<<"head is nullptr"<<endl;
        return {0};
    }
    ListNode* p=head;
    vector<int> res;
    while(p!=nullptr){
        res.push_back(p->val);
        p=p->next;
    }
    reverse(res.begin(),res.end());
    return res;
}
*/
//方法二 使用栈的先进后出特性
vector<int> reversePrint(ListNode* head) {
    if(head == nullptr){
        cout<<"head is nullptr"<<endl;
        return {0};
    }

    ListNode* p=head;
    stack<int> s;
    vector<int> res;
    while(p!=nullptr){
        s.push(p->val);
        p=p->next;
    }
    while(!s.empty()){
        res.push_back(s.top());
        s.pop();
    }
    return res;

}

int main() {
    //测试用例1:正常用例
    auto head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    //测试用例2:nullptr
    //ListNode* head = nullptr;
    vector<int> res;
    res = reversePrint(head);
    for(int i:res)
        cout << i;
    return 0;
}

面试题07. 重建二叉树

代码中用到的头文件见文末“BinaryTree.h”

// Created by zzh on 2020/4/26.
//面试题07. 重建二叉树
//输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
//例如,给出
//前序遍历 preorder = [3,9,20,15,7]
//中序遍历 inorder = [9,3,15,20,7]
//返回如下的二叉树:
//   3
//  / \
// 9  20
//   /  \
//  15   7
//限制:
//0 <= 节点个数 <= 5000

//题目分析:
//前序遍历:根左右  中序遍历 左中右
//前序遍历的首个元素即为根节点 root 的值;
//在中序遍历中搜索根节点 root 的索引 ,可将中序遍历划分为 [ 左子树 | 根节点 | 右子树 ] 。
//根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为 [ 根节点 | 左子树 | 右子树 ] 。
//自此可确定 三个节点的关系 :1.树的根节点、2.左子树根节点、3.右子树根节点(即前序遍历中左(右)子树的首个元素)。
//子树特点: 子树的前序和中序遍历仍符合以上特点
//根据子树特点,我们可以通过同样的方法对左(右)子树进行划分,每轮可确认三个节点的关系 。此递推性质让我们联想到用 递归方法 处理

#include <vector>
#include "BinaryTree.h"
using namespace std;

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    if(preorder.empty()||inorder.empty())
        return nullptr;
    auto head = new TreeNode(preorder[0]);
    int root=0;
    for(int i=0;i<inorder.size();i++){
        if(inorder[i]==preorder[0]){
            root=i;
            break;
        }
    }
    vector<int> left_pre(preorder.begin()+1,preorder.begin()+root+1);//左闭右开
    vector<int> right_pre(preorder.begin()+root+1,preorder.end());
    vector<int> left_in(inorder.begin(),inorder.begin()+root);
    vector<int> right_in(inorder.begin()+root+1,inorder.end());
    head->left=buildTree(left_pre,left_in);
    head->right=buildTree(right_pre,right_in);
    return head;
}

int main() {
    //测试用例1:正常用例
    vector<int> preorder = {3,9,20,15,7};
    vector<int> inorder = {9,3,15,20,7};
    //测试用例2:nullptr
//    vector<int> preorder;
//    vector<int> inorder = {9,3,15,20,7};
    auto res =  buildTree(preorder, inorder);
    PrintTree(res);
    return 0;
}

面试题09. 用两个栈实现队列

// Created by zzh on 2020/4/26.
//面试题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 次调用

#include <iostream>
#include <stack>
using namespace std;
class CQueue {
public:
    stack<int> stack1;
    stack<int> stack2;
    CQueue() {
    }
    void appendTail(int value) {
        stack1.push(value);
    }

    int deleteHead() {
        if(stack1.empty())
            return -1;
        while(!stack1.empty()){
            int tmp=stack1.top();
            stack1.pop();
            stack2.push(tmp);
        }
        int res=stack2.top();
        stack2.pop();
        while(!stack2.empty()){
            int tmp=stack2.top();
            stack2.pop();
            stack1.push(tmp);
        }
        return res;
    }
};
int main() {
    //测试用例1:正常用例
    auto* obj = new CQueue();
    obj->appendTail(7);
    int param_2 = obj->deleteHead();
    //测试用例2:nullptr
    cout << param_2;
    return 0;
}

面试题10- I. 斐波那契数列

// Created by zzh on 2020/4/26.
//面试题10- I. 斐波那契数列
//写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
//F(0) = 0,   F(1) = 1
//F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
//斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
//答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
#include <iostream>
using namespace std;
int fib(int n) {
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    else {
        int a = 0;
        int b = 1;
        int sum = 0;
        while (n > 1) { //动态规划
            sum = (a + b) % 1000000007;
            a = b;
            b = sum;
            n--;
        }
        return b;
    }
}
int main(){
    cout << fib(10);
    return 0;
}

面试题10- II. 青蛙跳台阶问题

// Created by zzh on 2020/4/26.
//面试题10- II. 青蛙跳台阶问题
//一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
//答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
//示例 1:
//输入:n = 2
//输出:2
//示例 2:
//输入:n = 7
//输出:21
//提示:
//0 <= n <= 100
#include <iostream>
using namespace std;
int numWays(int n) {
    if(n==0||n==1)
        return 1;
    else if(n==2)
        return 2;
    else{
        int a=1;
        int b=2;
        int c=0;
        while(n>2){
            c=(a+b)%1000000007;
            a=b;
            b=c;
            n--;
        }
        return c;
    }
}
int main(){
    cout << numWays(9);
    return 0;
}

面试题11. 旋转数组的最小数字

// Created by zzh on 2020/4/26.
//面试题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

#include <iostream>
#include <vector>
using namespace std;
int minArray(vector<int>& numbers) {
    if(numbers.empty())
        return 0;
    if(numbers.size()==1)
        return numbers[0];
    int left=0;
    int right=numbers.size()-1;
    //mid要与right作比较,因为可能有整个数组都旋转的情况,right肯定在右边序列。而left不确定是左边序列还是右边
    while(left<right){
        int mid=(left+right)/2;
        if(numbers[mid]>numbers[right])
            left=mid+1;
        else if(numbers[mid]<numbers[right])
            right=mid;
        else //相等的时候,比较特殊,无法判断最小值在左在右 如[1,0,1,1,1],[1,1,1,0,1]
            right--;//缩小范围,并且不会删掉最小值 left++则有可能删掉最小值
    }
    return numbers[left];
}
int main(){
    vector<int> numbers = {3,4,5,1,2};
    cout << minArray(numbers);
}

面试题12. 矩阵中的路径

// Created by zzh on 2020/4/26.
//面试题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

#include <iostream>
#include <string>
#include <vector>
using namespace std;
bool def(vector<vector<char>>& board, string word,int i,int j,int k){
    if(i<0||j<0||i>=board.size()||j>=board[0].size()||board[i][j]!=word[k])
        return false;
    if(k==word.size()-1)
        return true;
    char tmp=board[i][j];
    board[i][j]='/';
    bool res=def(board, word,i+1,j,k+1)||def(board, word,i-1,j,k+1)||def(board, word,i,j+1,k+1)||def(board, word,i,j-1,k+1);
    board[i][j]=tmp;
    return res;
}
bool exist(vector<vector<char>>& board, const string& word) {
    for(int i=0;i<board.size();i++)
        for(int j=0;j<board[0].size();j++){
            if(def(board, word,i,j,0))
                return true;
        }
    return false;
}

int main() {
    vector<vector<char>> board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
    string word = "ABCCED";
    cout << exist(board, word);
    return 0;
}

面试题13. 机器人的运动范围

//
// Created by zzh on 2020/4/26.
//面试题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

#include <iostream>
#include <vector>

using namespace std;
int get(int i){//得到各位的和
    int res=0;//局部变量要给初值,不然不确定
    for(;i;i/=10){
        res += i%10;
    }
    return res;
}
int movingCount(int m, int n, int k) {
    int res=0;
    vector<vector<int>> vis(m,vector<int>(n,0));//m*n维,初值为0
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            if(get(i)+get(j)>k)
                continue;
            if(i==0&&j==0)
                vis[i][j]=1;
            if(j==0&&i>0)//这两个分开,而不直接用左和上的或,是为了考虑边上的行和列,防止越界
                vis[i][j] = vis[i-1][j];//和上边的求或
            if(i==0&&j>0)
                vis[i][j] = vis[i][j-1];//和左边的求或
            if(i>0&&j>0)
                vis[i][j]=vis[i-1][j]|vis[i][j-1];
            res+=vis[i][j];
        }
    }
    return res;
}
int main() {
    int m = 2, n = 3, k = 1;
    cout << movingCount(m, n, k);
    return 0;
}

面试题14- I. 剪绳子

//
// Created by zzh on 2020/4/26.
//面试题14- I. 剪绳子
//给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),
//每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?
//例如,当绳子的长度是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


#include <iostream>
#include <algorithm>
using namespace std;
int cuttingRope(int n) {
    if (n<=3)
        return n-1;
    int a=n/3;
    int b=n-3*a;
    if(b==0)
        return pow(3,a);
    else if(b==1)
        return pow(3,a-1)*4;
    else
        return pow(3,a)*b;

}
int main() {
    int n = 8;
    cout << cuttingRope(n);
    return 0;
}

面试题14- II. 剪绳子 II

//
// Created by zzh on 2020/4/26.
//面试题14- II. 剪绳子 II
//给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1)
// 每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?
// 例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
//答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

#include <iostream>

using namespace std;
int cuttingRope(int n) {
    if(n<=3)
        return n-1;
    long res=1;
    while(n>4){
        res = (res*3)%1000000007;
        n -=3;
    }
    res = (res*n)%1000000007;
    return res;
}
int main() {
    int n = 8;
    cout << cuttingRope(n);
    return 0;
    return 0;
}

面试题15. 二进制中1的个数

//
// Created by zzh on 2020/4/26.
//面试题15. 二进制中1的个数
//请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。
//例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

#include <iostream>

using namespace std;
/*
//无符号int型,最简单
int hammingWeight(uint32_t n) {
    int res=0;
    while(n){
        if(n&1)
            res++;
        n >>= 1;
    }
    return res;
}

//有符号型,可能为负数,最高位为1,右移之后最高位仍补1,会死循环,因此不能使用右移
//负数以补码形式存储,原码->符号位不变,其他位取反->反码 +1 ->补码
int hammingWeight(int n) {
    int res=0;
    unsigned int flag=1;
    while(flag){
        if(n&flag)
            res++;
        flag <<= 1;
    }
    return res;
}
*/
//利用特性,n=n&(n-1),会去掉n从右边开始的第一个1,时间复杂度更低,负数也适用
int hammingWeight(int n) {
    int res=0;
    while(n){
        n=n&(n-1);
        res++;
    }
    return res;
}
int main() {
    int n=7;
    cout << hammingWeight(n);
    return 0;
}

面试题16. 数值的整数次方

//
// Created by zzh on 2020/4/26.
//面试题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] 。


#include <iostream>
using namespace std;
//暴力循环求解复杂度较高,改进用二分法x^n=(x^2)^(n/2)
double myPow(double x, int n) {
    long a=n;
    if(x==0)
        return x;
    if(n==0)
        return 1;
    if(n<0){
        a=-a;
        x=1/x;
    }
    double res=x;
    double tmp=1;
    while(a>=2){
        if(a&1)
            tmp *= res;//存储那些奇数项
        res *= res;
        a >>= 1; //等价于a/2
    }
    res *= tmp;
    return res;
}
int main() {
    double x=7.1;
    int n=2;
    cout << myPow(x, n);
    return 0;
}

面试题17. 打印从1到最大的n位数

//
// Created by zzh on 2020/4/26.
//面试题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 为正整数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> printNumbers(int n) {
    vector<int> res;
    int i=1;
    int tmp=pow(10,n)-1;
    while(tmp--){
        res.push_back(i++);
    }
    return res;
}
int main() {
    int n = 2;
    vector<int> res = printNumbers(n);
    for(int i:res)
        cout << i;
    return 0;
}

面试题18. 删除链表的节点

//
// Created by zzh on 2020/4/26.
//面试题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 被删除的节点

#include <iostream>

using namespace std;
struct ListNode {
    int val;
    ListNode *next;
    explicit ListNode(int x) : val(x), next(NULL) {}
    };
ListNode* deleteNode(ListNode* head, int val) {
    ListNode* tmp=head;
    if(tmp->val==val)
        return head->next;
    while(tmp->next->val!=val){
        tmp=tmp->next;
    }
    tmp->next=tmp->next->next;
    return head;
}
int main() {
    auto head = new ListNode(4);
    head->next = new ListNode(1);
    head->next->next = new ListNode(5);
    head->next->next->next = new ListNode(9);
    head = deleteNode(head, 5);
    auto tmp=head;
    while(tmp){
        cout<<tmp->val<<" ";
        tmp = tmp->next;
    }
    return 0;
}

面试题19. 正则表达式匹配

//
// Created by zzh on 2020/4/26.
//面试题19. 正则表达式匹配
//请实现一个函数用来匹配包含'. '和'*'的正则表达式。
//模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。
//在本题中,匹配是指字符串的所有字符匹配整个模式。
//例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"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 = "c*a*b"
//输出: true
//解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
//示例 5:
//输入:
//s = "mississippi"
//p = "mis*is*p*."
//输出: false
//s 可能为空,且只包含从 a-z 的小写字母。
//p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。


#include <iostream>
#include <string>
using namespace std;
bool isMatch(string s, string p) {
    if(p.empty()) return s.empty();
    if(p[1] == '*'){
        return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || p[0] == '.')) && isMatch(s.substr(1), p);
    }
    else{
        return !s.empty() && (s[0] == p[0] || p[0] == '.') && (isMatch(s.substr(1), p.substr(1)));
    }

}
int main() {
    string s = "mississippi";
    string p = "mis*is*p*.";
    cout << isMatch(s, p);
    return 0;
}

面试题20. 表示数值的字符串

// Created by zzh on 2020/4/26.
//面试题20. 表示数值的字符串
//请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
//例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"0123"都表示数值,
//但"12e"、"1a3.14"、"1.2.3"、"+-5"、"-1E-16"及"12e+5.4"都不是。

//解题思路
//1)先去除字符串首尾的空格
//2)然后根据e划分指数和底数
//3)判断指数和底数是否合法即可

#include <iostream>

using namespace std;
bool judgeBase(string s){//判断底数是否正常
    if (s.empty())
        return false;
    int cnt_point = 0;
    bool res = false;
    for(int i=0;i<s.size();i++){
        if(s[i]=='+'||s[i]=='-'){//符号位如果不在第一位,则false
            if(i!=0) return false;
            else continue;
        }
        else if(s[i]=='.'){//小数点多于一位,false
            cnt_point++;
            if(cnt_point>1||cnt_point==s.size()) return false;
        }
        else if(s[i]<'0'||s[i]>'9'){//出现非数字,false
            return false;
        }
        else//至少有一个正常数字,才变成true
            res = true;
    }
    return res;//全部正常
}
bool judgeExponent(string s){//判断指数是否正常
    if (s.empty())
        return false;
    bool res = false;
    for(int i=0;i<s.size();i++){
        if(s[i]=='+'||s[i]=='-'){//符号位如果不在第一位,则false
            if(i!=0) return false;
            else continue;
        }
        else if(s[i]<'0'||s[i]>'9'){//出现非数字,false
            return false;
        }
        else//至少有一个正常数字,才变成true
            res = true;
    }
    return res;
}
bool isNumber(string s) {
    int i = s.find_first_not_of(' ');//找到第一个不是空格的位置
    if(i == string::npos) return false;//如果找不到
    int j = s.find_last_not_of(' ');//找到最后一个不是空格的位置
    s = s.substr(i,j-i+1);//从i开始,截取j-i+1个元素
    int pos = s.find('e');//找e的位置
    if(pos == string::npos) //如果没有e
        return judgeBase(s);
    else
        return judgeBase(s.substr(0, pos)) && judgeExponent(s.substr(pos+1));

}
int main() {
//    string s = ".";
//    string s = "-4e+";
//    string s = "e1";
//    string s = "";
    string s = "5e2";
    cout << isNumber(s);
    return 0;
}

面试题21. 调整数组顺序使奇数位于偶数前面

// Created by zzh on 2020/4/27.
//面试题21. 调整数组顺序使奇数位于偶数前面
//输入一个整数数组,实现一个函数来调整该数组中数字的顺序,
//使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

//解题思路:
//像这种数组分前后的,考虑使用前后两个指针。
//如果都是奇数,前指针后移。都是偶数,后指针前移
//前奇后偶,同时移动。前偶后奇,则交换位置。
#include <iostream>
#include <vector>
using namespace std;
vector<int> exchange(vector<int>& nums) {
    int i = 0, j = nums.size() - 1;
    while (i < j) {
        bool a = nums[i] & 1;
        bool b = nums[j] & 1;
        if (a && b)//都是奇数
            i++;
        else if (!a && !b)//都是偶数
            j--;
        else if (a && !b) {//a奇b偶
            i++;
            j--;
        } else {//a偶b奇
            int tmp = nums[i];
            nums[i++] = nums[j];
            nums[j--] = tmp;
        }
    }
    return nums;
}
int main() {
    vector<int> nums = {1,2,3,4};
    auto res = exchange(nums);
    for(int n:res)
        cout << n << ' ';
    return 0;
}

面试题22. 链表中倒数第k个节点

// Created by zzh on 2020/4/27.
//面试题22. 链表中倒数第k个节点
//输入一个链表,输出该链表中倒数第k个节点。
//为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
//例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。
//这个链表的倒数第3个节点是值为4的节点。
//示例:
//给定一个链表: 1->2->3->4->5, 和 k = 2.
//返回链表 4->5.

//解题思路:
//这类题,使用两个指针,后边的指针先走k步,然后二者同步走,直到后指针到头

#include <iostream>
#include "List.h"
using namespace std;
ListNode* getKthFromEnd(ListNode* head, int k) {
    if(head==nullptr||k<=0)
        return nullptr;
    ListNode* pre=head;
    ListNode* cur=head;
    while(k--){
        if(cur==nullptr)
            return nullptr;
        cur=cur->next;
    }
    while(cur){
        pre=pre->next;
        cur=cur->next;
    }
    return pre;
}
int main() {
    vector<int> list = {1,2,3,4,5};
    ListNode* head = createList(list);
    ListNode* res = getKthFromEnd(head, 3);
    cout << res->val;
    return 0;
}

面试题24. 反转链表

// Created by zzh on 2020/4/27.
//面试题24. 反转链表
//定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
//示例:
//输入: 1->2->3->4->5->NULL
//输出: 5->4->3->2->1->NULL
//限制:
//0 <= 节点个数 <= 5000

#include <iostream>
#include <vector>
#include "List.h"
#include <stack>
using namespace std;

ListNode* reverseList(ListNode* head) {
    if(head == nullptr)
        return head;
    ListNode* pre= nullptr;
    ListNode* cur=head;
    ListNode* tmp=head;

    while(cur){
        tmp=cur->next;//暂存当前节点的下一个节点的指针
        cur->next=pre;//当前节点的下一节点更改至前一节点
        pre=cur;//前节点后移
        cur=tmp;//当前节点后移
    }
    return pre;
}
int main() {
    vector<int> list = {1,2,3,4,5};
    ListNode* head = createList(list);
    ListNode* res = reverseList(head);
    printList(res);
    return 0;
}

面试题25. 合并两个排序的链表

// Created by zzh on 2020/4/27.
//面试题25. 合并两个排序的链表
//输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
//示例1:
//输入:1->2->4, 1->3->4
//输出:1->1->2->3->4->4
//限制:
//0 <= 链表长度 <= 1000

#include <iostream>
#include <vector>
#include "List.h"
using namespace std;
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    if(l1==nullptr && l2==nullptr)
        return nullptr;
    if(l1==nullptr && l2!=nullptr)
        return l2;
    if(l1!=nullptr && l2==nullptr)
        return l1;
    /*解法1 创建出一个新链表
    ListNode* sum=new ListNode(0);
    ListNode* cur=sum;
    while(l1!=NULL&&l2!=NULL){
        if(l1->val<=l2->val){
            cur->next=new ListNode(l1->val);
            cur=cur->next;
            l1=l1->next;
        }
        else{
            cur->next=new ListNode(l2->val);
            cur=cur->next;
            l2=l2->next;
        }
    }
    while(l1==NULL&&l2!=NULL){
        cur->next=new ListNode(l2->val);
        cur=cur->next;
        l2=l2->next;
    }
    while(l1!=NULL&&l2==NULL){
        cur->next=new ListNode(l1->val);
        cur=cur->next;
        l1=l1->next;
    }
    return sum->next;
    */
    //解法2,使用原链表,调整两个链表每个节点间的指向关系
    auto* cur=new ListNode(0);
    ListNode* res=cur;
    while(l1!=nullptr&&l2!=nullptr){
        if(l1->val<=l2->val){
            cur->next=l1;//下一个节点指向l1
            l1=l1->next;//l1后移
        }
        else{
            cur->next=l2;
            l2=l2->next;
        }
        cur=cur->next;  // 当前节点后移
    }
    cur->next = l1==nullptr?l2:l1;//接上没到头的部分
    return res->next;
}
int main() {
    vector<int> list1 = {1,2,3,4,5};
    vector<int> list2 = {7,8,9};
    ListNode* head1 = createList(list1);
    ListNode* head2 = createList(list2);
    ListNode* res = mergeTwoLists(head1,head2);
    printList(res);
    return 0;
}

面试题26. 树的子结构

// Created by zzh on 2020/4/29.
//面试题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

//思路:使用递归,isSubStructure递归使得A每个节点都能尝试作为初始节点,
// helper递归使得能一直比较到叶子节点

#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
bool helper(TreeNode* A, TreeNode* B){
    if(A==nullptr||B==nullptr)
        return (B == nullptr);//如果B遍历到叶子节点以下了,则是子树,如果A先到叶子节点以下了,则不是
    if(A->val!=B->val)
        return false;
    else//如果当前节点值相等,则继续向下比较左右子树
        return helper(A->left,B->left) && helper(A->right,B->right);
}


bool isSubStructure(TreeNode* A, TreeNode* B) {
    if(A==nullptr||B==nullptr){
        return false;
    }
    //helper用于判断从当前节点开始,是否为子树。
    // 另两个递归,是节点下移,递归使得A每个节点都能尝试作为初始节点
    return helper(A,B)||isSubStructure(A->left,B)||isSubStructure(A->right,B);
}


int main() {
    vector<int> A={3,4,5,1,2};
    vector<int> B={4,1};
    auto a = createTree(A);
    auto b = createTree(B);
    cout << isSubStructure(a,b);
    return 0;
}

面试题27. 二叉树的镜像

// Created by zzh on 2020/4/27.
//面试题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

#include <iostream>
#include "BinaryTree.h"
using namespace std;
TreeNode* mirrorTree(TreeNode* root) {
    if(root== nullptr)
        return nullptr;
    swap(root->left,root->right);//root节点存的两个指针交换位置
    mirrorTree(root->left);
    mirrorTree(root->right);
    return root;
}
int main() {
    auto p1 = createTreeNode(4);
    auto p2 = createTreeNode(2);
    auto p3 = createTreeNode(7);
    auto p4 = createTreeNode(1);
    auto p5 = createTreeNode(3);
    auto p6 = createTreeNode(6);
    auto p7 = createTreeNode(9);
    connectTreeNode(p1,p2,p3);
    connectTreeNode(p2,p4,p5);
    connectTreeNode(p3,p6,p7);
    PrintTree(p1);
    p1 = mirrorTree(p1);
    PrintTree(p1);
    return 0;
}

面试题28. 对称的二叉树

// Created by zzh on 2020/4/27.
//面试题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

#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
bool sub(TreeNode* left,TreeNode* right){
    if(left==nullptr&&right!=nullptr)
        return false;
    if(left!=nullptr&&right==nullptr)
        return false;
    if(left==nullptr&&right==nullptr)
        return true;
    if (left->val==right->val)
        return sub(left->left,right->right)&&sub(left->right,right->left);
    else
        return false;
}
bool isSymmetric(TreeNode* root) {
    if(root==nullptr)
        return true;
    return sub(root->left,root->right);
}

int main() {
    vector<int> vec = {1,2,2,3,4,4,3};
    //vector<char> vec = {'1','2','2','#','3','#','3'}; //#号表示空节点
    auto root = createTree(vec);
    bool res = isSymmetric(root);
    cout << res;
    return 0;
}

面试题29. 顺时针打印矩阵

// Created by zzh on 2020/4/27.
//面试题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

//思路:不断循环,不断缩小范围,直到上下或者左右碰头
#include <iostream>
#include <vector>
#include <typeinfo>
using namespace std;

vector<int> spiralOrder(vector<vector<int>>& matrix) {
    if(matrix.empty())
        return {};
    int top=0,bottom=matrix.size()-1,left=0,right=matrix[0].size()-1;
    vector<int> res;
    while(true){
        for(int i=left;i<=right;i++)
            res.push_back(matrix[top][i]);
        top++;
        if(top>bottom)
            break;
        for(int i=top;i<=bottom;i++)
            res.push_back(matrix[i][right]);
        right--;
        if(left>right)
            break;
        for(int i=right;i>=left;i--)
            res.push_back(matrix[bottom][i]);
        bottom--;
        if(top>bottom)
            break;
        for(int i=bottom;i>=top;i--)
            res.push_back(matrix[i][left]);
        left++;
        if(left>right)
            break;
    }
    return res;
}

int main() {
    vector<vector<int>> matrix = {{1,2,3},{4,5,6},{7,8,9}};
    auto res = spiralOrder(matrix);
    for(int i:res){
        cout << i;
    }
    return 0;
}

面试题30. 包含min函数的栈

// Created by zlgybz on 2020/5/1.
//面试题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.

//思路:使用一个辅助栈用来记录当前位置非严格降序的最小值
#include <iostream>
#include <stack>
using namespace std;
class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {
    }
    void push(int x) {
        s.push(x);
        if(m.empty()||x<=m.top())//如果m为空或者当前x最小
            m.push(x);
    }
    void pop() {
        if(s.top()==m.top())//如果当前是最小元素,则都吐出
            m.pop();
        s.pop();
    }

    int top() {
        return s.top();
    }

    int min() {
        return m.top();
    }
    stack<int> s;
    stack<int> m;
};

int main() {
    MinStack minStack;
    minStack.push(-2);
    minStack.push(0);
    minStack.push(-3);
    cout << minStack.min() << endl;
    minStack.pop();
    cout << minStack.top() << endl;
    cout << minStack.min() << endl;
    return 0;
}

面试题31. 栈的压入、弹出序列

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

//思路:
//尝试按照 popped 中的顺序模拟一下出栈操作,如果符合则返回 true,否则返回 false。这里用到的贪心法则是如果栈顶元素等于 popped 序列中下一个要 pop 的值,则应立刻将该值 pop 出来。
//使用一个栈 st 来模拟该操作。将 pushed 数组中的每个数依次入栈,同时判断这个数是不是 popped 数组中下一个要 pop 的值,如果是就把它 pop 出来。最后检查栈是否为空。


#include <iostream>
#include <vector>
#include <stack>
using namespace std;
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
    if(pushed.empty()||popped.empty())
        return true;
    stack<int> st;
    int j=0;
    for(int i=0;i<pushed.size();i++){//挨个压入
        st.push(pushed[i]);
        //由于压入栈的所有数字均不相等,如果当前栈顶元素等于要吐出的元素,则吐出
        while(!st.empty() && j<popped.size() && st.top()==popped[j]){
            st.pop();
            j++;
        }
    }
    return st.empty();//是否全部出完
}
int main() {
    vector<int> A={1,2,3,4,5};
    vector<int> B={4,3,5,1,2};
    cout << validateStackSequences(A, B) << endl;
    return 0;
}

面试题32 - I. 从上到下打印二叉树

// Created by zlgybz on 2020/5/1.
//面试题32 - I. 从上到下打印二叉树
//从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
//例如:给定二叉树: [3,9,20,null,null,15,7],
//    3
//   / \
//  9  20
//    /  \
//    15   7
//返回:
//[3,9,20,15,7]
//提示:
//节点总数 <= 1000

//思路:使用队列的先进先出特性,分层打印

#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
vector<int> levelOrder(TreeNode* root) {
    vector<int> res;
    queue<TreeNode*> tmp;
    if (root == nullptr){
        return {};
    }
    else{
        tmp.push(root);
    }
    while(!tmp.empty()){
        auto front = tmp.front();
        if(front == nullptr){//已经到了叶子节点的下方
            tmp.pop();
        }
        else{
            res.push_back(front->val);
            tmp.push(front->left);
            tmp.push(front->right);
            tmp.pop();
        }
    }
    return res;
}
int main() {
    vector<int> A={3,9,20,-1,-1,15,7};//-1表示空节点
    auto root = createTree(A);
    auto res = levelOrder(root);
    for(int i:res)
        cout << i << ' ';
    return 0;
}

面试题32 - II. 从上到下分行打印二叉树

// Created by zlgybz on 2020/5/1.
//面试题32 - II. 从上到下打印二叉树 II
//从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
//例如:给定二叉树: [3,9,20,null,null,15,7],
//    3
//   / \
//  9  20
//    /  \
//   15   7
//返回其层次遍历结果:
//[
//[3],
//[9,20],
//[15,7]
//]
//提示:
//节点总数 <= 1000

//思路:和上一题相比,增加一个for循环,用于分隔每行。

#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    queue<TreeNode*> tmp;
    if (root == nullptr){
        return {};
    }
    else {
        tmp.push(root);
    }
    while(!tmp.empty()){
        int length = tmp.size();
        vector<int> vec;
        for (int i=0;i<length;i++){
            auto front = tmp.front();
            vec.push_back(front->val);
            if(front->left!=nullptr) tmp.push(front->left);
            if(front->right!=nullptr) tmp.push(front->right);
            tmp.pop();
        }
        res.push_back(vec);

    }
    return res;
}
int main() {
    vector<int> A={3,9,20,-1,-1,15,7};//-1表示空节点
    auto root = createTree(A);
    auto res = levelOrder(root);
    for(int i=0;i<res.size();i++){
        for(int j:res[i])
            cout << j << ' ';
        cout << endl;
    }
    return 0;
}

面试题32 - III. 从上到下 之字型分行 打印二叉树

// Created by zlgybz on 2020/5/1.
//面试题32 - III. 从上到下打印二叉树 III
//请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
//例如:
//给定二叉树: [3,9,20,null,null,15,7],
//    3
//   / \
//  9  20
//    /  \
//   15   7
//返回其层次遍历结果:
//[
//[3],
//[20,9],
//[15,7]
//]
//提示:
//节点总数 <= 1000

//思路:和上一题相比,要使用双端队列,队列的存储顺序依旧是从前到后对应树的从左到右
// 但是用flag区分奇偶行,实现奇偶行不同的取和放操作。
#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    deque<TreeNode*> tmp;
    if (root == nullptr){
        return {};
    }
    else {
        tmp.push_back(root);
    }
    bool flag = true;
    while(!tmp.empty()){
        int length = tmp.size();
        vector<int> vec;
        for (int i=0;i<length;i++){
            if(flag){//奇数行,前取后放
                auto front = tmp.front();
                vec.push_back(front->val);
                if(front->left!=nullptr) tmp.push_back(front->left);//先放左节点
                if(front->right!=nullptr) tmp.push_back(front->right);
                tmp.pop_front();
            }
            else{//偶数行,后取前放
                auto front = tmp.back();
                vec.push_back(front->val);
                if(front->right!=nullptr) tmp.push_front(front->right);//先放右节点
                if(front->left!=nullptr) tmp.push_front(front->left);
                tmp.pop_back();
            }

        }
        flag = !flag;
        res.push_back(vec);

    }
    return res;
}
int main() {
    vector<int> A={3,9,20,-1,-1,15,7};//用-1来表示空节点
    auto root = createTree(A);
    auto res = levelOrder(root);
    for(auto & re : res){
        for(int j:re)
            cout << j << ' ';
        cout << endl;
    }
    return 0;
}

面试题33. 二叉搜索树的后序遍历序列

后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根”。

二叉搜索树定义: 左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 > 根节点的值;其左、右子树也分别为二叉搜索树。

 与解析处理不同的地方:

  1. m初始值给-1,用以判断如果找不到大于根节点的节点的情况,此时全是左子树。
  2. 当右子树区间出现不符合条件的节点时,直接返回false即可。
// Created by zlgybz on 2020/5/1.
//面试题33. 二叉搜索树的后序遍历序列
//输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
//参考以下这颗二叉搜索树:
//     5
//    / \
//   2   6
//  / \
// 1   3
//示例 1:
//输入: [1,6,3,2,5]
//输出: false
//示例 2:
//输入: [1,3,2,6,5]
//输出: true
//提示:
//数组长度 <= 1000
#include <iostream>
#include <vector>
using namespace std;
bool helper(vector<int>& postorder,int start,int end){
    if(start>=end)
        return true;
    int mid = -1;
    for(int i=start;i<end;i++){//找到第一个右子树的索引
        if(postorder[i]>postorder[end]){
            mid = i;
            break;
        }
    }
    if(mid==-1)//没找到,说明全是左子树
        return helper(postorder,start,end-1);
    for(int j=mid;j<end;j++){//判断右边的是否都大于根节点
        if(postorder[j]<postorder[end])
            return false;
    }
    return helper(postorder,start,mid-1) && helper(postorder,mid,end-1);
}
bool verifyPostorder(vector<int>& postorder) {
    if(postorder.empty())
        return true;
    return helper(postorder,0,postorder.size()-1);
}

int main() {
    vector<int> A={4, 8, 6, 12, 16, 14, 10};
    cout << verifyPostorder(A) << endl;
    return 0;
}

面试题34. 二叉树中和为某一值的路径

// Created by zlgybz on 2020/5/1.
//面试题34. 二叉树中和为某一值的路径
//输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
//示例:给定如下二叉树,以及目标和 sum = 22,
//              5
//             / \
//            4   8
//           /   / \
//          11  13  4
//         /  \    / \
//        7    2  5   1
//返回:
//[
//[5,4,11,2],
//[5,8,4,5]
//]
//提示:节点总数 <= 10000
#include <iostream>
#include <vector>
#include "BinaryTree.h"

using namespace std;
//或者使用类,将res和vec变成成员变量也可以。
void helper(vector<vector<int>> &res,vector<int> &vec,TreeNode* root, int sum){
    if(!root)//到达了叶子节点的下一层
        return;
    sum -= root->val;
    vec.push_back(root->val);
    if(sum==0&&root->left==nullptr&&root->right==nullptr)//到了叶子节点且满足要求
        res.push_back(vec);
    helper(res,vec,root->left,sum);//执行这一行,直到一个路径的叶子节点然后return,才会再执行下一行
    helper(res,vec,root->right,sum);
    vec.pop_back();//一层一层吐出,吐出的起点是叶子节点下边两个都return,然后到达这一行吐出叶子节点的值,然后往上递推吐出
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
    vector<vector<int>> res;
    vector<int> vec;
    helper(res,vec,root,sum);
    return res;
}
int main() {
    vector<int> A={5,4,8,11,-1,13,4,7,2,-1,-1,-1,-1,5,1};
    auto root = createTree(A);
    auto res = pathSum(root,22);
    PrintTree(root);
    for(auto & re : res){
        for(int j:re)
            cout << j << ' ';
        cout << endl;
    }
    return 0;
}

面试题35. 复杂链表的复制

// Created by zlgybz on 2020/5/1.
//面试题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 。

//思路:关键在于使用哈希表,将复制的节点和原来的节点一一对应,这样才能方便地建立random连接。
//另外还可以用BFS或者DFS等方法。
#include <iostream>
#include <unordered_map>
using namespace std;
class Node {
public:
    int val;
    Node* next;
    Node* random;

    explicit Node(int x) {
        val = x;
        next = nullptr;
        random = nullptr;
    }
};
Node* copyRandomList(Node* head) {
    if(head==nullptr)
        return head;
    unordered_map<Node*,Node*> m;
    //创建所有节点,并储存在哈希表中和原链表一一对应
    for(auto it = head;it!=nullptr;it=it->next){
        m[it] = new Node(it->val);
    }
    //将新建链表每个节点的next和random通过哈希表对应赋值
    for(auto it = head;it!=nullptr;it=it->next){
        m[it]->next = m[it->next];
        m[it]->random = m[it->random];
    }
    return m[head];
}
int main() {

    auto p0 = new Node(7);
    auto p1 = new Node(13);
    auto p2 = new Node(11);
    auto p3 = new Node(10);
    auto p4 = new Node(1);
    p0->next =p1;
    p0->random = nullptr;
    p1->next =p2;
    p1->random = p0;
    p2->next = p3;
    p2->random = p4;
    p3->next =p4;
    p3->random = p2;
    p4->random = p0;
    auto res = copyRandomList(p0);
    auto tmp = res;
    //    0        1      2      3      4
    //[[7,null],[13,0],[11,4],[10,2],[1,0]]
    while(tmp){
        cout << tmp->val << ' ';
        if(tmp->random)
            cout << tmp->random->val;
        else
            cout << "null";
        cout << ',';
        tmp = tmp->next;
    }
    return 0;
}

面试题36. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。

要求不能创建任何新的节点,只能调整树中节点指针的指向

  

/*
// 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:
    void helper(Node* root, Node*& head,Node*& pre){ //递归函数
        if(root==nullptr)//每到叶子节点的下一个,返回
            return;
        //左
        helper(root->left,head,pre);//往左走,进入新一轮左根右递归
        if(head==nullptr){//一直往左走,走到了最左边叶子节点的下一个,返回之后,走到这里
            head = root;//头结点
            pre = root;//暂存,用于和下一个节点连接
        }
        //根
        else{//如果不是最左边叶子节点
            root->left = pre;//当前节点左边是pre
            pre->right = root;//pre的右边是当前节点
            pre = root;//pre后移
        }
        //右
        helper(root->right,head,pre);//往右走,重新进入左根右递归
    }
    Node* treeToDoublyList(Node* root) {
        if(root==nullptr)//特殊输入
            return nullptr;
        Node* head=nullptr,*pre=nullptr;
        helper(root,head,pre);//树的中序 递归函数进行节点连接
        head->left = pre;//头尾节点连接
        pre->right = head;
        return head;
    }
};

面试题37. 序列化二叉树

// Created by zlgybz on 2020/5/1.
//面试题37. 序列化二叉树
//请实现两个函数,分别用来序列化和反序列化二叉树。
//示例:
//你可以将以下二叉树:
//    1
//   / \
//  2   3
//     / \
//    4   5
//序列化为 "[1,2,3,null,null,4,5]"
//测试用法:deserialize(serialize(root))应该与root相同;
//思路:
//serialize:使用队列层序输出树,使用ostringstream更方便的转换成字符串
//deserialize:使用istringstream分离字符串的每个元素。
// 空节点对应空指针,实际节点对应实际节点的指针,存入一个数组。然后在节点之间建立连接关系

#include <iostream>
#include <vector>
#include <c++/sstream>

#include "BinaryTree.h"
using namespace std;

// Encodes a tree to a single string.
string serialize(TreeNode* root) {
    if(!root)
        return "";
    ostringstream res;//使用ostringstream,是为了能将null和数值一起转成字符串
    //使用队列,以层序输出到res中,注意以空格隔开,方便下一个函数读取
    queue<TreeNode*> q;
    q.push(root);
    while(!q.empty()){
        if(q.front()==nullptr){
            res << "null ";
            q.pop();
        }
        else{
            res << q.front()->val << " ";
            q.push(q.front()->left);
            q.push(q.front()->right);
            q.pop();
        }
    }
    return res.str();
}

// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
    if (data.empty())
        return nullptr;
    istringstream input(data);//使用istringstream,是为了能将整套数据挨个读取,因为中间用空格隔开的
    string tmp;
    vector<TreeNode*> vec;//层序存储每个节点的指针,里面也包括了每个叶子节点的下一层节点
    while(input>>tmp){
        if(tmp=="null")//叶子节点的下一节点
            vec.push_back(nullptr);
        else//正常带数值的节点
            vec.push_back(new TreeNode(stoi(tmp)));//stoi 将字符串转换成数值
    }
    //设置每个节点的指向关系
    int j=1;
    for(int i=0;j<vec.size()-1;i++){//遍历vec的每个指针,直到最后一层,剩下的全是叶子节点的下一层时
        if(vec[i]==nullptr) continue;//叶子节点的下一节点,则不做处理
        vec[i]->left = vec[j++];//对应的规律
        vec[i]->right = vec[j++];
    }
    return vec[0];
}

int main() {
    vector<int> vec = {1,2,3,-1,-1,4,5};
    TreeNode* head = createTree(vec);//使用头文件中的函数生成输入二叉树
    PrintTree(head);
    string res = serialize(head);//将二叉树转换成字符串并输出
    cout << res << endl;

    TreeNode* tree = deserialize(res);//将字符串转换成树结构并打印出来
    PrintTree(tree);

    return 0;
}

面试题38. 字符串的排列

解题思路:回溯+剪枝
排列方案数量: 对于一个长度为 n 的字符串(假设字符互不重复),其排列共有 n×(n−1)×(n−2)…×2×1种方案。
排列方案的生成方法: 根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1 位字符( n种情况)、再固定第 2位字符( n−1 种情况)、... 、最后固定第 n 位字符( 1 种情况)。
重复方案与剪枝: 当字符串存在重复字符时,排列方案中也存在重复方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。

// Created by zlgybz on 2020/5/1.
//面试题38. 字符串的排列
//输入一个字符串,打印出该字符串中字符的所有排列。
//你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
//示例:
//输入:s = "abc"
//输出:["abc","acb","bac","bca","cab","cba"]
//限制:
//1 <= s 的长度 <= 8

#include <iostream>
#include <vector>
#include <set>
using namespace std;
void helper(string& s,int start,set<string>& st) {
    if(start==s.size()-1){//终止条件,当回溯进行到最后一个元素时
        st.insert(s);//插入当前的排列
        return;//返回,回到上一层,也就是倒数第二层
    }

    for(int i=start;i<s.size();i++){
        swap(s[start],s[i]);//交换,使s[i]在s[start]位置
        helper(s,start+1,st);//当前情况下,start后移,继续操作余下的元素
        swap(s[start],s[i]);//将元素再交换回来,i++,进入下一个循环,直到交换到最后一个元素,本层结束,返回上一层
    }
}
vector<string> permutation(string s) {
    if(s.empty())//特殊输入
        return {""};
    set<string> st;//集合,用于筛掉重复字符串
    helper(s,0,st);//回溯函数
    return vector<string>(st.begin(),st.end());//返回整个集合
}

int main() {
    string input = "abc";
    auto res = permutation(input);
    for(const string& s:res)
        cout << s << ',';
    return 0;
}


面试题39. 数组中出现次数超过一半的数字

// Created by zlgybz on 2020/5/1.
//面试题39. 数组中出现次数超过一半的数字
//数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
//你可以假设数组是非空的,并且给定的数组总是存在多数元素。
//示例 1:
//输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
//输出: 2
//限制:1 <= 数组长度 <= 50000
//1哈希表记录(当前)
//2排序,找中间位置元素
//3摩尔投票:
//初始化: 票数统计 votes=0 , 众数 x;
//循环抵消: 遍历数组 nums 中的每个数字 num ;
//当 票数 votes 等于 0 ,则假设 下个数字 num 为 众数 x ;
//当 num=x 时,票数 votes 自增 1 ;否则,票数 votes 自减 1 。
//返回值: 返回 众数 x 即可。

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
int majorityElement(vector<int>& nums) {
    int length = nums.size()/2;
    unordered_map<int,int> m;
    for(int i:nums){
        if(m.find(i)!=m.end())
            m[i]++;
        else
            m[i]=1;
        if(m[i]>length)
            return i;
    }
    return 0;
}
int main() {
    vector<int> A={1, 2, 3, 2, 2, 2, 5, 4, 2};
    cout << majorityElement(A) << endl;
    return 0;
}

面试题40. 最小的k个数

// Created by zlgybz on 2020/5/1.
//面试题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

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> getLeastNumbers(vector<int>& arr, int k) {
    vector<int> res;
    sort(arr.begin(),arr.end());
    for(int i=0;i<k;i++)
        res.push_back(arr[i]);
    return res;

}
int main() {
    vector<int> A={3,4,5,1,2};
    auto res = getLeastNumbers(A, 2);
    for(int i:res)
        cout << i << ' ';
    return 0;
}




面试题41. 数据流中的中位数

// Created by zlgybz on 2020/5/1.
//面试题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、findMedia进行 50000 次调用。

//方法一,每进来一个新元素就对数组进行sort排序,时间复杂度较高
//方法二,使用二分查找的lower_bound找到插入的位置,使用insert插入。
//方法三,使用大小堆:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/solution/you-xian-dui-lie-by-z1m/

#include <iostream>
#include <vector>
using namespace std;
class MedianFinder {
public:
    /** initialize your data structure here. */
    MedianFinder() {//构造函数
    }
    void addNum(int num) {//添加元素
        if(store.empty())
            store.push_back(num);
        else{
            //insert,在某个位置插入元素
            //lower_bound()函数,给定有序数组的起点和终点,找到第一个大于等于num的位置索引,其内部使用的是二分法查找
            //upper_bound()函数,它返回大于num的第一个元素的索引
            store.insert(lower_bound(store.begin(),store.end(),num),num);

        }
    }
    double findMedian() {
        int n=store.size();
        return n&1 ? store[n/2] : (store[n/2]+store[n/2-1])*0.5;
    }
    vector<int> store;
};
int main() {
    MedianFinder* obj = new MedianFinder();
    obj->addNum(1);
    obj->addNum(2);
    cout << obj->findMedian() << ' ';
    obj->addNum(3);
    cout << obj->findMedian() << ' ';
    return 0;
}

面试题42. 连续子数组的最大和

// Created by zlgybz on 2020/5/1.
//面试题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
//思路:动态规划,判断是保留前边子序的贡献还是另起一个新子序

#include <iostream>
#include <vector>
using namespace std;
int maxSubArray(vector<int>& nums) {
    if(nums.empty())
        return 0;
    int res = nums[0];
    int res_max=nums[0];
    //动态规划的方法
    for(int i=1;i<nums.size();i++){
        //以当前值结尾的子序的最大值。如果前边的贡献是正的,则加上,否则从从当前值开始另起一个新子序
        res = max(nums[i],res+nums[i]);
        res_max = max(res,res_max);//历史子序的最大值
    }
    return res_max;
}
int main() {
    vector<int> A={-2,1,-3,4,-1,2,1,-5,4};
    cout << maxSubArray(A) << endl;
    return 0;
}

面试题43. 1~n整数中1出现的次数

解析:需要通过找规律来分析。
我们假设高位为high,当前位为cur,低位为low,i代表着需要统计的位置数(1对应个位,10对应十位,100对应百位),则对每一位的个数count有:
cur=0,count = high*i;    高位在0到high-1这high个数中,cur都可以取1,  并且low有从0到i-1 这i种取值
cur=1,count=high*i+low+1 高位在0到high-1的情况与上边相同,当高位取high时,cur还可以取1,此时低位还有从0到low这low+1个取值
cur>1,count=high*i+i  高位在0到high-1的情况与上边相同。高位取high,cur取1时,低位有从0到i-1 这i种取值
最终累加所有位置上的个数即最终答案。

// Created by zlgybz on 2020/5/1.
//面试题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

#include <iostream>
#include <vector>
using namespace std;
int countDigitOne(int n) {
    int res = 0;
    long i = 1;//从各位开始,i=10代表十位,i=100代表百位    1 <= n < 2^31,所以用long(4字节32位) int在16位编译器上为16位
    while(n/i!=0){
        long high = n/(10*i);//将当前位和低位舍掉,得到高位的数字
        int cur = (n/i)%10;//将低位舍掉,再对10取余得到当前位的数字
        long low = n%i;//对i取余,得到低位的数字
        if(cur==0)
            res += high*i;
        else if(cur==1)
            res += high*i + low + 1;
        else
            res += high*i + i ;
        i *= 10;
    }
    return res;
}
int main() {
    cout << countDigitOne(1410065408) << endl;
    return 0;
}


面试题44. 数字序列中某一位的数字

找规律的题

// Created by zlgybz on 2020/5/1.
//面试题44. 数字序列中某一位的数字
//数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
//请写一个函数,求任意第n位对应的数字。
//示例 1:
//输入:n = 3
//输出:3
//示例 2:
//输入:n = 11
//输出:0
//限制:
//0 <= n < 2^31
#include <iostream>
#include <vector>
using namespace std;
int findNthDigit(int n){
    if(n==0)//将第0位的0额外考虑
        return 0;
    //以下每层都考虑是从索引1开始 1-9 9个数   10-99 90个数   100-999 900个数
    int start = 1;//每一层的起点
    int digit = 1;//每一层的一个数的位数
    while(n-9*start*digit>0){ //得到第n位所在的数在哪一层,其起点是start,位数为digit
        n -= 9*start*digit;
        start *= 10;
        digit += 1;
    }
    int num = start + (n-1)/digit;//得到第n位所在的数
    // n进行减一操作,是因为如果刚好是digit的整数倍,意味着是某个数的最后一位。比如n=digit,则n应该是start的最后一位,不用start+1
    string s = to_string(num);
    int i = (n-1)%digit;//在数的哪一位,索引从0开始,所以减一
    return (s[i]-'0');
}
int main() {
    cout << findNthDigit(11) << endl;
    return 0;
}


面试题45. 把数组排成最小的数

思路:使用sort排序,自定义一个排序规则函数。

// Created by zlgybz on 2020/5/1.
//面试题45. 把数组排成最小的数
//输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
//示例 1:
//输入: [10,2]
//输出: "102"
//示例 2:
//输入: [3,30,34,5,9]
//输出: "3033459"
//提示:0 < nums.length <= 100

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//如果不用lambda表达式,需要再定义一个排序规则函数
//bool compare(string a,string b){
//    return(a+b<b+a);
//}
string minNumber(vector<int>& nums) {
    vector<string> tmp;
    for(int i:nums)
        tmp.push_back(to_string(i));
//    lambda表达式方式一
//    auto compare = [](const string& a,const string& b){return(a+b<b+a);};
//    sort(tmp.begin(),tmp.end(),compare);
    //lambda表达式方式二
    sort(tmp.begin(),tmp.end(),[](const string& a,const string& b){return(a+b<b+a);});//排序规则,按照compare都为true排

    string s;
    for(const auto& i:tmp)
        s += i;
    return s;
}

int main() {
    vector<int> A={3,30,34,5,9};
    cout << minNumber(A) << endl;
    return 0;
}

面试题46. 把数字翻译成字符串

// Created by zlgybz on 2020/5/1.
//面试题46. 把数字翻译成字符串
//给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。
//一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
//示例 1:
//输入: 12258
//输出: 5
//解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
//提示:0 <= num < 231
//思路:类似于跳台阶问题,这种寻找所有组合可能性的,大多使用递归回溯方法
#include <iostream>
#include <vector>
using namespace std;
int helper(string s,int pos){ //递归回溯函数 
    if(pos>=s.size()-1)//这里面包括了到达最后一位和最后一位之后的一位
        return 1;
    if(s[pos]=='0'|| s.substr(pos,2) > "25")//只有一种选择,后移一位
        return helper(s,pos+1);
    else //10到25之间,有两种选择
        return helper(s,pos+1)+helper(s,pos+2); //这里pos+2有可能到达最后一位之后,此时返回的是1+1
}
int translateNum(int num) {
    string s = to_string(num);
    return helper(s,0);
}
int main() {
    cout << translateNum(12258) << endl;
    return 0;
}


面试题47. 礼物的最大价值

class Solution {
public:
    //采用这种回溯递归的方法,可以解,但中间会出现很多重复计算,会超时。和上楼梯,斐波那契数列类似
    //如果是在求最大解的同时要得到最优的路径,则可以采用此类方法
    //从前往后的回溯递归
    // int helper(vector<vector<int>>& grid,int row,int col,int i,int j,int res){
    //     res += grid[row][col];
    //     if(row==i&&col==j)
    //         return res;
    //     else if(row==i&&col<j)
    //         return helper(grid,row,col+1,i,j,res); 
    //     else if(row<i&&col==j)
    //         return helper(grid,row+1,col,i,j,res); 
    //     else
    //         return max(helper(grid,row,col+1,i,j,res),helper(grid,row+1,col,i,j,res));
    // }
    //从后往前的回溯递归
    // int helper(vector<vector<int>>& grid,int row,int col){
    // if(row==0&&col==0)
    //     return grid[row][col];
    // else if(row==0&&col>0)
    //     return grid[row][col]+helper(grid,row,col-1); 
    // else if(row>0&&col==0)
    //     return grid[row][col]+helper(grid,row-1,col); 
    // else
    //     return grid[row][col]+max(helper(grid,row,col-1),helper(grid,row-1,col));
    // }
    // int maxValue(vector<vector<int>>& grid) {
    //     int i = grid.size()-1;
    //     int j = grid[0].size()-1;
    //     int tmp = 0;
    //     //int res = helper(grid,0,0,i,j,tmp); 
    //     int res = helper(grid,i,j); 
    //     return res;
    // }

    //时间复杂度更低的方法:使用一个二维数组记录到达每个格子的最大礼物价值
    int maxValue(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        vector<vector<int>> tmp(m,vector<int>(n,0));
        // 将第0行和第0列单独拿出来,可以减少判断,缩短时间
        tmp[0][0]=grid[0][0];
        for(int i=1;i<m;i++)//第0列
            tmp[i][0]=tmp[i-1][0]+grid[i][0];
        for(int i=1;i<n;i++)//第0行
            tmp[0][i]=tmp[0][i-1]+grid[0][i];
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                    tmp[i][j]=max(tmp[i-1][j],tmp[i][j-1])+grid[i][j];
            }
        }
        return tmp[m-1][n-1];
    }
     

    //再优化空间复杂度的话,就是使用一维数组,只记录上一行的每个格子的最大礼物价值,比较难想到
    //当前行当前坐标左边的情况实时更新
    // int maxValue(vector<vector<int>>& grid) {
    //     int r = grid.size(), c = grid[0].size();
    //     vector<int> dp(c+1,0);//左边多定义一个,用于处理第0列的情况
    //     for(int i = 0; i < r; i++)
    //     {
    //         for(int j = 0; j < c; j++)
    //         {
    //             dp[j+1] = max(dp[j],dp[j+1]) + grid[i][j];  
                //dp[j+1]和grid[i][j]对应  等式右边dp[j]代表左边的 dp[j+1]代表上边的
    //         }
    //     }
    //     return dp[c];
    // }


};

面试题48. 最长不含重复字符的子字符串

// Created by zlgybz on 2020/5/1.
//面试题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) {
        unordered_map<char,int> m;
        int left = 0;
        int res = 0;
        for(int right=0;right<s.size();right++){
            if(m.find(s[right])!=m.end()){//哈希表中有该字符
                left = max(left, m[s[right]]+1);//该字符如果在滑动窗口内,则滑动窗口左指针移至上次出现该字符的后一位
                m[s[right]]=right;//更新这个字符的索引位置
            }
            else
                m[s[right]]=right;//加入这个字符的索引位置
            res = max(res,right-left+1);
        }
        return res;
    }
};

面试题49. 丑数

class Solution {
public:
    int nthUglyNumber(int n) {
        int a=0,b=0,c=0;//三个索引的位置
        vector<int> vec(n,0);
        vec[0] = 1;
        for(int i=1;i<n;i++){
            int n2 = vec[a]*2, n3 = vec[b]*3, n5 = vec[c]*5;
            vec[i] = min(min(n2,n3),n5);//取三个的最小值
            cout << vec[i] << ' ';
            //更新abc的值,是为了使其满足约束条件,前一个乘上2/3/5小于等于vec[i],当前乘上2/3/5之后大于vec[i]
            if((vec[i])==n2) a++;
            if ((vec[i])==n3) b++;//不能用else if 因为要每个都判断一次,防止重复元素出现。即保证每个索引满足的条件
            if ((vec[i])==n5) c++;
        }
        return vec[n-1];
    }
};

面试题50. 第一个只出现一次的字符

// Created by zlgybz on 2020/5/1.
//面试题50. 第一个只出现一次的字符
//在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。
//示例:
//s = "abaccdeff"
//返回 "b"
//s = ""
//返回 " "
//限制:
//0 <= s 的长度 <= 50000

#include <iostream>
#include <unordered_map>
using namespace std;
char firstUniqChar(string s) {
    unordered_map<char,int> m; //unordered_map无序,查找更快
    for(char i:s)
        m[i]++;
    for(char i:s){
        if(m[i]==1)
            return i;
    }
    return ' ';
}
int main() {
    cout << firstUniqChar("leetcode") << endl;
    return 0;
}

面试题51. 数组中的逆序对

// Created by zzh on 2020/7/9.
//剑指 Offer 51. 数组中的逆序对
//在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
//输入一个数组,求出这个数组中的逆序对的总数。
//示例 1:
//输入: [7,5,6,4]
//输出: 5
//限制:
//0 <= 数组长度 <= 50000
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
    int reversePairs(vector<int>& nums) {
        //方法一:暴力搜索 超时
        // int cnt = 0;
        // for(int i=0;i<nums.size();i++){
        //     for(int j=i+1;j<nums.size();j++){
        //         if(nums[i]>nums[j])
        //             cnt++;
        //     }
        // }
        // return cnt;

        //方法二:归并排序的过程中记录逆序对的个数
        int n = nums.size();
        vector<int> tmp(n);
        return mergeSort(nums, tmp, 0, n-1);
    }
    int mergeSort(vector<int>& nums, vector<int>& tmp, int left, int right){
        if(left>=right)//递归分到只有一个元素的时候,则直接返回0
            return 0;
        int mid = left + (right-left)/2;
        int inv_count = mergeSort(nums,tmp,left,mid)+mergeSort(nums,tmp,mid+1,right);//先将左右两部分各自的逆序对加起来
        //以下是求解将当前左右两部分归并的时候,新发现的逆序对
        int i = left, j=mid+1, pos = left;
        while(i<=mid && j<=right){//两部分都不为空时
            if(nums[i]<=nums[j]){//如果左边第一个小,说明这个数是当前最小的,对逆序对没有贡献
                tmp[pos++] = nums[i++];
                // inv_count += (j-(mid+1));
            }
            else{//如果右边第一个小,则右边的这个元素与左边现有的元素都构成逆序对
                tmp[pos++] = nums[j++];
                inv_count += mid-(i-1);
            }
        }
        while(i<=mid){ //左边剩余的处理完
            tmp[pos++] = nums[i++];
        }
        while(j<=right){ //右边剩余的处理完
            tmp[pos++] = nums[j++];
        }
        copy(tmp.begin()+left, tmp.begin()+right+1, nums.begin()+left); //将tmp排好序的部分拷贝到nums的对应位置中
        return inv_count;
    }

};

int main(int argc, char **argv) {
    vector<int> nums = {7,5,6,4};
    auto obj = new Solution;
    auto res = obj->reversePairs(nums);
    cout << res << endl;
    return 0;
}

面试题52. 两个链表的第一个公共节点



/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
 //思路:一个链表到尾了与另一个链表的头相接。
 //如果有相交节点,两个指针碰头的位置就是第一个共同节点,
 //如果没有相交,则碰头的位置将是遍历完两个链表时的nullptr
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(!headA || !headB)
            return nullptr;
       ListNode* tmpA = headA;
       ListNode* tmpB = headB;
       while(tmpA!=tmpB){
            tmpA = tmpA==nullptr ? headB : tmpA->next; 
            tmpB = tmpB==nullptr ? headA : tmpB->next; 
       } 
       return tmpA;
    }
};

面试题53 - I. 在排序数组中查找数字

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

总结一下写二分查找法的思路:

    while(left < right) 在退出循环的时候,一定有 left == right 成立,因此,在循环中,不断排除不是目标元素的区间,以确定下一轮搜索的区间,进而确定边界如何设置,在退出循环的时候,根据情况,需要单独做一次判断;
    在循环体里,对 nums[mid] 与 target 的大小关系的判断,可以二分,也可以三分,本题采用三分去判断,只是为了演示怎么设置边界:先得到目标元素可能在哪个区间里,然后再设置边界,这样不容易出错,建议写成注释;可以二分的好处是只要一边判断是正确的,另一边就是其反面,考虑的内容会少一些;
    看到边界是 left = mid 的时候,取中间数的计算表达式应该上取整,即在括号里加 1;
    编码时应该先写主干逻辑,然后分支逻辑作为私有函数。

class Solution {
public:
    
    int search(vector<int>& nums, int target) {
        int cnt = 0;

        //方法一:使用STL算法函数1:
        //lower_bound()函数,给定有序数组的起点和终点,找到第一个大于等于num的位置索引,其内部使用的是二分法查找
        //upper_bound()函数,它返回大于num的第一个元素的索引
        //cnt = upper_bound(nums.begin(),nums.end(),target) - lower_bound(nums.begin(),nums.end(),target);

        // //方法二:”使用STL算法函数2:
        // count       :  在序列中统计某个值出现的次数
        // count_if   :    在序列中统计与某谓词匹配的次数
        //cnt = count(nums.begin(),nums.end(),target);

        //方法三:手写二分法查找起点终点索引
        if(nums.empty())return 0;
        int n=nums.size();
        int left=0,right=n-1,mid;
        int x;
        int y;
        while(left<right){
            mid=(left+right)/2;
            if(nums[mid]>=target)right=mid;
            else left=mid+1;
        }
        if(nums[left]!=target)return 0;
        x=left;
        right=n;
        while(left<right){
            mid=(left+right)/2;
            if(nums[mid]<=target) left=mid+1;
            else right=mid;
        }
        y=left;
        cnt =  y-x;

        return cnt;
    }
};

 

面试题54. 二叉搜索数的第k大节点

给定一棵二叉搜索树,请找出其中第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:
    vector<int> vec;
    void helper(TreeNode* root){
        if(!root)
            return;
        helper(root->right);
        vec.push_back(root->val);
        helper(root->left);

    }
    int kthLargest(TreeNode* root, int k) {
        helper(root);
        return vec[k-1];
    }
};

55-1 二叉树的深度

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

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

55-2平衡二叉树

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

/**
 * 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 helper(TreeNode* root){
    //     if(!root)
    //         return 0;
    //     return max(helper(root->left),helper(root->right))+1;
    // }
    // bool isBalanced(TreeNode* root) {
    //     if(!root)
    //         return true;
    //     if(abs(helper(root->left)-helper(root->right))>1) return false;
    //     return isBalanced(root->left) && isBalanced(root->right);
    // }
    //方法二:后序遍历,左右中,从下往上判断,没有重复计算
    bool helper(TreeNode* root,int& Depth){//使用一个辅助变量Depth用于记录当前节点深度,使用引用使其值可改变和使用
        if(!root){
            Depth = 0;
            return true;
        };
        int left = 0,right = 0;
        if(helper(root->left,left)&&helper(root->right,right)){//如果左右树都满足,则判断当前节点,如果满足则深度加1
            if(abs(left-right)>1)
                return false;
            else{
                Depth = max(left,right)+1;
                return true;
            }
        }
        return false;//左右树有不满足的
        
    }
    bool isBalanced(TreeNode* root) {
        if(!root)
            return true;
        int Depth = 0;
        return helper(root,Depth);
    }

};

56-1 数组中数字出现的次数

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

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int tmp = 0;
        for(int i:nums){//找到这两个数字异或的值
            tmp ^= i;
        }
        int div = 1;
        while((tmp&div)==0)//找到tmp第一个不为0的位,也就是两个数字不用的位
            div <<= 1;
        int a=0,b=0;
        for(int i:nums){//根据那个不同的位将所有数分成两类,然后分别异或
            if(i&div)
                a ^= i; 
            else
                b ^= i;
        }
        return {a,b};
    }
};

56-2 数组中出现的的次数2

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

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(int i=0;i<32;i++){
            int cnt = 0;
            for(int num:nums){
                if(num&1<<i)
                    cnt++;
            }
            res |= (cnt%3==0?0:1<<i);
        }
    return res;       
    }
};

57 和为s的两个数字

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

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int left=0,right = nums.size()-1;
        while(left<right){
            int tmp = nums[left]+nums[right];
            if(tmp==target)
                return {nums[left],nums[right]};
            else if(tmp>target)
                right--;
            else
                left++;
        }
        return {};
    }
};

 

 

 

 

 

 

 

 

头文件

List.h

#ifndef SWORD_TO_OFFER_LIST_H
#define SWORD_TO_OFFER_LIST_H
#include <vector>
using namespace std;
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

//输入vector,生成链表,返回头指针
ListNode* createList(const vector<int>& list){
    auto* res = new ListNode(0);
    ListNode* tmp = res;
    for(int i : list){
        tmp->next = new ListNode(i);
        tmp = tmp->next;
    }
    return res->next;
}
#endif //SWORD_TO_OFFER_LIST_H

BinaryTree.h

// Created by zzh on 2020/4/26.

#ifndef SWORD_TO_OFFER_BINARYTREE_H
#define SWORD_TO_OFFER_BINARYTREE_H

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    explicit TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
///创建二叉树的三种方法/
//1.先创建节点,然后连接各节点
TreeNode* createTreeNode(int val){
    auto res = new TreeNode(val);
    return res;
}
void connectTreeNode(TreeNode* root,TreeNode* left, TreeNode* right){
    root->left = left;
    root->right = right;
}


//2. 可通过int型数组(层序)创建二叉树,(注意这里数组得是满序,所有的空节点都要用-1表示,叶子节点的下一节点不表示)
TreeNode* createTree(const vector<int> &vec){
    vector<TreeNode*> vecTree;
    for(int i:vec){
        if(i==-1) vecTree.push_back(nullptr);
        else vecTree.push_back(new TreeNode(i));
    }
    int j=1;
    for(int i=0;j<vecTree.size();i++){
        if(vecTree[i]==nullptr) continue;//中间层叶子节点的下一层
        vecTree[i]->left = vecTree[j++];
        vecTree[i]->right = vecTree[j++];
    }
    return vecTree[0];
}
//3. 方法二的递归实现方式
void createTreeCore(TreeNode* &root, const vector<int> &vec, int len, int index){
    if(index>=len||vec[index]==-1)
        return;
    root = new TreeNode((vec[index]));
    createTreeCore(root->left,vec,len,2*index+1);//画一个二叉树可发现左右节点和根节点编号的规律
    createTreeCore(root->right,vec,len,2*index+2);
}
TreeNode* createTree2(const vector<int> &vec){
    TreeNode* root = nullptr;
    int len = vec.size();
    createTreeCore(root, vec, len,0);
    return root;
}

///以层序方式输出打印二叉树 使用队列//
void PrintTree(TreeNode* root)
{
    queue<TreeNode*> tmp;
    if (root == nullptr){
        cout << "PringtTree failed: root is nullptr!";
        return;
    }
    else{
        tmp.push(root);
    }
    int cur_level = 1, next_level = 0;//记录每行有多少个节点,用于换行
    while(!tmp.empty()){
        auto front = tmp.front();
        if(front == nullptr){//已经到了叶子节点的下方,打出#号,方便检查
            cout << "# ";
            tmp.pop();
        }
        else{
            cout << front->val << " ";
            tmp.push(front->left);
            tmp.push(front->right);
            next_level += 2;
            tmp.pop();
            }
        cur_level--;
        if(cur_level==0){
            cur_level = next_level;
            next_level = 0;
            cout << endl;
        }
    }
}
#endif //SWORD_TO_OFFER_BINARYTREE_H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值