剑指Offer
- 面试题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. 删除链表的节点
- 面试题21. 调整数组顺序使奇数位于偶数前面
- 面试题22. 链表中倒数第k个节点
- 面试题24. 反转链表
- 面试题25. 合并两个排序的链表
- 面试题26. 树的子结构
- 面试题27. 二叉树的镜像
- 面试题28. 对称的二叉树
- 面试题29. 顺时针打印矩阵
- 面试题30. 包含min函数的栈
- 面试题31. 栈的压入、弹出序列
- 面试题32 - II. 从上到下打印二叉树 II
- 面试题32 - I. 从上到下打印二叉树
- 面试题32 - III. 从上到下打印二叉树 III
- 面试题33. 二叉搜索树的后序遍历序列
- 面试题34. 二叉树中和为某一值的路径
- 面试题35. 复杂链表的复制
- 面试题36. 二叉搜索树与双向链表
- 面试题37. 序列化二叉树
- 面试题38. 字符串的排列
- 面试题39. 数组中出现次数超过一半的数字
- 面试题40. 最小的k个数
- 面试题41. 数据流中的中位数
- 面试题42. 连续子数组的最大和
- 面试题44. 数字序列中某一位的数字
- 面试题45. 把数组排成最小的数
面试题03. 数组中重复的数字
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
时间复杂度O(N),空间复杂度O(1)。
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
if(nums.size()==0){
return NULL;
}
for(int i=0;i<nums.size();i++){
while(nums[i]!=i){
//见下面图片
if(nums[i]==nums[nums[i]]){
return nums[i];
}
int temp=nums[i];
nums[i]=nums[temp];
nums[temp]=temp;
}
}
return -1;
}
};
面试题04. 二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:从右上角开始搜索若大于target则列数往左縮一列,若小于target则行数往下增一行
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(matrix.size()==0){
return false;
}
int n=matrix.size()-1;
int m=matrix[0].size()-1;
int i=0;
while(i<=n&&m>=0){
if(matrix[i][m]>target){
m--;
}
else if(matrix[i][m]<target){
i++;
}
else{
//target==matrix[i][m]
return true;
}
}
return false;
}
};
面试题05. 替换空格
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
思路:先增加字符串长度至于其长度,从后往前依次替换填充的字符
class Solution {
public:
string replaceSpace(string s) {
if(s.size()==0)return s;
int len=s.size();
int count=0;
for(int i=0;i<len;i++){
if(s[i]==' '){
count++;
}
}
int l=2*count+s.size();
int left=s.size()-1;
int right= l-1;
s.append(2*count,0);//增加s的长度
while(left>=0){
if(s[left]==' '){
s[right]='0';
s[right-1]='2';
s[right-2]='%';
right-=3;
}
else{
s[right]=s[left];
right-=1;
}
left--;
}
return s;
}
};
面试题06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
思路:使用栈
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
stack<int>mystack;
ListNode *p=head;
while(p){
mystack.push(p->val);
p=p->next;
}
vector<int> vec;
while(!mystack.empty()){
vec.push_back(mystack.top());
mystack.pop();
}
return vec;
}
};
面试题07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路:使用4个指针分别指向子二叉树的头和尾
/**
* 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 *build(vector<int>&preorder,int a1,int b1,vector<int>&inorder,int a2, int b2){
TreeNode *root=new TreeNode(preorder[a1]);
int i=a2;
while(inorder[i]!=preorder[a1])i++;
int left=i-a2;
int right=b2-i;
if(left>0)root->left=build(preorder,a1+1,a1+left,inorder,a2,i-1);
if(right>0)root->right=build(preorder,a1+left+1,b1,inorder,i+1,b2);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size()==0||inorder.size()==0){
return NULL;
}
return build(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1);
}
};
面试题09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
class CQueue {
public:
CQueue() {}
void appendTail(int value) {
inner.push(value);
}
int deleteHead() {
int temp;
if(outer.empty()){
while(!inner.empty()){
outer.push(inner.top());
inner.pop();
}
}
//这儿是有先后顺序的
if(inner.empty()&&outer.empty()){
return -1;
}
int top=outer.top();
outer.pop();
return top;
}
protected:
stack<int> inner;
stack<int> outer;
};
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
面试题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。
class Solution {
public:
int fib(int n) {
vector<int> res(101,0);
res[0]=0;
res[1]=1;
for(int i=2;i<=n;i++){
res[i]=(res[i-1]+res[i-2])%1000000007;
}
return res[n];
}
};
面试题10- II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
class Solution {
public:
int numWays(int n) {
vector<int> res(101,1);
res.at(1)=1;
res.at(2)=2;
for(int i=3;i<=n;i++){
res.at(i)=(res.at(i-1)+res.at(i-2))%1000000007;
}
return res.at(n);
}
};
面试题11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
思路:见解题思路
class Solution {
public:
int minArray(vector<int>& numbers) {
int i=0;
int j=numbers.size()-1;
int mid=0;
while(i<j){
mid=i+(j-i)/2;
//不能换3个if 原有见下图
if(numbers[mid]>numbers[j]){
i=mid+1;
}
else if(numbers[mid]<numbers[j]){
j=mid;
}
else {
j=j-1;
}
}
return numbers[i];
}
};
面试题12. 矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
class Solution {
public:
bool backtrack(vector<vector<char>>& board,int i,int j,string word,int index){
//越界处理并判断是否相等
if(i<0||i>=size||j<0||j>=sizeY||board[i][j]!=word[index]){
return false;
}
//全部一致时
if(index==word.size()-1){return true;}
// 到这里说明已经board[i][j]=word[index]了
// 为了防止深度遍历的时候,再回来用到这个字符,需要先修改一下
char tmp=board[i][j];
board[i][j]='/';
//上下左右搜索
bool res=backtrack(board,i-1,j,word,index+1)
||backtrack(board,i+1,j,word,index+1)
||backtrack(board,i,j-1,word,index+1)
||backtrack(board,i,j+1,word,index+1);
// 从当前字符深度遍历完后,应该将字符设回原来的值,因为遍历到其他字符时,可能回用到这个字符
board[i][j]=tmp;
return res;
}
bool exist(vector<vector<char>>& board, string word) {
size= board.size();
if(size==0){return false;}
sizeY=board[0].size();
int i=0,j=0,index=0;
for(int i=0;i<size;i++){
for(int j=0;j<sizeY;j++){
if(backtrack(board,i,j,word,0)){
return true;
}
}
}
return false;
}
protected:
int size;
int sizeY;
};
面试题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。请问该机器人能够到达多少个格子?
思路:DFS,满足本题条件则计数符加1,并设置该节点标志为true,同时判断该节点的子节点是否也满足本题条件,进行递归操作。
class Solution {
public:
//计算整数位数和函数
int sum(int x){
int s=0;
while(x){
s+=x%10;
x/=10;
}
return s;
}
//深度优先搜索
void dfs(int m,int n,int k,int i,int j,int &count,vector<vector<bool>>&visit){
//满足位数和小于k
if(sum(i)+sum(j)<=k){
//满足条件的数量+1
count++;
/该格子被访问过
visit[i][j]=true;
//判断向右+1:(i+1, j),在矩阵内,且未被访问过
if(i+1<m&&j<n&&!visit[i+1][j]){
dfs(m,n,k,i+1,j,count,visit);
}
//判断向下+1:(i, j+1),在矩阵内,且未被访问过
if(i<m&&j+1<n&&!visit[i][j+1]){
dfs(m,n,k,i,j+1,count,visit);
}
}
}
int movingCount(int m, int n, int k) {
//初始化标志数组
vector<vector<bool>> visit(m,vector<bool>(n,false));
//计数符
int c=0;
dfs(m,n,k,0,0,c,visit);
return c;
}
};
值得参考的文章:参考答案
面试题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。
思路:DP
class Solution {
public:
int cuttingRope(int n) {
vector<int>dp(n+1,0);
dp[1]=1;
dp[2]=1;
for(int i=3;i<n+1;i++){
for(int j=0;j<=i/2;j++){
//最外层的max是用来不同的j时的最大的乘积
//例如F(4)=max(max(0,0),max(3*1,1*dp[3]),max(2*2,2*dp[2]))
dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j]));
}
}
return dp[n];
}
};
参考地址:参考
面试题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。
思路:贪心算法,详情见书。
class Solution {
public:
int cuttingRope(int n) {
if(n<2){return 1;}
if(n==2){return 1;}
if(n==3){return 2;}
long int sum=1;
while(n>4){
sum*=3;
sum%=1000000007;
n-=3;
}
return (sum*n)%1000000007;;
}
};
面试题15. 二进制中1的个数
请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
思路:位运算 见书。
class Solution {
public:
int hammingWeight(uint32_t n) {
int count =0;
while(n){
count++;
n=n&(n-1);
}
return count;
}
};
面试题16. 数值的整数次方
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
思路:快速幂算法。这里留意的是n为负数的时候,其实是需要将x取倒数。
另外一个坑是n为负数的时候,转为正数的时候会溢出,需要先转为long类型。
class Solution {
public:
double myPow(double x, int n) {
if (n == 0) return 1;
long int N = n;//long 是long int的简称
if (n < 0) {
x = 1/x;
N = -N;
}
double res = 1;
while (N > 0) {
//判断是否为奇数
if ((N & 1) == 1) {
res = res * x;
}
x *= x;
N >>= 1;
}
return res;
}
};
面试题17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
class Solution {
public:
vector<int> printNumbers(int n) {
vector<int>res;
int max=pow(10,n);
for(int i=1;i<max;i++){
res.push_back(i);
}
return res;
}
};
大数解法:
class Solution {
public:
bool Increment(string& number) {
bool isOverflow=false;//检测是否越界
int nLength=number.size();
//进位标志位
int nTakeOver=0;
for(int i=nLength-1;i>=0;i--){
int nSum=number[i]-'0'+nTakeOver;
if(i==nLength-1){
//如果是个位,累加
nSum++;
}
//如果个位遇10进1
if(nSum==10){
//如果在最高位进位则溢出
if(i==0){
isOverflow=true;
}
else{
nTakeOver=1;
number[i]='0';//对个位重新设置值
}
}
else{//没有进位
//设置第i位数字并直接跳出循环
number[i]=nSum+'0';
break;
}
}
return isOverflow;
}
void saveNumber(string number){
string s;
bool isBegin0=true;
string::iterator it=number.begin();
while(it!=number.end()){
//判断高位是否为0例如 0020要越过高位的00然后从2开始输出
if(isBegin0&&*it!='0')isBegin0=false;
if(!isBegin0){s+=*it;}
it++;
}
int num=stoi(s);
res.push_back(num);
}
vector<int> printNumbers(int n) {
if(n<=0){return res;}
string number(n,'0');//创建一个能容纳最大值的字符数组//初始全部设置为0
while(!Increment(number)){
saveNumber(number);
}
return res;
}
protected:
vector<int> res;
};
递归解法
class Solution {
public:
vector<int> printNumbers(int n) {
string temp(n, '0');
for (int i = 0; i <= 9; i++) {
temp[0] = i + '0';
sortedNumber(temp, n-1, 1);
}
return res;
}
void sortedNumber(string& temp, int n, int index) {
if (n < index) {
saveNum(temp);
return;
}
else {
for (int i = 0; i < 10; i++) {
temp[index] = i + '0';
sortedNumber(temp, n, index + 1);
}
}
}
void saveNum(string str) {
auto idx = str.find_first_not_of('0');
if (idx != string::npos) {
res.push_back(stoi(str.substr(idx)));
}
}
private:
vector<int>res;
};
面试题18. 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
思路:很简单一遍过
/**
* 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) {
if(head==NULL){return NULL;}
if(head->val==val){return head->next;}
ListNode *p=head;
ListNode *q= p;
while(p!=NULL&&p->val!=val){q=p;p=p->next;}
if(p==NULL){return head;}
else{
q->next=p->next;
}
return head;
}
};
面试题20. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"及”-1E-16"都表示数值,但"12e"、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
class Solution {
public:
bool judgeP(string s){//判断底数是否合法
bool result=false,point=false;
int len=s.size();
if (len==0)return false;
for (int i=0;i<len;i++){
if(s[i]=='+'||s[i]=='-'){//符号位不在第一位,返回false
if(i!=0){return false;}
}
else if(s[i]=='.'){//有多个小数点,返回false
if(point)return false;
point =true;
}
else if(s[i]<'0'||s[i]>'9'){return false;}//非纯数字,返回false
else {result=true;}
}
return result;
}
bool judgeS(string s){//判断指数是否合法
int res=false;
int len=s.size();
//注意指数不能出现小数点,所以出现除符号位的非纯数字表示指数不合法
if(len==0)return false;
for(int i=0;i<len;i++){
if(s[i]=='+'||s[i]=='-'){
//符号位不在第一位,返回false
if(i!=0)return false;
}
else if(s[i]<'0'||s[i]>'9'){return false;}//非纯数字,返回false
else{res=true;}
}
return res;
}
bool isNumber(string s) {
//1、从首尾寻找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);
if(s.empty())return false;
//2、根据e或者E来划分底数和指数
int e=s.find('e');
int E=s.find('E');
//3、指数为空,判断底数
if((e==string::npos)&&(E==string::npos)) return judgeP(s);
//4、指数不为空,判断底数和指数
else {
if (e==string::npos){return judgeP(s.substr(0,E))&&judgeS(s.substr(E+1));}
else {return judgeP(s.substr(0,e))&&judgeS(s.substr(e+1));}
}
}
};
面试题21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
思路:双指针
class Solution {
public:
vector<int> exchange(vector<int>& nums) {
int len=nums.size();
int begin=0;
int end=len-1;
while(begin<end){
while((begin<end)&&(nums[begin]&1)!=0)begin++;
while((begin<end)&&(nums[end]&1)==0)end--;
if(begin<end){
int temp=nums[begin];
nums[begin]=nums[end];
nums[end]=temp;
}
}
return nums;
}
};
面试题22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
思路:双指针(能独立完成)
/**
* 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) {
//特殊情况处理
if(head==NULL||k==0){return NULL;}
ListNode *p=head;
ListNode *q=head;
int count=k;
while(count){
p=p->next;
count--;
}
if(p==NULL){
//判断是否k为正数第一个数
if(count==0){
return q;
}
//k大于节点数
else{
return NULL;
}
}
while(p!=NULL){p=p->next;q=q->next;}
return q;
}
};
142. 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
方法1:哈希set
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *p1=head;
unordered_set<ListNode*>s;
while(p1!=NULL&&s.count(p1)==0){
s.insert(p1);
p1=p1->next;
}
if(s.count(p1)==1){
return p1;
}
else{
return NULL;
}
}
};
方法2:官方方法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head==NULL||head->next==NULL)return NULL;
ListNode *p1=head;
ListNode *p2=head;
bool flag=false;
while(p2!=NULL&&p2->next!=NULL){
p1=p1->next;
p2=p2->next->next;
if(p1==p2){flag=true;break;}
}
if(flag){
p2=p2->next;
int count=1;
while(p1!=p2){p2=p2->next;count++;}
ListNode *p1=head;
ListNode *p2=head;
while(count){p2=p2->next;count--;}
while(p1!=p2){p1=p1->next;p2=p2->next;}
return p1;
}
else{
return NULL;
}
}
};
面试题24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
思路:定义3个指针暴力解决(一次过)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==NULL)return NULL;
if(head->next==NULL)return head;
ListNode *p=head;
ListNode *temp=head;
ListNode *q=NULL;
while(p!=NULL){
temp=p->next;
p->next=q;
q=p;
p=temp;
}
return q;
}
};
面试题25. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
思路:暴力解决(一次搞定)
/**
* 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) {
if(l1==NULL&&l2==NULL)return NULL;
if(l1==NULL)return l2;
if(l2==NULL)return l1;
ListNode *p1=l1;
ListNode *p2=l2;
ListNode *res= new ListNode(0);
ListNode *temp=res;
while(p1!=NULL&&p2!=NULL){
if(p1->val>=p2->val){
temp->next=p2;
p2=p2->next;
}
else{
temp->next=p1;
p1=p1->next;
}
temp=temp->next;
}
if(p1==NULL)temp->next=p2;
else{
temp->next=p1;
}
return res->next;
}
};
面试题26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
思路:递归
首先要遍历A找出与B根节点一样值的节点R
然后判断树A中以R为根节点的子树是否包含和B一样的结构
class Solution {
public:
bool isSubStructure(TreeNode* A, TreeNode* B) {
bool res = false;
//当TreeA和TreeB都不为零的时候,才进行比较。否则直接返回false
if (A!=NULL && B!=NULL)
{
//如果找到了对应TreeB的根节点的点
if (A->val == B->val)
//以这个根节点为为起点判断是否包含TreeB
res = helper(A, B);
//如果找不到,那么就再去TreeA的左子树当作起点,去判断是否包含TreeB
if (!res)
res = isSubStructure(A->left, B);
//如果还找不到,那么就再去TreeA的右子树当作起点,去判断是否包含TreeB
if (!res)
res = isSubStructure(A->right, B);
}
// 返回结果
return res;
}
bool helper(TreeNode* A, TreeNode* B)
{
//如果TreeB已经遍历完了都能对应的上,返回true
if (B==NULL)
return true;
//如果TreeB还没有遍历完,TreeA却遍历完了。返回false
if (A==NULL)
return false;
//如果其中有一个点没有对应上,返回false
if (A->val != B->val)
return false;
//如果根节点对应的上,那么就分别去子节点里面匹配
return helper(A->left, B->left) && helper(A->right, B->right);
}
};
方法二:
class Solution {
public:
bool helper(TreeNode *Node1,TreeNode *Node2){
if(Node2==NULL)return true;
if(Node1==NULL)return false;
if(Node1->val!=Node2->val)return false;
return helper(Node1->left,Node2->left)&&helper(Node1->right,Node2->right);
}
bool check(TreeNode *A,TreeNode *B){
if(A==NULL)return false;
if(A->val==B->val){
if(helper(A,B)){
return true;
}
}
return check(A->left,B)||check(A->right,B);
}
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(B==NULL)return false;
if(A==NULL)return false;
return check(A,B);
}
};
面试题27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
思路:直接交换节点(看完思路一次过)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void swap(TreeNode *Node){
TreeNode *temp=Node->left;
Node->left=Node->right;
Node->right=temp;
}
void trackback(TreeNode *Node){
if(Node==NULL)return ;
swap(Node);
if(Node->left)trackback(Node->left);
if(Node->right)trackback(Node->right);
}
TreeNode* mirrorTree(TreeNode* root) {
if(root==NULL)return NULL;
trackback(root);
return root;
}
};
面试题28. 对称的二叉树
class Solution {
public:
bool helper(TreeNode *left ,TreeNode *right){
if(left==NULL&&right==NULL)return true;
if(left==NULL||right==NULL)return false;
if(left->val!=right->val)return false;
return helper(left->left,right->right)&&helper(left->right,right->left);
}
bool isSymmetric(TreeNode* root) {
if(root==NULL)return true;
return helper(root,root);
}
};
面试题29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if(matrix.size()==0||matrix[0].size()==0)return {};
vector<int>res;
int top=0,bottom=matrix.size()-1,left=0,right=matrix[0].size()-1;
while(true){
/*此算法模拟顺时针输出的过程,请联想打印过程*/
/*1.top行从左到右遍历*/
for(int i=left;i<=right;i++){
res.push_back(matrix[top][i]);
}
/*top移动至下一行,并进行边界检测*/
top++;
if(top>bottom)break;
/*2.right列从上到下遍历*/
for(int i=top;i<=bottom;i++){
res.push_back(matrix[i][right]);
}
right--;
/*right左移,并进行边界检测*/
if(left>right)break;
/*3.bottom行从右往左遍历*/
for(int i=right;i>=left;i--){
res.push_back(matrix[bottom][i]);
}
bottom--;
/*bottom行上移,并进行边界检测*/
if(bottom<top)break;
*4.left列从下往上遍历*/
for(int i=bottom;i>=top;i--){
res.push_back(matrix[i][left]);
}
left++;
//*left右移,并进行边界检测*/
if(left>right)break;
}
return res;
}
};
面试题30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
思路:书上的思路
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {}
void push(int x) {
s_data.push(x);
if(s_min.size()==0){
s_min.push(x);
}
else{
if(x<s_min.top())s_min.push(x);
else s_min.push(s_min.top());
}
}
void pop() {
if(s_min.size()==0)return;
s_data.pop();
s_min.pop();
}
int top() {
return s_data.top();
}
int min() {
return s_min.top();
}
protected:
stack<int>s_data;
stack<int> s_min;
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->min();
*/
面试题31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
思路:模拟,我们尝试按照 popped 中的顺序模拟一下出栈操作,如果符合则返回 true,否则返回 false。这里用到的贪心法则是如果栈顶元素等于 popped 序列中下一个要 pop 的值,则应立刻将该值 pop 出来。
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> aux;
int n=popped.size();
int j=0;
for(int i=0;i<pushed.size();i++){
aux.push(pushed[i]);
//不加会越界如[1,2,3,4,5] [4,5,3,2,1],最后aux会空,aux.top()为非法
while(!aux.empty()&&aux.top()==popped[j]){
aux.pop();
j++;
}
}
return aux.empty();
}
};
面试题32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
思路:层序遍历(一次过)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if(root==NULL)return {};
vector<vector<int>>res;
vector<int>eachLevel;
queue<TreeNode*> q;
TreeNode *temp;
q.push(root);
int n=0;
while(!q.empty()){
n=q.size();
for(int i=0;i<n;i++){
temp=q.front();
q.pop();
eachLevel.push_back(temp->val);
if(temp->left)q.push(temp->left);
if(temp->right)q.push(temp->right);
}
res.push_back(eachLevel);
eachLevel.resize(0);
}
return res;
}
};
面试题32 - I. 从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
思路:层序遍历(一次过)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> levelOrder(TreeNode* root) {
if(root==NULL)return{};
queue<TreeNode*>q;
vector<int>res;
TreeNode *temp;
q.push(root);
while(!q.empty()){
temp=q.front();
res.push_back(temp->val);
q.pop();
if(temp->left)q.push(temp->left);
if(temp->right)q.push(temp->right);
}
return res;
}
};
面试题32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
思路:奇数插入容器前端
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if(root==NULL)return {};
vector<vector<int>>res;
vector<int>EachLevel;
queue<TreeNode*>q;
TreeNode *temp;
bool flag=false;
q.push(root);
while(!q.empty()){
int len=q.size();
for(int i=0;i<len;i++){
temp=q.front();
q.pop();
if(temp->left)q.push(temp->left);
if(temp->right)q.push(temp->right);
if(flag){
EachLevel.insert(EachLevel.begin(),temp->val);
}
else{
EachLevel.push_back(temp->val);
}
}
flag=!flag;
res.push_back(EachLevel);
EachLevel.resize(0);
}
return res;
}
};
面试题33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
思路:二叉搜索树特征,左子树所有节点的值<根节点的值<右子树节点的值
递归检查根节点是否将序列划分为左右子树
class Solution {
public:
bool bfs(vector<int>& postorder,int begin,int end){
if(begin>=end)return true;//单节点或空节点返回true
int root=postorder[end];//后序遍历序列最后的值为根节点的值
int l=begin;
while(l<end&&postorder[l]<root)l++;//遍历左子树(值小于根),左子树序列post[lo, l);
int r=l;
while(r<end&&postorder[r]>root)r++;//遍历右子树(值大于根),右子树序列post[l, r);
if(r!=end)return false;//若未将post[l, hi)遍历完,则非后序遍历序列 返回false
return bfs(postorder,begin,l-1)&&bfs(postorder,l,end-1);//递归检查左右子树
}
bool verifyPostorder(vector<int>& postorder) {
if(postorder.size()==0)return true;
return bfs(postorder,0,postorder.size()-1);
}
};
面试题34. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
思路:回溯法,(vs studio疯狂测试得到的答案)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void bfs(TreeNode* root, int sum) {
sum += root->val;
if (root->left) {
temp.push_back(root->left->val);
bfs(root->left, sum);
temp.pop_back();
}
if (root->right) {
temp.push_back(root->right->val);
bfs(root->right, sum);
temp.pop_back();
}
if (root->left == NULL && root -> right == NULL) {
if (sum == total) {
res.push_back(temp);
}
}
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
total = sum;
if (root == NULL)return {};
int n = 0;
temp.push_back(root->val);
bfs(root, n);
return res;
}
protected:
vector<vector<int>>res;
vector<int>temp;
int total;
};
面试题35. 复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head==NULL)return NULL;
// Creating a new weaved list of original and copied nodes.
Node *ptr=head;
while(ptr!=NULL){
// Cloned node
Node *cpy=new Node(ptr->val);
// Inserting the cloned node just next to the original node.
// If A->B->C is the original linked list,
// Linked list after weaving cloned nodes would be A->A'->B->B'->C->C'
cpy->next=ptr->next;
ptr->next=cpy;
ptr=cpy->next;
}
ptr=head;
// Now link the random pointers of the new nodes created.
// Iterate the newly created list and use the original nodes' random pointers,
// to assign references to random pointers for cloned nodes.
while(ptr!=NULL){
//因为NULL节点只有一个和其他节点不一样
ptr->next->random=(ptr->random==NULL) ? NULL: ptr->random->next;
ptr=ptr->next->next;
}
// Unweave the linked list to get back the original linked list and the cloned list.
// i.e. A->A'->B->B'->C->C' would be broken to A->B->C and A'->B'->C'
Node *ptrOldList=head;// A->B->C
Node *ptrNewList=head->next;// A'->B'->C'
Node *headNew= head->next;
while(ptrOldList!=NULL){
ptrOldList->next=ptrOldList->next->next;
ptrNewList->next=(ptrNewList->next==NULL)? NULL:ptrNewList->next->next;
ptrOldList=ptrOldList->next;
ptrNewList=ptrNewList->next;
}
return headNew;
}
};
面试题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)return;
helper(root->left,head,pre);
if(!head){
//先找头结点然后head指向头结点如上图里面的1
head=root;
pre=root;
}
else{
pre->right=root;
root->left=pre;
pre=root;
}
helper(root->right,head,pre);
}
Node* treeToDoublyList(Node* root) {
if(root==NULL)return NULL;
Node *head=NULL,*pre=NULL;
helper(root,head,pre);
head->left=pre;
pre->right=head;
return head;
}
};
面试题37. 序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树。
思路:先序遍历二叉树然后重组
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// Encodes a tree to a single string.
void dfs(TreeNode *root,string &res) {
if (root == NULL) {
res += "$,";
return;
}
else {
res += to_string(root->val);
res += ',';
}
dfs(root->left,res);
dfs(root->right,res);
}
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if (root == NULL)return "";
string res = "";
dfs(root,res);
return res.substr(0, res.size() - 1);
}
TreeNode *help(int &i,string &s){
if(s[i]=='$'){
i+=2;
return NULL;
}
int st=i;
while(s[i]!=',')i++;
TreeNode *root = new TreeNode (stoi(s.substr(st,i)));
i++;
root->left =help(i,s);
root->right=help(i,s);
return root;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if(data=="")return NULL;
int st=0;
return help(st,data);
}
}
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
面试题38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
思路:回溯剪枝,
class Solution {
public:
void dfs(string &s, int start){
if(start>=s.size()){
res.push_back(s);
return;
}
for(int i=start;i<s.size();i++){
if(judge(s,start,i))continue;//如果start和i之间有字符等于s[i],则跳过(剪枝操作)
swap(s[start],s[i]);
dfs(s,start+1);
swap(s[start],s[i]);
}
}
bool judge(string &s,int start,int end){
for(int i=start;i<end;i++){
if(s[i]==s[end])return true;
}
return false;
}
vector<string> permutation(string s) {
dfs(s,0);
return res;
}
protected:
vector<string>res;
};
面试题39. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
思路:哈希表法
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int ,int>map;
for(auto x:nums){
map[x]++;
if(map[x]>nums.size()/2)return x;
}
return 0;
}
};
面试题40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
方法二:堆
思路和算法
我们用一个大根堆实时维护数组的前 k 小值。首先将前 k 个数插入大根堆中,随后从第 k+1个数开始遍历,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。在下面的代码中,由于 C++ 语言中的堆(即优先队列)为大根堆,我们可以这么做。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int>vec(k,0);
if(k==0)return vec;
priority_queue <int>Q;
for(int i=0;i<k;i++)Q.push(arr[i]);
for(int i=k;i<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;
}
};
面试题41. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
思路:大顶堆小顶堆,我们将中位数左边的数保存在大顶堆中,右边的数保存在小顶堆中。这样我们可以O(1) 时间内得到中位数
class MedianFinder {
public:
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
high.push(num);//先两边都加num
lowe.push(high.top());
high.pop();
//然后比较2个堆的大小,维护两个堆元素个数
if(high.size()<lowe.size()){
high.push(lowe.top());
lowe.pop();
}
}
double findMedian() {
return high.size()>lowe.size()?double(high.top()):(high.top()+lowe.top())*0.5;
}
protected:
priority_queue<int>high;
priority_queue<int,vector<int>,greater<int>>lowe;
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
面试题42. 连续子数组的最大和
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
思路:动态规划
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size(),0);
dp[0]=nums[0];
int max=dp[0];
for(int i=1;i<nums.size();i++){
if(dp[i-1]>0){
dp[i]=dp[i-1]+nums[i];
}
else{
dp[i]=nums[i];
}
if(max<dp[i]){
max=dp[i];
}
}
return max;
}
};
面试题44. 数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
思路:和书上一样
class Solution {
public:
int findNthDigit(int n) {
if (n < 10)return n;
int base = 1;
//计算n在哪个位数区间,
while (n > 9 * pow(10, base - 1) * base) {
n -= 9 * pow(10, base - 1) * base;
base++;
}
//若输入n为1001,则此时n是812还需减1,
//因为之前少减了一个1 0~9是10个数while循环
//里面第一次循环为n>9而非10所以要在下面减1;
n--;
int mod = n % base;//求对应数字的第几个位
int res = n / base + pow(10, base - 1);
string final = to_string(res);
string total;
total += final[mod];
return stoi(total);
}
};
面试题45. 把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
思路:和书上一样,首先要明白静态成员函数和非静态成员函数的区别,静态成员函数属于类,非静态成员函数属于对象(可以这么理解),非静态成员函数里的参数默认有this指针,但是sort函数里的op,不需要这个隐形参数,所以,把这个函数设置位static成员函数则没有this指针,参数和普通函数是一样的了。
class Solution {
public:
static bool cmp(string &a,string &b){
return a+b<b+a;
}
string minNumber(vector<int>& nums) {
vector<string>str_num;
for(auto x:nums){
str_num.push_back(to_string(x));
}
sort(str_num.begin(),str_num.end(),cmp);
string res="";
for(int i=0;i<str_num.size();i++){
res+=str_num[i];
}
return res;
}
};