面试题3:在二维数组中,每一行都按照从左到右的递增顺序的顺序排序,每一列都按照从上到下的递增顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否包含这该整数。
思路:
要点1-每次选择矩阵中的一个数和该整数进行比较,逐渐缩小搜索范围!
本次设计中,每次比较采用矩阵右上角的那个数字进行比较,这是因为这样的比较可以唯一的确定目标的范围!
如果右上角的数等于该整数,则返回true;
如果右上角的数小于该整数,则右上角的数的行号 +1;
如果右上角的数大于该整数,则右上角的数的列号 -1;
要点2-二维数组的表示
采用一维数组间接表示,数组为m*n,第i行第j列的数据为:matrix[ i*n+j ]。
#include <iostream>
using namespace std;
bool Find(int* matrix,int r,int c,int num);
int main()
{
int row,column;
cin>>row>>column;
int mSize=row*column;
int matrix[mSize];
for(int i=0;i<row;i++)
{
for(int j=0;j<column;j++)
cin>>matrix[i*column+j];
}
cout<<"输入一个数:"<<endl;
int num;
cin>>num;
bool f=Find(matrix,row,column,num);
if(f==true) cout<<"In"<<endl;
else cout<<"Not In"<<endl;
return 0;
}
bool Find(int* matrix,int row,int column,int num)
{
bool flag=false;
if(matrix&&row>0&&column>0)
{
int r=0,c=column-1;
while(r<row&&c>=0)
{
if(matrix[r*column+c]==num)
{
flag=true;
break;
}
else if(matrix[r*column+c]<num)
{
r++;
}
else c--;
}
}
return flag;
}
面试题4:替换空格(字符串)
请实现一个函数,把字符串的每个空格替换为‘%20’。例如输入:"We are happy.",则输出:"We%20are%20happy."
思路1:
从前向后遍历字符串,一旦遇到空格,则将空格后面的所有字符后移两位,再将空格部分替换为"%20"。
如何后移:
后移时,应该从最后一位开始(strlen(str)得到最后一个字符的位置),这就要求定义字符串时,需要足够大的内存空间;
输入一行字符串:
同时,如何输入一行字符串,也需要注意:cin.getline( str , mSize);
根据思路1,写出Change1();
思路2:
很明显,思路1的时间复杂度为O(n*n),并不是一个好的解决方案
统计空格数量,原长度,新长度=原长度+空格数*2;
两个指针p1,p2,分别指代原字符串最后一位'\0'以及新长度最后一位;
p1、p2同时前移,移动过程中进行判断:
如果p1指向的字符部位空字符,则p2复制p1;
否则的话,p1正常前移,而p2前移3位,这三位即为"%20"
#include <iostream>
#include <cstring>
#define mSize 50
using namespace std;
void Change1(char* str);
void Change2(char* str);
int main()
{
char str[mSize];
cout<<"输入一个字符串:"<<endl;
cin.getline(str,mSize);
cout<<"将字符串中的空格替换为'%20'后,字符串变为:"<<endl;
Change2(str);
cout<<str;
return 0;
}
void Change1(char* str)
{
if(str)
{
int i=0;
while(str[i]!='\0')
{
if(str[i]==' ')
{
for(int j=strlen(str);j>i;j--)
{
str[j+2]=str[j];
}
str[i]='%';
str[i+1]='2';
str[i+2]='0';
}
i++;
}
}
}
void Change2(char* str)
{
if(str)
{
int oriLen=strlen(str);
int i=0,num=0;
while(str[i]!='\0')
{
if(str[i]==' ')
num++;
i++;
}
int newLen=oriLen+2*num;
i=oriLen;
int j=newLen;
while(i>=0&&i<j)
{
if(str[i]!=' ')
{
str[j--]=str[i]; //str[i]不为空格时,str[j]复制strp[i];
}
else //str[i]为空格时,str[j]开始之前的三个元素为"%20";
{
str[j--]='0';
str[j--]='2';
str[j--]='%';
}
i--;
}
}
}
面试题5:从尾到头打印链表
输入一个链表的头结点,从尾到头反过来打印出每个节点的值。
思路1:
首先会想到将链表反转,然而这需要改变链表的结构,应该问清楚具体要求,能否改变原链表。
如果不改变链表的结构,怎么办?
采用辅助的数据结构,这里从尾到头的逆序---栈(先进后出)
即遍历链表,逐次放入一个栈里面,从栈顶开始,输出数据。
思路2:
还可以采用递归的思想,因为递归本质上就是一种栈结构!每次访问到一个结点时,先递归的访问他后面的结点,再返回访问该结点。
#include <iostream>
#include <stack>
using namespace std;
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
class LinkList
{
private:
ListNode* Head;
void ReversePrint_DiGui(ListNode* t);
public:
LinkList(){Head=NULL;}
~LinkList(){}
ListNode* getHead(){return Head;}
void CreateList();
void ReversePrint();
void ReversePrint_DiGui();
};
void LinkList::CreateList()
{
int val;
while(cin>>val)
{
ListNode* newNode=new ListNode();
newNode->m_nValue=val;
newNode->m_pNext=NULL;
if(Head==NULL)
Head=newNode;
else
{
ListNode* pNode=Head;
while(pNode->m_pNext)
{
pNode=pNode->m_pNext;
}
pNode->m_pNext=newNode;
}
}
}
void LinkList::ReversePrint()
{
if(Head==NULL) return;
stack<ListNode*> sln;
ListNode* pNode=Head;
while(pNode)
{
sln.push(pNode);
pNode=pNode->m_pNext;
}
while(!sln.empty())
{
pNode=sln.top();
cout<<pNode->m_nValue<<" ";
sln.pop();
}
cout<<endl;
}
void LinkList::ReversePrint_DiGui()
{
ReversePrint_DiGui(Head);
}
void LinkList::ReversePrint_DiGui(ListNode* t)
{
if(t)
{
if(t->m_pNext)
{
ReversePrint_DiGui(t->m_pNext);
}
cout<<t->m_nValue<<" ";
}
}
int main()
{
LinkList ls;
ls.CreateList();
//使用堆栈的方法:
ls.ReversePrint();
// //使用递归的方法:
// ls.ReversePrint_DiGui();
return 0;
}
</pre><pre name="code" class="cpp"><strong><span style="color:#ff0000;">C风格的程序(使用普通函数)</span></strong>
<pre name="code" class="cpp">#include <iostream>
#include <stack>
using namespace std;
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
ListNode* CreateList(){ListNode* Head=NULL; return Head;}
void Insert(ListNode** Head,int value);
void ReversePrint(ListNode* Head);
void ReversePrint_DiGui(ListNode* t);
int main()
{
ListNode* Head;
Head=CreateList();
CreateList();
int value;
while(cin>>value)
{
Insert(&Head,value);
}
///使用栈的方法
// ReversePrint(Head);
///使用递归
ReversePrint_DiGui(Head);
return 0;
}
void Insert(ListNode** Head,int value)
{
ListNode* newNode=new ListNode();
newNode->m_nValue=value;
if(*Head==NULL)
*Head=newNode;
else
{
ListNode* pNode=*Head;
while(pNode->m_pNext)
pNode=pNode->m_pNext;
pNode->m_pNext=newNode;
}
}
void ReversePrint(ListNode* Head)
{
if(Head==NULL) return;
ListNode* pNode=Head;
stack<ListNode*> s;
while(pNode)
{
s.push(pNode);
pNode=pNode->m_pNext;
}
while(!s.empty())
{
pNode=s.top();
cout<<pNode->m_nValue<<" ";
s.pop();
}
cout<<endl;
}
void ReversePrint_DiGui(ListNode* t)
{
if(t!=NULL)
{
if(t->m_pNext)
{
ReversePrint_DiGui(t->m_pNext);
}
cout<<t->m_nValue<<" ";
}
}
面试题6:重建二叉树
输入某二叉树的前序和中序遍历的结果,请重建出该二叉树。假设不含重复的数字。
例如:输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}。
思路:
基本思路并不难,关键是如何实现:
前序结果中,第一个数字即为根结点的值,中序序列中,根结点的左右子序列分别为根结点的左右子树;
子树中,上述规律同样有效!
关键点:
使用递归,结束条件为左右子树均为空;
#include <iostream>
using namespace std;
struct Node
{
int m_nValue;
Node* left;
Node* right;
};
Node* Struct(int* PreOrder,int* InOrder,int len);
Node* StructCore(int* PreS,int* PreE,int* InS,int* InE);
int main()
{
int PreOrder[8]={1,2,4,7,3,5,6,8};
int InOrder[8]={4,7,2,1,5,3,8,6};
Node* r=Struct(PreOrder,InOrder,8);
cout<<r->m_nValue<<endl;
return 0;
}
Node* Struct(int* PreOrder,int* InOrder,int len)
{
if(PreOrder==NULL||InOrder==NULL||len<=0)
return NULL;
return StructCore(PreOrder,PreOrder+len-1,InOrder,InOrder+len-1);
}
Node* StructCore(int* PreS,int* PreE,int* InS,int* InE)
{
int rootV=PreS[0];
Node* root=new Node();
root->m_nValue=rootV;
root->left=root->right=NULL;
if(PreS==PreE) //结束递归的条件!
{
if(InS==InE&&*PreS==*InS)
return root;
else
{
cout<<"Invalid input!"<<endl;
return NULL;
}
}
int* p=InS;
while(*p!=rootV&&p<=InE)
p++;
if(InS==InE&&*p!=rootV)
{
cout<<"Invalid input!"<<endl;
return NULL;
}
int leftLen=p-InS;
int* PreEnew=PreS+leftLen;
if(leftLen>0) //左子树结束递归的条件
root->left=StructCore(PreS+1,PreEnew,InS,p-1);
if(leftLen<PreE-PreS) //右子树结束递归的条件!!!
root->right=StructCore(PreEnew+1,PreE,p+1,InE);
return root;
}
面试题7:用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。
思路:
在队尾插入数据时,无需复杂的操作,直接对第一个栈s1进行插入操作
而删除头结点时,首先,检查第二个栈s2中是否为空,不为空,则直接删除s2中的栈顶元素(s2中的栈顶元素始终为队首元素)
否则,如果s2为空,则将s1中的所有元素自顶向下压入s2中,s1放空之后,再删除s2中的栈顶元素
扩展:使用两个队列实现一个栈
思路:
出栈决定入栈:始终保持一个队列为空,入栈时,数据压入到部位空的队列的队尾(即非空队列的队尾为栈顶元素)
出栈时,将非空的队列的除队尾元素外全部压入到空队列中,则该队尾元素变为出栈的元素
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
///两个栈实现队列
template <class T>
class Queue
{
private:
stack<T> s1;
stack<T> s2;
public:
Queue();
~Queue();
void appendTail(const T& val);
T deleteHead();
};
template <class T>
Queue<T>::Queue(){}
template <class T>
Queue<T>::~Queue(){}
template <class T>
void Queue<T>::appendTail(const T& val)
{
s1.push(val);
}
template <class T>
T Queue<T>::deleteHead()
{
T del;
if(s2.empty())
{
while(!s1.empty())
{
T temp=s1.top();
s2.push(temp);
s1.pop();
}
}
del=s2.top();
s2.pop();
return del;
}
///两个队列实现栈
template <class T>
class Stack
{
private:
queue<T> q1,q2;
public:
Stack();
~Stack();
void Push(T val);
T Pop();
};
template <class T>
Stack<T>::Stack(){}
template <class T>
Stack<T>::~Stack(){}
template <class T>
void Stack<T>::Push(T val)
{
if(q1.empty())
q2.push(val);
else if(q2.empty())
q1.push(val);
}
template <class T>
T Stack<T>::Pop()
{
T p,temp;
if(q1.empty())
{
while(q2.size()>1)
{
temp=q2.front();
q1.push(temp);
q2.pop();
}
p=q2.front();
q2.pop();
}
else if(q2.empty())
{
while(q1.size()>1)
{
temp=q1.front();
q2.push(temp);
q1.pop();
}
p=q1.front();
q1.pop();
}
return p;
}
int main()
{
// ///两个栈实现队列测试
// Queue<int> q;
// int val;
// while(cin>>val)
// {
// q.appendTail(val);
// }
// cout<<q.deleteHead()<<endl;
// q.appendTail(5);
// cout<<q.deleteHead()<<endl;
///两个队列实现栈测试
Stack<int> s;
int val;
while(cin>>val)
s.Push(val);
cout<<s.Pop()<<endl;
s.Push(7);
cout<<s.Pop()<<endl;
return 0;
}
面试题8:旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素,例如数组:{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组最小值为1
思路1:
题目很简单,遍历一边即可得到结果。
思路2:(二分查找的思想)
根据旋转数组的特点:
3个指针,left,right,mid。left指向最左边的元素,right指向最右边的元素,mid指向left和right中间的元素
如果right-left==1,则结束,right指向最小值
否则:mid=(left+right)/2; 同时判断mid在哪个子数组中,(*mid>*left则在左子数组中,*mid<*right则在右子数组中)
判断完毕,则用mid代替left或者right(在哪个子数组中替哪个)
直到right-left==1;结束返回right
思路3:
思路2中,要求left指向的元素值始终要大鱼right的元素。然而如果相等的话即:0,1,1,1,1,1,则只能用思路1
总结:最终地结果是思路1和思路2的结合!
#include <iostream>
#define mSize 20
using namespace std;
int Min2(int* arr,int len);//思路1
int Min1(int* arr,int len);//顺序查找,思路2
int main()
{
int arr[5]={3,4,5,1,2};
int len=5;
// int result=Min1(arr,len);
int result=Min2(arr,len);
cout<<result<<endl;
return 0;
}
int Min1(int* arr,int len)
{
int result=arr[0];
for(int i=1;i<len;i++)
{
if(arr[i]<result)
result=arr[i];
}
return result;
}
int Min2(int* arr,int len)
{
int left=0,right=len-1;
int mid=0;
while(arr[left]>=arr[right])
{
if(right-left==1)
return arr[right];
mid=(right+left)/2;
if(arr[mid]>=arr[left])
left=mid;
else if(arr[mid]<=arr[right])
right=mid;
}
return arr[mid];
}
面试题9:斐波那契数列
写一个函数,输入n,求斐波那契数列的第n项。
思路1:
当然是递归最常用了。
思路2:
使用循环,从n=2开始逐步计算:
面试题10:
#include <iostream>
using namespace std;
int Fibonacci1(int n);
int Fibonacci2(int n);
int main()
{
cout<<Fibonacci1(7)<<endl;
cout<<Fibonacci2(7)<<endl;
return 0;
}
int Fibonacci1(int n)
{
if(n==0)
return 0;
if(n==1)
return 1;
return Fibonacci1(n-1)+Fibonacci1(n-2);
}
int Fibonacci2(int n)
{
if(n==0)
return 0;
if(n==1)
return 1;
int result;
int a=0,b=1;
for(int i=2;i<=n;i++)
{
result=a+b;
a=b;
b=result;
}
return result;
}
扩展:斐波那契数列的应用:青蛙跳台阶的问题
一只青蛙一次可以跳上一级台阶,也可以跳上两级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:
只有一个台阶n=1时,只有一种跳法; 有两个台阶n=2时,有两种跳法;
n=3时,有两种:第一次跳一个,剩下的台阶跳法有f(2)种;第一次跳两个,剩下的台阶跳法有f(1)种,f(3)=f(1)+f(2);
从而:f(n)=f(n-1)+f(n-2)(第一次跳一个还是两个,每种剩下的通过递归实现)
#include <iostream>
using namespace std;
int numKinds(int n);
int main()
{
cout<<numKinds(3)<<endl;
cout<<numKinds(5)<<endl;
return 0;
}
int numKinds(int n)
{
if(n==1)
return 1;
if(n==2)
return 2;
return numKinds(n-1)+numKinds(n-2);
}
面试题10:
二进制中1的个数
请事先一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示为1001,有两个1。因此函数输出2。
思路1:
整数与1相与,直到结果为0,每次相与后,如果结果为1,则1的个数+1。同时将整数右移一位。
思路2:
思路1正常情况下,是有效的,然而,当输入为负的时候,会造成死循环
思路1中是将整数左移,破坏了整数原本的结构,因此会造成输入为负的时候的死循环。换个思路,为何不将1左移呢?这样的话避免了破坏整数的结构。每次相与结束后,将1左移。
思路3:
面试题目中很常见的一种方法:n=(n-1)&n,直到n为0,每次计算,则有一个1;
因为:
整数的二进制-1,相当于去掉整数中的一个1,再与减一之前的整数相与,结果是:整数的最后一个1变为0!
#include <iostream>
using namespace std;
int numOne1(int n);
int numOne2(int n);
int numOne3(int n);
int main()
{
int n;
cout<<"输入一个整数(ctrl+z结束):"<<endl;
while(cin>>n)
{
// cout<<n<<" 的二进制中1的个数为:"<<numOne1(n)<<endl;
// cout<<n<<" 的二进制中1的个数为:"<<numOne2(n)<<endl;
cout<<n<<" 的二进制中1的个数为:"<<numOne3(n)<<endl;
}
return 0;
}
int numOne1(int n)
{
int count=0;
while(n)
{
if(n&1)
count++;
n=n>>1;
}
return count;
}
int numOne2(int n)//这种计算方法中,若输入为负数,则结果为负数补码中1的个数
{
int count=0;
int flag=1;
while(flag)//循环的次数取决于int型整数的位数!当flag左移超过位数时,flag=0;
{
if(n&flag)
count++;
flag=flag<<1;
}
return count;
}
int numOne3(int n)
{
int count=0;
while(n)
{
n=(n-1)&n;
count++;
}
return count;
}