2020.2.14
面试题03:数组中重复的数字
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路:hash或vector
方法一:vector
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if(length<=1) return false;
vector<int> numbers_index(length,0);
for(int i=0;i<length;i++){
numbers_index[numbers[i]]++;
if(numbers_index[numbers[i]]>1){
*duplication=numbers[i];
return true;
}
}
return false;
}
};
方法二:hash表
bool duplicate(int numbers[], int length, int* duplication) {
if(length<=0) return false;
int index=0;
map<int,int>m;
for(int i=0;i<length;++i)
{
m[numbers[i]]++;
if(m[numbers[i]]>1) {duplication[0]=numbers[i];return true;}
}
return false;
}
12.15
**面试题4:**题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,
每一列都按照从上到下递增的顺序排序。请完成一个函数,
输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
class Solution {
public:
bool Find(int target, vector<vector<int>> array)
{
int i=0;
int j=array[0].size()-1;
while(i<array.size()&&j>=0)
{
if(array[i][j]==target)
return true;
if(array[i][j]<target)
i++;
else if(array[i][j]>target)
j--;
}
return false;
}
};
12.16
**面试题5:**题目描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。
例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
class Solution {
public:
void replaceSpace(char *str,int length) {
if(str==NULL||length<=0)
{
return;
}
int Olength=0;//原始长度
int Space=0;
int i=0;
while(str[i]!='\0')
{
Olength++;
if(str[i]==' ')
{
Space++;
}
i++;
}
int Nlength=Olength+Space*2;//新长度
if(Nlength>length)
{
return;
}
int PO=Olength;
int PN=Nlength;
while(PO>=0&&PN>PO)
{
if(str[PO]==' ')
{
str[PN--]='0';
str[PN--]='2';
str[PN--]='%';
}
else
{
str[PN--]=str[PO];
}
PO--;
}
}
};
注意:新长度要写约束范围;
是str[PO]==' '而不是str[PO]==''或str[PO]=' '
不能少了else条件,不能写成
```cpp
if(str[PO]==' ')
{
str[PN--]='0';
str[PN--]='2';
str[PN--]='%';
}
str[PN--]=str[PO];
PO--;
因为if else 保证了这两个条件不会每次都执行,只会执行其中的一个,是独立的。
2020.1.2
面试题7:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
struct TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> in) {
int inlen=in.size();
if(inlen==0)
return NULL;
vector<int> left_pre,right_pre,left_in,right_in;
//创建根节点,根节点肯定是前序遍历的第一个数
TreeNode* head=new TreeNode(pre[0]);
//找到中序遍历根节点所在位置,存放于变量gen中
int gen=0;
for(int i=0;i<inlen;i++)
{
if (in[i]==pre[0])
{
gen=i;
break;
}
}
//对于中序遍历,根节点左边的节点位于二叉树的左边,根节点右边的节点位于二叉树的右边
//利用上述这点,对二叉树节点进行归并
for(int i=0;i<gen;i++)
{
left_in.push_back(in[i]);
left_pre.push_back(pre[i+1]);//前序第一个为根节点
}
for(int i=gen+1;i<inlen;i++)
{
right_in.push_back(in[i]);
right_pre.push_back(pre[i]);
}
//和shell排序的思想类似,取出前序和中序遍历根节点左边和右边的子树
//递归,再对其进行上述所有步骤,即再区分子树的左、右子子数,直到叶节点
head->left=reConstructBinaryTree(left_pre,left_in);
head->right=reConstructBinaryTree(right_pre,right_in);
return head;
}
};
2020.1.2
面试题9: 两个栈实现一个队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {
int a;
if (stack2.empty())
{
while(!stack1.empty())
{
a=stack1.top();
stack2.push(a);
stack1.pop();
}
}
a=stack2.top();//栈顶元素已经保存到a中了
stack2.pop();
return a;
}
2020.1.4
面试题10.2 青蛙跳台阶问题
比较倾向于找规律的解法,f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出f(n) = f(n-1) + f(n-2)的规律,
但是为什么会出现这样的规律呢?假设现在6个台阶,我们可以从第5跳一步到6,
这样的话有多少种方案跳到5就有多少种方案跳到6,另外我们也可以从4跳两步跳到6,
跳到4有多少种方案的话,就有多少种方案跳到6,其他的不能从3跳到6什么的啦,
所以最后就是f(6) = f(5) + f(4);这样子也很好理解变态跳台阶的问题了。
a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);
b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)
c.由a\b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2
e.可以发现最终得出的是一个斐波那契数列:
| 1, (n=1)
f(n) = | 2, (n=2)
| f(n-1)+f(n-2) ,(n>2,n为整数)
class Solution {
public:
int jumpFloor(int number) {
if(number<=0)
return 0;
if(number==1)
return 1;
if(number==2)
return 2;
int first=1;
int second=2;
int third=0;
for(int i=3;i<=number;i++)
{
third=first+second;
first=second;
second=third;
}
return third;
}
};
2020.1.4
面试题11 旋转数组的最小数字题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int size=rotateArray.size();
if(size==0)
return 0;
int left=0;
int right=size-1;
int mid=0;
while(rotateArray[left]>=rotateArray[right])
{
if(right-left==1)
{
mid=right;
break;
}
mid=(left+right)/2;
if(rotateArray[mid]>=rotateArray[left])
{
left=mid;
}
else if(rotateArray[mid]<=rotateArray[left])
{
right=mid;
}
if(rotateArray[right]==rotateArray[left]&&rotateArray[mid]>=rotateArray[left])
{
return mid_order(rotateArray,left,right);
}
}
return rotateArray[mid];
}
private:
int mid_order(vector<int> &num,int left,int right){
int result=num[left];
for (int i=left+1;i<=right;i++){
if(num[i]<result){
result=num[i];
}
}
return result;
}
};
**因为牛客网上的环境不能调试啥的,一点点小错误都让人抓狂!真的是细节决定成败,
几个不注意的地方:
1.for 语句里要用分号
2. ‘==’ 而不是‘=’
3. 新定义的函数要定义变量
**
2.20.1.5
面试题14 剪绳子题目描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),
每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?
例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
class Solution {
public:
int cutRope(int number) {
if (number<2)
return 0;
if (number==2)
return 1;
if (number==3)
return 2;
int * products=new int[number];
//products[0]=0;
products[1]=1;
products[2]=2;
products[3]=3;
for(int i=4;i<=number;i++){
int max=0;
for(int j=1;j<=number/2;j++){
int num=products[j]*products[i-j];
if(num>max){
max=num;
}
}
products[i]=max;
}
int MAX=products[number];
delete[] products;
return MAX;
}
};
2020.1.6
面试题16 数值的整数次方题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
class Solution {
public:
double Power(double base, int n) {
int exponent;
if(n>0){
exponent=n;
}
else if(n==0){
return 1;
}
else{//n<0
if(base==0)
throw invalid_argument("Invalid input!");
else
exponent=-n;
}
double res=1;
** //黑色加粗为快速幂
while (exponent!=0){
if((exponent&1)==1){ //为1时将该位代表的乘数累乘到最终结果。
res*=base;
}
base*=base; //右移一次,翻倍一次
exponent>>=1;
}
**
return n>0 ? res:(1/res);
}
};
多了个变量n就不报错了;最后一行,n>=0,但=0时在前面已经说过了,会返回1,所以这里只写>也可以。
2020.1.8
面试题18.2 删除链表中重复的节点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,
重复的结点不保留,返回链表头指针。
例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if (pHead==NULL){
return NULL;
}
ListNode* pre=new ListNode(1); //新建一个头结点,括号里面任何数字都可以(分配一个数的大小)
ListNode* p=pre;
ListNode* q=pHead;
pre->next=pHead;
while(q!=NULL && q->next!=NULL){
int i=0;
while(q->next->val==q->val && q->next!=NULL){
q=q->next;
i++;
}
if(i==0)
p=q;//保证第一个指针向右移
else
p->next=q->next; //如果重复,直接跳掉q值相同的后面
q=q->next;
}
return pre->next;
}
};
面试题21:调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
方法1:用一个新数组保存
class Solution {
public: //评论里说此方法有点low!
void reOrderArray(vector<int> &array) {
vector<int> res;
for(int i = 0; i < array.size(); i++)
{
if(array[i] % 2 == 1)
res.push_back(array[i]);
}
for(int i = 0; i < array.size(); i++)
{
if(array[i] % 2 == 0)
res.push_back(array[i]);
}
//array.swap(res);
array = res;
}
};
方法2:用2个指针 这个是按书上思路写的,后来发现运行错误:
您的代码已保存
答案错误:您提交的程序没有通过所有的测试用例点击对比用例标准输出与你的输出
case通过率为0.00%
用例:
[1,2,3,4,5,6,7]
对应输出应该为:
[1,3,5,7,2,4,6]
你的输出为:
[1,7,3,5,4,6,2] 因为题目还要求奇数或偶数的相对位置不变,而我的仅仅交换位置,没有保证相对位置不变
class Solution {
public:
void reOrderArray(vector<int> &array) {
int size = array.size();
vector<int>::iterator pBegin = array.begin();
vector<int>::iterator pEnd = array.end();
if (size==0)
return ;
while(pBegin<pEnd){
while(*pBegin%2==1)
pBegin++;
while(*pEnd%2==0)
pEnd--;
if(pBegin<pEnd){
int temp=*pBegin;
*pBegin=*pEnd;
*pEnd=temp;
}
}
}
};
方法3:插入算法思想,先把当前的数保存,整体后移。。。妙啊
class Solution {
public:
//插入排序思想
void reOrderArray(vector<int> &array) {
int size=array.size();
for(int i=0;i<size;i++){
int target = array[i];
if(array[i] % 2 == 1){
int j = i;
while(j >= 1 && array[j-1] % 2 == 0){
array[j] = array[j-1];
j--;
}
array[j] = target;
}
}
}
};
2020.1.10
面试题22:链表中倒数第k个节点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
思路:用两个相继的指针。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if (pListHead==NULL || k==0){
return NULL;
}
ListNode* pA=pListHead;
ListNode* pB=NULL;
for( int i=1;i<k;i++){ //刚开始pA已经指向了头结点,走k-1步就行了
if(pA->next!=NULL){
pA=pA->next;
}
else{
return NULL;
}
}
pB=pListHead;
while(pA->next!=NULL){
pA=pA->next;
pB=pB->next;
}
return pB;
}
};
2020.1.10
面试题24:翻转链表
题目描述
输入一个链表,反转链表后,输出新链表的表头。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* pReverHead=NULL;
ListNode* pNode=pHead;
ListNode* pPrev=NULL;
while(pNode!=NULL){
ListNode* pNext=pNode->next;
if(pNext==NULL){
pReverHead=pNode;
}
pNode->next=pPrev;
pPrev=pNode;
pNode=pNext;
}
return pReverHead;
}
};
** 重点理解:
1->2->3->4->5->null
翻转后是
5->4->3->2->1->null
其实每个链表后面的null我们是看不到的,所以它就不算一个结点。
**
** 例:
1->2
刚开始pNode->1
pr->pNode(实质上是pr通过pNode指向了pNode指向的结点)
现在pNode移动了,指向2
问?pr指向谁?
答:pr指向1
**
2020.1.11
面试题25:合并两个排序的链表
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:非递归,添加一个新结点 思路2:递归(感觉递归好难啊),见书P147
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1==NULL&&pHead2!=NULL){
return pHead2;
}
if(pHead2==NULL&&pHead1!=NULL){
return pHead1;
}
if(pHead2==NULL&&pHead1==NULL){
return NULL;
}
ListNode* head=new ListNode(1);
head->next=NULL;
ListNode* root=head;
while(pHead1!=NULL&&pHead2!=NULL){
if(pHead1->val<pHead2->val){
head->next=pHead1;
head=head->next;
pHead1=pHead1->next;
}
else{
head->next=pHead2;
head=head->next;
pHead2=pHead2->next;
}
}
if(pHead1==NULL){
head->next=pHead2;
}
if(pHead2==NULL){
head->next=pHead1;
}
return root->next;
}
};
面试题26:树的子结构
题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
递归好难啊,递归得专门训练啊!
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool result=false;
if(pRoot1!=nullptr&&pRoot2!=nullptr){
if(pRoot1->val==pRoot2->val){
result=Dtree(pRoot1,pRoot2);
}
if(!result){
result=HasSubtree(pRoot1->left,pRoot2);
}
if(!result){
result=HasSubtree(pRoot1->right,pRoot2);
}
}
return result;
}
bool Dtree(TreeNode* pRoot1,TreeNode* pRoot2){
if(pRoot2==nullptr)
return true;
if(pRoot1==nullptr)
return false;
if(pRoot1->val!=pRoot2->val)
return false;
return Dtree(pRoot1->left,pRoot2->left)&&Dtree(pRoot1->right,pRoot2->right);
}
};
2020.1.11
面试题27:二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
二叉树的镜像定义:源二叉树
8
/
6 10
/ \ /
5 7 9 11
镜像二叉树
8
/
10 6
/ \ /
11 9 7 5
方法1:递归
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if (pRoot == NULL)return;
if (pRoot->left==NULL&&pRoot->right==NULL) return;
swap(pRoot->left, pRoot->right);
Mirror(pRoot->left);
Mirror(pRoot->right);
}
};
方法2:栈的非递归 没看懂啥意思,好像看懂了^^
class Solution {
public:
//栈的非递归
void Mirror(TreeNode *pRoot) {
if (pRoot == NULL)return;
stack<TreeNode*> st;
TreeNode* p = NULL;
st.push(pRoot);
while (st.size())
{
p = st.top();
st.pop();
swap(p->left, p->right);
if (p->left)st.push(p->left);
if (p->right)st.push(p->right);
}
}
面试题28:对称二叉树
题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:递归,好难啊
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
return isSymmetrical(pRoot,pRoot);
}
bool isSymmetrical(TreeNode* pRoot1,TreeNode* pRoot2)
{
if(pRoot1==NULL&&pRoot2==NULL)
return true;
if(pRoot1==NULL || pRoot2==NULL)
return false;
if(pRoot1->val!=pRoot2->val)
return false;
return isSymmetrical(pRoot1->left,pRoot2->right) && isSymmetrical(pRoot1->right,pRoot2->left);
}
};
2020.1.12
面试题29:顺时针打印矩阵
题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
参考牛客网的,这个我还能理解。
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
int row = matrix.size();
int col = matrix[0].size();
vector<int> res;
// 输入的二维数组非法,返回空的数组
if (row == 0 || col == 0) return res;
// 定义四个关键变量,表示左上和右下的打印范围
int left = 0, top = 0, right = col - 1, bottom = row - 1;
while (left <= right && top <= bottom)
{
// left to right
for (int i = left; i <= right; ++i) res.push_back(matrix[top][i]);
// top to bottom
for (int i = top + 1; i <= bottom; ++i) res.push_back(matrix[i][right]);
// right to left
if (top != bottom)
for (int i = right - 1; i >= left; --i) res.push_back(matrix[bottom][i]);
// bottom to top
if (left != right)
for (int i = bottom - 1; i > top; --i) res.push_back(matrix[i][left]);
left++,top++,right--,bottom--;
}
return res;
}
};
2020.1.12
面试题30:包含min函数的栈
题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
思路:辅助栈。
class Solution {
public:
void push(int value) {
s.push(value);
if(sMin.empty()){
sMin.push(value);
}
else if(value<=sMin.top()){
sMin.push(value);
}
else{
sMin.push(sMin.top());
}
}
void pop() {
// if(s.top()==sMin.top()){
sMin.pop();
// }
s.pop();
}
int top() {
return s.top();
}
int min() {
return sMin.top();
}
private:
stack<int> s;
stack<int> sMin;
};
2020.1.12
面试题31:栈的压入、弹出序列
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路:辅助栈。
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
if(pushV.empty()||popV.empty()||pushV.size()!=popV.size())
return false;
stack<int> s;
int j=0;
for(int i=0;i<pushV.size();i++){
s.push(pushV[i]);
while(!s.empty()&&s.top()==popV[j]){
s.pop();
j++;
}
}
if(s.empty()){
return true;
}
else{
return false;
}
}
};
2020.1.12
面试题32:从上到下打印二叉树
题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路:借助队列。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> res;//保存要打印的数。
if(root==NULL)
return res;
queue<TreeNode*> q; //新建一个队列。
q.push(root);
while(!q.empty()){
res.push_back(q.front()->val);
if(q.front()->left!=NULL)
q.push(q.front()->left);
if(q.front()->right!=NULL)
q.push(q.front()->right);
q.pop(); //弹出第一个元素。
}
return res;
}
};
2020.1.13
面试题33:二叉搜索树的后续遍历序列
题目描述 对于难度 ☆☆☆☆☆
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
递归加油! 看着书上的理解的,然而形参的个数少一个,我却没有想到用一个vector保存,唉。
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
if(sequence.empty())
return false;
int length=sequence.size();
int root=sequence[length-1];
//找左子树
int i=0;
for(;i<length-1;++i)
{
if(sequence[i]>root)
break;
}
//找右子树
int j=i;
for(;j<length-1;++j)
{
if(sequence[j]<root)
return false;
}
//判断左子树是不是二叉搜索树
bool left=true;
vector<int> sequence1;
for(int m=0;m<i;++m)
{
sequence1.push_back(sequence[m]);
}
if(i>0)
left=VerifySquenceOfBST(sequence1);
//判断右子树是不是二叉搜索树
bool right=true;
vector<int> sequence2;
for(int m=0;m<length-i-1;++m)
{
sequence2.push_back(sequence[m+i]);
}
if(i<length-1)
right=VerifySquenceOfBST(sequence2);
return (left&&right);
}
};
2020.1.29
面试题34:二叉树中和为某一值的路径
题目描述
输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路:递归,在纸上画出来才理解。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
find(root, expectNumber);
return res;
}
vector<vector<int>> res;
vector<int> path;
void find(TreeNode* root, int sum)
{
if (root == NULL)return;
path.push_back(root->val);
if (!root->left && !root->right && sum == root->val)//!root->left && !root->right 两个条件是保证更新到了最下面的叶结点。
res.push_back(path); //所有的一下全放进去
else
{
if (root->left)
find(root->left, sum - root->val);
if (root->right)
find(root->right, sum - root->val);
}
path.pop_back();
}
};
2020.1.30
面试题35:复杂链表的复制
题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路
三步法
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
//复制原始链表的任一节点N并创建新节点N',再把N'链接到N的后边
void CloneNodes(RandomListNode* pHead)
{
RandomListNode* pNode=pHead;
while(pNode!=NULL)
{
RandomListNode* pCloned=new RandomListNode(0);
pCloned->label=pNode->label;
pCloned->next=pNode->next;
pCloned->random=NULL;
pNode->next=pCloned;
pNode=pCloned->next;
}
}
//如果原始链表上的节点N的random指向S,则对应的复制节点N'的random指向S的下一个节点S'
void ConnectRandomNodes(RandomListNode* pHead)
{
RandomListNode* pNode=pHead;
while(pNode!=NULL)
{
RandomListNode* pCloned=pNode->next;
if(pNode->random!=NULL)
pCloned->random=pNode->random->next;
pNode=pCloned->next;
}
}
//把得到的链表拆成两个链表,奇数位置上的结点组成原始链表,偶数位置上的结点组成复制出来的链表
RandomListNode* ReConnectNodes(RandomListNode* pHead)
{
RandomListNode* pNode=pHead;
RandomListNode* pClonedHead=NULL;
RandomListNode* pClonedNode=NULL;
//初始化
if(pNode!=NULL)
{
pClonedHead=pClonedNode=pNode->next;
pNode->next=pClonedNode->next;
pNode=pNode->next;
}
//循环
while(pNode!=NULL)
{
pClonedNode->next=pNode->next;
pClonedNode=pClonedNode->next;
pNode->next=pClonedNode->next;
pNode=pNode->next;
}
return pClonedHead;
}
//三步合一
RandomListNode* Clone(RandomListNode* pHead)
{
CloneNodes(pHead);
ConnectRandomNodes(pHead);
return ReConnectNodes(pHead);
}
};
2020.1.30
面试题36
题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:先中序遍历,再用2个for循环摆好指针的位置。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode* Convert(TreeNode* pRoot)
{
if (nullptr == pRoot){
return nullptr;
}
tranverse(pRoot);
return adjustTree();
}
vector<TreeNode*> nodes;
void tranverse(TreeNode* pRoot) {
if (nullptr == pRoot)
return;
tranverse(pRoot->left);
nodes.push_back(pRoot);
tranverse(pRoot->right);
}
TreeNode* adjustTree() {
for (int i = 0; i < nodes.size() - 1; ++i)
nodes[i]->right = nodes[i+1]; //因为一个结点只有左指针和右指针而没有next指针。
nodes[nodes.size()-1]->right = nullptr;
for (int i = nodes.size() - 1; i > 0; --i)
nodes[i]->left = nodes[i-1];
nodes[0]->left = nullptr;
return nodes[0];
}
};
面试题37
题目描述
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树
感觉有点难,看了半天,理解了大概,后来终于理解了,加油。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
//序列化
string sHelper(TreeNode *node)
{
if (node == NULL)
return "N";
return to_string(node->val) + "," +
sHelper(node->left) + "," +
sHelper(node->right);
}
char* Serialize(TreeNode *root)
{
string s = sHelper(root);
char *ret = new char[s.length() + 1];
strcpy(ret, const_cast<char*>(s.c_str()));
return ret;
}
//反序列化
TreeNode *dHelper(stringstream &ss)
{
string str;
getline(ss, str, ',');
if (str == "N")
return NULL;
else
{
TreeNode *node = new TreeNode(stoi(str));
node->left = dHelper(ss);
node->right = dHelper(ss);
return node;
}
}
TreeNode* Deserialize(char *str) {
stringstream ss(str);
return dHelper(ss);
}
};
2020.1.31
面试题38
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
思路:1、调用next_permutation函数;
2、递归;3、全排列。
后两种还没好好理解,等第二遍再理解。
方法1:调用函数
class Solution {
public:
vector<string> Permutation(string str) {
if (str.empty()) return {};
sort(str.begin(), str.end());
vector<string> ans;
ans.push_back(str);
while (next_permutation(str.begin(), str.end()))
ans.push_back(str);
return ans;
}
};
2020.1.31
面试题39
题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
**思路一:**数组排序后,如果符合条件的数存在,则一定是数组中间那个数。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)
这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty()) return 0;
sort(numbers.begin(),numbers.end()); // 排序,取数组中间那个数
int middle = numbers[numbers.size()/2];
//虽然中间数取出来了,但不一定超过数组长度的一半,得返回0。比如122223345,返回0
int count=0; // 出现次数
for(int i=0;i<numbers.size();++i)
{
if(numbers[i]==middle) ++count;
}
return (count>numbers.size()/2) ? middle : 0;
}
};
**思路二:**如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。
在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
int n = numbers.size();
if (n == 0) return 0;
int num = numbers[0], count = 1;
for (int i = 1; i < n; i++) {
if (numbers[i] == num) count++;
else count--;
if (count == 0) {
num = numbers[i];
count = 1;
}
}
int times = 0;
for(int i=0;i<n;++i)
{
if(numbers[i] == num) ++times;
}
return (times > numbers.size()/2) ? num : 0;
}
};
面试题40
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路:全排列(最无脑的方法!)
以后慢慢研究其它好的方法!
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
if(input.empty()||k>input.size())
return res;
sort(input.begin(),input.end());
for(int i=0;i<k;i++){
res.push_back(input[i]);
}
return res;
}
};
-----------------------------------------------第40几道题也太难了吧,看不进去,先做后面的,果然有简单的!-------------------
2020.1.31晚
面试题54: 二叉搜索树的第k大节点
题目描述
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot==NULL||k<=0) return NULL;
vector<TreeNode*> vec;
Inorder(pRoot,vec);
if(k>vec.size())
return NULL;
return vec[k-1];
}
//中序遍历,将节点依次压入vector中
void Inorder(TreeNode* pRoot,vector<TreeNode*>& vec)
{
if(pRoot==NULL) return;
Inorder(pRoot->left,vec);
vec.push_back(pRoot);
Inorder(pRoot->right,vec);
}
};
2020.1.31晚22:53
面试题55 二叉树深度
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot==NULL){
return 0;
}
int nleft=TreeDepth(pRoot->left);
int nright=TreeDepth(pRoot->right);
return (nleft>nright)? (nleft+1):(nright+1);
}
};
------------------终于刷了5道题,明天加油!---------------
2020.2.1 新的2月,加油!
面试题55题目二:平衡二叉树
思路:后序遍历,左右根
class Solution {
public:
bool IsBalanced_Solution(TreeNode* pRoot) {
int depth=0;
return IsBalanced(pRoot,depth);
}
bool IsBalanced(TreeNode* pRoot,int &depth){
if(pRoot==NULL)
return true;
int left=0;
int right=0;
if(IsBalanced(pRoot->left,left) && IsBalanced(pRoot->right,right)){
int diff=left-right;
if(diff<=1 && diff>=-1){
depth=(left>right? left:right)+1;
return true;
}
}
return false;
}
};
2020.2.1
面试题56题目一:数组中指出现一次的数字
题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
int length=data.size();
if(length<2){
return;
}
int Bresult=0;
for(int i=0;i<length;i++){
Bresult^=data[i];
}
int index=findFirst(Bresult);
for(int i=0;i<length;i++){
if(isBit(data[i],index))
*num1^=data[i];
else
*num2^=data[i];
}
}
bool isBit(int target,int index){
return((target>>index)&1)==0;
}
int findFirst(int Bresult){
int index=0;
while((Bresult & 1)==0){
Bresult>>=1;
index++;
}
return index;
}
};
2020.2.1
面试题57:和为s的数字
题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
不要被题目误导了!证明如下,清晰明了:
//输出两个数的乘积最小的。这句话的理解?
假设:若b>a,且存在,
a + b = s;
(a - m ) + (b + m) = s
则:(a - m )(b + m)=ab - (b-a)m - m*m < ab;说明外层的乘积更小
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> result;
int size=array.size();
int left=0;
int right=size-1;
while(right>left){
if(array[left]+array[right]==sum){
result.push_back(array[left]);
result.push_back(array[right]);
break;
}
else if(array[left]+array[right]<sum){
left++;
}
else{
right--;
}
}
return result;
}
};
下面看到这个答案也挺好的,不用证明啥的:
//双指针
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> ret;
int i = 0, j = array.size()-1;
while(i < j){
if(array[i] + array[j] == sum){
if(ret.empty()){
ret.push_back(array[i]);
ret.push_back(array[j]);
}
else if(array[i]*array[j] < ret[0]*ret[1]){
ret[0] = array[i];
ret[1] = array[j];
}
++i;
--j;
}
else if(array[i] + array[j] < sum) ++i;
else --j;
}
return ret;
}
};
2020.2.2
面试题57题目二:和为s的连续正数序列
思路:双指针
vector<vector> Print;刚开始写成了vector Print; 这是不对的。因为Print 里面也是为了放vector。
class Solution {
public:
vector<vector<int> > FindContinuousSequence(int sum) {
vector<vector<int>> Print;
int small=1;
int big=2;
while(big>small){
int result=(big-small+1)*(small+big)/2; //等差数列公式
if(result<sum)
big++;
else if(result>sum)
small++;
else //result==sum
{
vector<int> res;
for(int i=small;i<=big;i++)
res.push_back(i);
Print.push_back(res);
small++;
}
}
return Print;
}
};
2020.2.2
面试题58题目一:翻转单词顺序
思路:用两个指针,先翻转整个句子,再翻转句子中的每个单词。
遇到的问题,注意end++和++end的区别 :
end--;
reverse(str,begin,end);
这两句话合并后应该是reverse(str,begin,--end);
而不是reverse(str,begin,end--);
,因为要先减了之后才送入函数。
class Solution {
public:
//先翻转整个句子,再翻转句子中的每个单词
string ReverseSentence(string str) {
if(str.empty())//如果是指针,才说指针不为null
return str;
int length=str.length();//
reverse(str,0,length-1);
int begin=0;
int end=0;
while(str[begin]!='\0'){
if(str[begin]==' '){
begin++;
end++;
}
else if(str[end]==' '||str[end]=='\0'){
end--;
reverse(str,begin,end);
end++;
begin=end;
}
else
end++;
}
return str;
}
void reverse(string & str,int begin,int end){
while(begin<end)
swap(str[begin++],str[end--]);
}
};
2020.2.2
面试题58:左旋转字符串
方法1:
class Solution {
public:
string LeftRotateString(string str, int n) {
if(n<0) return NULL;
if(n==0) return str;
//string s1=str.substr(0,n);//添加0-n;
string s1(str,0,n);
str.erase(0,n);//删除0-n;
str=str+s1;
return str;
}
};
方法2:
string LeftRotateString(string str, int n){
int len=str.length();
if(str.empty()||n<=0)
return str; //假设输入str为abcXYZdef,n=3
Reverse(str,0,n-1); //反转前n个字符,得到cbaXYZdef
Reverse(str,n,len-1); //反转第n个字符后面所有的字符cbafedZYX
Reverse(str,0,len-1); //反转整个字符串XYZdefabc
return str;
}
//交换函数
void Reverse(string &str,int begin,int end)
{
int temp;
while(begin<end)
{
temp=str[begin];
str[begin]=str[end];
str[end]=temp;
begin++,end--;
}
}
2020.2.3
面试题59队列最大值。题目一:滑动窗口最大值
方法一:书上p291,似懂非懂。
方法二:一个i指示每个窗口的起点,在i循环内j遍历该窗口找出最大值。
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
int len = num.size();
vector<int> result;
if(len < size||size==0)
return result;
for(int i=0; i<=len-size; i++)
{
int max = num[i];
for(int j = i; j< i+size;j++)
{
if(num[j]>max)
max = num[j];
}
result.push_back(max);
}
return result;
}
};
2020.2.4上午
面试题61:扑克中的顺子
题目描述
一副扑克牌,里面有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
class Solution {
public:
bool IsContinuous( vector<int> numbers ) {
int size=numbers.size();
if(size<=0)return false;
sort(numbers.begin(),numbers.end());
int numof0=0;
int kong=0;
for(int i=0;i<size;i++)
{
if(numbers[i]==0)numof0++;
}
for(int i=numof0+1;i<size;i++)
{
if(numbers[i]==numbers[i-1])return false;
else kong+=numbers[i]-numbers[i-1]-1;
}
if(numof0>=kong)return true;
return false;
}
};
面试题62:圆圈中最后剩下的数字
太难了,按程序的步骤下来没有走通
题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n<1||m<1)
return -1;
list<int> numbers;
for(int i=0;i<n;i++)
numbers.push_back(i);
list<int>::iterator current=numbers.begin();
while(numbers.size()>1)
{
for(int i=1;i<m;i++)//走m-1步到达第m个数处
{
++current;
if(current==numbers.end())
current=numbers.begin();
}
list<int>::iterator next=++current;
if(next==numbers.end())
next=numbers.begin();
--current;
numbers.erase(current);
current=next;
}
return *current;//对迭代器取值,等价于对指针取值
}
};
2020.2.4
面试题:求1+2+3+……n
思路:&&短路运算
区别:
1意思不同: &&是“与”的意思,||是“或者”的意思。
2 使用上不同:a && b:a和b同时为true 才返回 true, 否则返回false;a || b:a或b任意一个为true 就返回true , 否则返回false
3 两者都表示运算,但是&&运算符第一个表达式不成立的话,后面的表达式不运算,直接返回。而&对所有表达式都得判断。
class Solution {
public:
int Sum_Solution(int n) {
int ans = n;
ans && (ans += Sum_Solution(n - 1));
return ans;
}
};
还有其他思路。。。以后做。
2020.2.4
面试题65:不用加减乘除做加法
13+11 = ?;
13 的二进制 1 1 0 1 -----a 13
11 的二进制 1 0 1 1 -----b 11
(a&b) <<1 -> 1 0 0 1 0 -----d 18
a^b -> 0 1 1 0 -----e 6
(d&e) <<1 -> 0 0 1 0 0 ------f 4
d^e -> 1 0 1 0 0 -----g 20
(f&g) <<1 -> 0 1 0 0 0 ------h 8
f^g -> 1 0 0 0 0 ------i 16
(h&i) <<1 -> 0 0 0 0 0 ------h 0 ---- --------退出循环
h^i -> 1 1 0 0 0 ------i 24
class Solution {
public:
int Add(int num1, int num2)
{
while (num2!=0) {
int a = num1^num2;
int b = (num1&num2)<<1;
num1 = a;
num2 = b;
}
return num1;
}
};
2020.2.5
面试题49:丑数
题目描述
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
**方法1:**暴力解法,思路简单,运行超时!
class Solution {
public:
//方法1: 运行超时
int GetUglyNumber_Solution(int index) {
int num=0;
int uglyNum=0;
while(uglyNum<index){
++num;
if(IsUgly(num)){
++uglyNum;
}
}
return num;
}
bool IsUgly(int num){
while(num%2==0)
num/=2;
while(num%3==0)
num/=3;
while(num%5==0)
num/=5;
return (num==1)? true:false;
}
};
方法2:空间换时间!
通俗易懂的解释:
首先从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,我们发现这种方***得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:
(1)丑数数组: 1
乘以2的队列:2
乘以3的队列:3
乘以5的队列:5
选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(2)丑数数组:1,2
乘以2的队列:4
乘以3的队列:3,6
乘以5的队列:5,10
选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(3)丑数数组:1,2,3
乘以2的队列:4,6
乘以3的队列:6,9
乘以5的队列:5,10,15
选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(4)丑数数组:1,2,3,4
乘以2的队列:6,8
乘以3的队列:6,9,12
乘以5的队列:5,10,15,20
选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(5)丑数数组:1,2,3,4,5
乘以2的队列:6,8,10,
乘以3的队列:6,9,12,15
乘以5的队列:10,15,20,25
选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;
……………………
疑问:
1.为什么分三个队列?
丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的;
2.为什么比较三个队列头部最小的数放入丑数数组?
因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。
实现思路:
我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,arr表示丑数数组;
(1)1
|2
|3
|5
目前指针指向0,0,0,队列头arr[0] * 2 = 2, arr[0] * 3 = 3, arr[0] * 5 = 5
(2)1 2
2 |4
|3 6
|5 10
目前指针指向1,0,0,队列头arr[1] * 2 = 4, arr[0] * 3 = 3, arr[0] * 5 = 5
(3)1 2 3
2| 4 6
3 |6 9
|5 10 15
目前指针指向1,1,0,队列头arr[1] * 2 = 4, arr[1] * 3 = 6, arr[0] * 5 = 5
………………
class Solution {
public:
int GetUglyNumber_Solution(int index) {
// 0-6的丑数分别为0-6
if(index < 7) return index;
//p2,p3,p5分别为三个队列的指针,newNum为从队列头选出来的最小数
int p2 = 0, p3 = 0, p5 = 0, newNum = 1;
vector<int> arr;
arr.push_back(newNum);
while(arr.size() < index) {
//选出三个队列头最小的数
newNum = min(arr[p2] * 2, min(arr[p3] * 3, arr[p5] * 5));
//这三个if有可能进入一个或者多个,进入多个是三个队列头最小的数有多个的情况
if(arr[p2] * 2 == newNum) p2++;
if(arr[p3] * 3 == newNum) p3++;
if(arr[p5] * 5 == newNum) p5++;
arr.push_back(newNum);
}
return newNum;
}
};
面试题23:链表中环的入口节点
题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
先说个定理:两个指针一个fast、一个slow同时从一个链表的头部出发
fast一次走2步,slow一次走一步,如果该链表有环,两个指针必然在环内相遇
此时只需要把其中的一个指针重新指向链表头部,另一个不变(还在环内),
这次两个指针一次走一步,相遇的地方就是入口节点。
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode*fast=pHead,*low=pHead;
while(fast&&fast->next){
fast=fast->next->next;
low=low->next;
if(fast==low)
break;
}
if(!fast||!fast->next)return NULL;
low=pHead;//low从链表头出发
while(fast!=low){//fast从相遇点出发
fast=fast->next;
low=low->next;
}
return low;
}
};
2020.2.5
面试题8:二叉树的下一个节点
题目描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
**
分析二叉树的下一个节点,一共有以下情况:
1.二叉树为空,则返回空;
2.节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;
3.节点不是根节点。如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果。代码如下:**
/*
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next;
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
*/
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode==NULL)
return NULL;
if(pNode->right!=NULL)
{
pNode=pNode->right;
while(pNode->left!=NULL)
pNode=pNode->left;
return pNode;
}
while(pNode->next!=NULL)
{
TreeLinkNode *proot=pNode->next;
if(proot->left==pNode)
return proot;
pNode=pNode->next;
}
return NULL;
}
};
2020.2.5
面试题32题目二:分行从上到下打印二叉树
思路:层次遍历,用一个队列
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
queue<TreeNode*> Q;
if(!pRoot) return res;
Q.push(pRoot);
while(!Q.empty()){
vector<int> tmp;//因为类型不一样,所以要定义这个。
int len = Q.size();
for(int i = 0; i < len; ++i){
TreeNode* p = Q.front();//p指向队头,即指向二叉树头结点
Q.pop();//从队列弹出。不会改变p的指向。
tmp.push_back(p->val);
if(p->left) Q.push(p->left);
if(p->right) Q.push(p->right);
}
res.push_back(tmp);
}
return res;
}
};
2020.2.6
面试题32题目三:之字形打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
方法一:用一个队列,虽然方便 ,但用reverse的时候复杂度要增加
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
if(pRoot==NULL)
return res;
queue<TreeNode*> que; //建立个单向队列,用于存放节点
que.push(pRoot);
bool flag=false; //判断是否为偶数行,flag=false代表奇数行,flag=true代表偶数行
while(!que.empty()){
vector<int> vec; //行容器,用于存入当前行输出的结果
int len=que.size();
for(int i=0;i<len;i++){
TreeNode* tmp=que.front();
vec.push_back(tmp->val);
que.pop();
if(tmp->left)que.push(tmp->left);
if(tmp->right)que.push(tmp->right);
}
if(flag) reverse(vec.begin(),vec.end()); //是偶数行就反转顺序
flag=!flag; //改变flag的值
res.push_back(vec);
}
return res;
}
};
方法二:用两个栈,时间换空间
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > vec;
if(pRoot == NULL) return vec;
stack<TreeNode*> stk1, stk2;
stk1.push(pRoot);
vector<int> tmp;
while(!stk1.empty() || !stk2.empty()){
while(!stk1.empty()){
TreeNode* pNode = stk1.top();
tmp.push_back(pNode->val);
if(pNode->left) stk2.push(pNode->left);
if(pNode->right) stk2.push(pNode->right);
stk1.pop();
}
if(tmp.size()){
vec.push_back(tmp);
tmp.clear();
}
while(!stk2.empty()){
TreeNode* pNode = stk2.top();
tmp.push_back(pNode->val);
if(pNode->right) stk1.push(pNode->right);
if(pNode->left) stk1.push(pNode->left);
stk2.pop();
}
if(tmp.size()){
vec.push_back(tmp);
tmp.clear();
}
}
return vec;
}
};
2020.2.6
不知道面试题几:
题目描述
统计一个数字在排序数组中出现的次数。
方法1:看到有序,应该用二分查找
class Solution {
public: //二分查找,通过两次二分查找,分别找到第一个k和最后一个k,可以使时间复杂度减少为O(logn)
int GetNumberOfK(vector<int> data ,int k) {
int first = findFirstK(data, k);
int last = findLastK(data, k);
if(first == -1 || last == -1){
return 0;
}
return last - first + 1;
}
int findFirstK(const vector<int> &data, int k){
int l = 0, r = data.size() - 1;
int mid = (l + r) >> 1;
while(l <= r){
if(data[mid] > k){
r = mid - 1;
} else if(data[mid] < k){
l = mid + 1;
} else if(mid - 1 >= 0 && data[mid - 1] == k){
r = mid - 1;
} else{
return mid;
}
mid = (l + r) >> 1;
}
return -1;
}
int findLastK(const vector<int> &data, int k){
int l = 0, r = data.size() - 1;
int mid = (l + r) >> 1;
while(l <= r){
if(data[mid] > k){
r = mid - 1;
} else if(data[mid] < k){
l = mid + 1;
} else if(mid + 1 < data.size() && data[mid + 1] == k){
l = mid + 1;
} else{
return mid;
}
mid = (l + r) >> 1;
}
return -1;
}
};
其他:简单暴力,不是面试官想要的;
方法2:
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
return count(data.begin(),data.end(),k);
}
};:
方法3:
//利用C++ stl的二分查找
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
auto resultPair = equal_range(data.begin(), data.end(),k);
return resultPair.second - resultPair.first;
}
};
方法4:
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
int num=0;
for(int i=0;i<data.size();i++){
if(data[i]==k)
num++;
}
return num;
}
};
面试题52:两个链表的第一个公共节点
题目描述
输入两个链表,找出它们的第一个公共结点。
思路:找出2个链表的长度,然后让长的先走两个链表的长度差,然后再一起走;
第一个公共点之后的next都是一样的,所以必然有公共的尾部;;
原来公共结点的意思是两个链表相遇之后后面都是一样的,我还以为是交叉的两个链表
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
int length1=GetLength(pHead1);
int length2=GetLength(pHead2);
ListNode* pNode1=pHead1;
ListNode* pNode2=pHead2;
int lengthDif=0;
if(length1>=length2){//如果第一个链表比第二个链表长
lengthDif=length1-length2;
for(int i=0;i<lengthDif;i++)
pNode1=pNode1->next;
}
if(length1<length2){//如果第一个链表比第二个链表短
lengthDif=length2-length1;
for(int i=0;i<lengthDif;i++)
pNode2=pNode2->next;
}
while((pNode1!=nullptr)&&(pNode2!=nullptr)&&(pNode1->val!=pNode2->val)){
// if(pNode1->val==pNode2->val)
// return pNode1;
pNode1=pNode1->next;
pNode2=pNode2->next;
}
ListNode* pCommonNode=pNode1;
return pCommonNode;
}
int GetLength(ListNode* pHead){
ListNode* pNode=pHead;
int length=0;
while(pNode!=nullptr){
length++;
pNode=pNode->next;
}
return length;
}
};
2020.2.13
面试题42:连续子数组的最大和
方法1:遍历,遇到负数和,抛弃之前的结果,重新积累,期间保留最大值
int FindGreatestSumOfSubArray(vector<int> array) {
if(array.empty())
return 0;
int result=array[0]; //考虑第一个是负数的情况,不能初始化为0;
int sum=0;
for(int i=0;i<array.size();i++){
sum=sum+array[i];
if(sum>result){
result=sum;
}
if(sum<0){
sum=0;
}
}
return result;
}
方法2:应用动态规划法
虽然通常我们用递归的方式分析动态规划的问题,但最终都会基于循环去编码。
思路:
F(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变
F(i)=max(F(i-1)+array[i] , array[i])
res:所有子数组的和的最大值
res=max(res,F(i))
如数组[6, -3, -2, 7, -15, 1, 2, 2]
初始状态:
F(0)=6
res=6
i=1:
F(1)=max(F(0)-3,-3)=max(6-3,3)=3
res=max(F(1),res)=max(3,6)=6
i=2:
F(2)=max(F(1)-2,-2)=max(3-2,-2)=1
res=max(F(2),res)=max(1,6)=6
i=3:
F(3)=max(F(2)+7,7)=max(1+7,7)=8
res=max(F(2),res)=max(8,6)=8
i=4:
F(4)=max(F(3)-15,-15)=max(8-15,-15)=-7
res=max(F(4),res)=max(-7,8)=8
以此类推
最终res的值为8
代码如下:
int FindGreatestSumOfSubArray(vector<int> array){
int res=array[0];
int Max=array[0];
for(int i=1;i<array.size();i++){
Max=max(Max+array[i],array[i]);
//sum=sum+array[i];
//sum = (array[i] > sum) ? array[i] : sum;
res=max(res,Max);
}
return res;
}
尽量避免下面的命名方式,即,用库函数当做某个名字,这样会报错!!
int FindGreatestSumOfSubArray(vector<int> array){
int res=array[0];
int **max**=array[0];
for(int i=1;i<array.size();i++){
**max**=max(**max**+array[i],array[i]);
res=max(res,**max**);
}
return res;
}
面试题43(找规律):1-n整数中1出现的次数
方法1:暴力,把每个为分离出来再与1进行比较,见书p221。
方法2:找规律,好难,哈哈
思路:
设N = abcde ,其中abcde分别为十进制中各位上的数字。
如果要计算百位上1出现的次数,它要受到3方面的影响:百位上的数字,百位以下(低位)的数字,百位以上(高位)的数字。
① 如果百位上数字为0,百位上可能出现1的次数由更高位决定。比如:12013,则可以知道百位出现1的情况可能是:100~199,1100~1199,2100~2199,,...,11100~11199,一共1200个。可以看出是由更高位数字(12)决定,并且等于更高位数字(12)乘以 当前位数(100)。
② 如果百位上数字为1,百位上可能出现1的次数不仅受更高位影响还受低位影响。比如:12113,则可以知道百位受高位影响出现的情况是:100~199,1100~1199,2100~2199,,....,11100~11199,一共1200个。和上面情况一样,并且等于更高位数字(12)乘以 当前位数(100)。但同时它还受低位影响,百位出现1的情况是:12100~12113,一共114个,等于低位数字(113)+1。
③ 如果百位上数字大于1(2~9),则百位上出现1的情况仅由更高位决定,比如12213,则百位出现1的情况是:100~199,1100~1199,2100~2199,...,11100~11199,12100~12199,一共有1300个,并且等于更高位数字+1(12+1)乘以当前位数(100)。
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
int count = 0;//1的个数
int i = 1;//当前位
int current = 0,after = 0,before = 0;
while((n/i)!= 0){
current = (n/i)%10; //当前位数字
before = n/(i*10); //高位数字
after = n-(n/i)*i; //低位数字
//如果为0,出现1的次数由高位决定,等于高位数字 * 当前位数
if (current == 0)
count += before*i;
//如果为1,出现1的次数由高位和低位决定,高位*当前位+低位+1
else if(current == 1)
count += before * i + after + 1;
//如果大于1,出现1的次数由高位决定,//(高位数字+1)* 当前位数
else{
count += (before + 1) * i;
}
//前移一位
i = i*10;
}
return count;
}
};
c++中的sort函数和实例
sort中的比较函数compare要声明为静态成员函数或全局函数,不能作为普通成员函数,否则会报错。 因为:非静态成员函数是依赖于具体对象的,而std::sort这类函数是全局的,因此无法再sort中调用非静态成员函数。静态成员函数或者全局函数是不依赖于具体对象的, 可以独立访问,无须创建任何对象实例就可以访问。同时静态成员函数不可以调用类的非静态成员。
通俗说明用法:
c++中的sort函数一般用来对数组进行排序,有三个参数,第一个参数是是数组的起始位子,第二个参数为你要排序的数组的终止位子。第三个参数一般是排序的条件,可以通过这个参数达到各种各样的排序(后面再讲),也可以不写,默认是升序。
如:int arr[5]={1,3,2,5,4}. 操作:sort(arr,arr+5). 结果{1,2,3,4,5} //默认升序
如: int arr[5]={1,3,2,5,3}. 操作:sort(arr,arr+3) 结果{1,2,3,5,4} //对数组可以部分操作
这里我对第三个参数进行详细解释:第三个参数可以是一个函数,如果该函数返回为真,就将操作对象位子不变,否则交换位子(后面有例子)。我们可以通过调整该函数的内容来控制,当某个条件满足时返回值的真假。
如:例如一个数组{32,3}这两个数如何拼接组合达到的数最小,两种情况323,332。显然323小。这类问题可以用sort来进行操作。
代码如下:
int arr[3]={3,32,321} // 组成最小数是321323
sort(arr,arr+3,cmp). //对数组三个位子进行操作,条件是cmp函数,一般是bool类型函数
static bool cmp(int a, int b)
{
string A = to_string(a)+to_string(b);
string B =to_string(b)+to_string(a);
return A<B;
}
//函数的意思是sort函数操作的对象数组中两个挨着的顺序的元素,分别赋值到a和b上。通过一系列操作,满足条件某个条件(题中条件是A<B),返回如果真,两个数顺序不变,如果返回假,两个元素交换位子。
详细解读上面得到最小值321323的过程:
从sort函数开始,将数组前两个值3,32丢入cmp中。即a=3,b=32.先将a和b转换成字符串
再拼接故A=“332” B=“323” 故return A<B这个条件是假值,sort函数将这两个值的位子交换。此时arr={32,3,321}.
接下来类似操作依次得到结果是:
arr={32,321,3}
arr={321,32,3}。
将上述数组遍历输出及得到结果。(到这里sort内容讲完)
面试题45:把数组排成最小的数
题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
class Solution {
public:
string PrintMinNumber(vector<int> numbers) {
int len = numbers.size();
if(len == 0) return "";
string answer;
sort(numbers.begin(),numbers.end(),cmp);
for(int i=0;i<numbers.size();i++){
answer+=to_string(numbers[i]);
}
return answer;
}
static bool cmp(int a,int b){
string A = to_string(a) + to_string(b);
string B = to_string(b) + to_string(a);
return A < B;
}
};
面试题50:第一个只出现一次的字符
题目描述
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
思路:哈希
class Solution {
public:
int FirstNotRepeatingChar(string str) {
map<char, int> mp;
for(int i = 0; i < str.size(); i++)
mp[str[i]]++;
for(int i = 0; i < str.size(); i++){
if(mp[str[i]]==1)
return i;
}
return -1;
}
};
其他一样的解法
不初始化就报错!一下2种写法都对:
(1)
int FirstNotRepeatingChar(string str) {
**int ch[256]=0;**
//memset(ch, 0, sizeof(ch));
if(str.length()<=0) return -1;
for(int i=0;i<str.length();i++)
{
ch[(int)str[i]]++;
}
for(int i=0;i<str.length();i++)
{
if(ch[(int)str[i]]==1)
return i;
}
return -1;
}
(2)
int FirstNotRepeatingChar(string str) {
**int ch[256];
memset(ch, 0, sizeof(ch));**
if(str.length()<=0) return -1;
for(int i=0;i<str.length();i++)
{
ch[(int)str[i]]++;
}
for(int i=0;i<str.length();i++)
{
if(ch[(int)str[i]]==1)
return i;
}
return -1;
}
2020.2.14
面试题51:数组中的逆序对。难!
思路:归并排序,貌似是剑指最难的一道,看了半天放弃了。这里先贴一个可通过的代码。
class Solution {
public:
int InversePairs(vector<int> data) {
int length=data.size();
if(length<=0)
return 0;
//vector<int> copy=new vector<int>[length];
vector<int> copy;
for(int i=0;i<length;i++)
copy.push_back(data[i]);
long long count=InversePairsCore(data,copy,0,length-1);
//delete[]copy;
return count%1000000007;
}
long long InversePairsCore(vector<int> &data,vector<int> ©,int start,int end)
{
if(start==end)
{
copy[start]=data[start];
return 0;
}
int length=(end-start)/2;
long long left=InversePairsCore(copy,data,start,start+length);
long long right=InversePairsCore(copy,data,start+length+1,end);
int i=start+length;
int j=end;
int indexcopy=end;
long long count=0;
while(i>=start&&j>=start+length+1)
{
if(data[i]>data[j])
{
copy[indexcopy--]=data[i--];
count=count+j-start-length; //count=count+j-(start+length+1)+1;
}
else
{
copy[indexcopy--]=data[j--];
}
}
for(;i>=start;i--)
copy[indexcopy--]=data[i];
for(;j>=start+length+1;j--)
copy[indexcopy--]=data[j];
return left+right+count;
}
};
面试题50.2:字符流中第一个不重复的字符
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
思路:hash表。之所以把哈希表的大小设为256,是因为字符是8bit的类型,总共有256个字符。
字符是一个长度为8的数据类型,因此总共有256种可能。
class Solution
{
public:
//Insert one char from stringstream
string s;
char hash[256]={0};
//map<char,int> hash; //直接这样也可以
void Insert(char ch)
{
s+=ch;
hash[ch]++;
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce()
{
int size=s.size();
for(int i=0;i<size;++i)
{
if(hash[s[i]]==1)
return s[i];
}
return '#';
}
};
面试题66:构建乘积数组
**题目描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…A[i-1]A[i+1]…A[n-1]。不能使用除法。(注意:规定B[0]和B[n-1] = 1)
思路:
B[i]的值可以看作下图的矩阵中每行的乘积。
下三角用连乘可以很容求得,上三角,从下向上也是连乘。
因此我们的思路就很清晰了,先算下三角中的连乘,即我们先算出B[i]中的一部分,然后倒过来按上三角中的分布规律,把另一部分也乘进去。
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
int length = A.size();
vector<int> B(length);
if(length != 0 ){
B[0] = 1;
//计算下三角连乘
for(int i = 1; i < length; i++){
B[i] = B[i-1] * A[i-1];
}
int temp = 1;
//计算上三角
for(int j = length-2; j >= 0; j--){
temp *= A[j+1];
B[j] *= temp;
}
}
return B;//从B[0]~B[n-1]
}
};
面试题67:字符串转化为整数
判断溢出最坑!
题目描述
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
输入描述:
输入一个字符串,包括数字字母符号,可以为空
输出描述:
如果是合法的数值表达则返回该数字,否则返回0
示例1
输入
+2147483647
1a33
输出
2147483647
0
class Solution {
public:
int StrToInt(string str) {
int n = str.size(), s = 1;
long long res = 0;
if(!n) return 0;
if(str[0] == '-') s = -1;
for(int i = (str[0] == '-' || str[0] == '+') ? 1 : 0; i < n; ++i){
if(!('0' <= str[i] && str[i] <= '9')) return 0;
res=res*10+str[i]-'0';
//res = (res << 1) + (res << 3) + (str[i] & 0xf);// 位运算更快。
}
//判断溢出
long temp=res*s;
if(temp<-2147483648||temp>2147483647)return 0;//判断是否超过int类型的范围//int [-2^31,2^31-1]
return (int)res*s;
}
};