剑指offer答案全集Word格式文档下载.docx
《剑指offer答案全集Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《剑指offer答案全集Word格式文档下载.docx(118页珍藏版)》请在冰点文库上搜索。
elsej--;
//向前一列
}
if(i==row||j<
0)returnfalse;
//没找到
returntrue;
//此句执行不到,只是为了编译没问题
}
分析:
时间复杂度O(m+n),m和n分别是行数和列数。
空间复杂度O
(1)。
思想是从右上角开始判断,每次判断可以排除一行或一列。
●替换空格
请实现一个函数,将一个字符串中的空格替换成“%20”。
例如,当字符串为WeAreHappy.则经过替换之后的字符串为We%20Are%20Happy。
//length为牛客系统规定字符串输出的最大长度,固定为一个常数
voidreplaceSpace(char*str,intlength){
if(NULL==str)return;
char*p=str;
intsize=0;
//记录str长度
intblankNum=0;
//记录空格个数
while(*p!
='
\0'
)//一直找到字符串尾部
if(*p=='
'
)blankNum++;
size++;
p++;
if(blankNum==0)return;
//没有空格,直接返回
intnewSize=size-blankNum+blankNum*3;
//计算新字符串大小
//if(newSize>
length)return;
char*end=str+newSize;
//
*end='
;
//最后一个字符置空
end--;
//往前一步
p=str+size-1;
//原字符串的最后一个字符处
while(p>
=str)
)
{//当前字符是空格,则替换
*end--='
0'
2'
%'
}
else
{//否则,不替换
*end--=*p;
p--;
//依次向前
}
时间复杂度O(N),N为字符串长度。
思想是先遍历一次得到字符串的长度和空格个数,计算出替换后的字符串长度,这样会在原字符串后面空出一段;
然后从后往前,碰到空格就替换。
鉴于义军的话:
就在牛客网上的那个编辑器里写代码,不要在VS里面写,因为VS有自动补全功能,而面试的时候是没有自动补全的。
所以从现在起,不在VS中写代码了,可能会在VS里查看有哪些成员函数。
2016.06.04
●从尾到头打印链表
输入一个链表,从尾到头打印链表每个节点的值。
/**
*structListNode{
*intval;
*structListNode*next;
*ListNode(intx):
*val(x),next(NULL){
*}
*};
*/
vector<
printListFromTailToHead(structListNode*head){
vec;
printTmp(head,vec);
returnvec;
voidprintTmp(ListNode*head,vector<
&
vec){
if(NULL==head)return;
printTmp(head->
next,vec);
vec.push_back(head->
val);
};
递归,时间复杂度O(N),N为链表长度。
因为要返回一个vector,所以必须另外写个函数,以便把vector作为参数。
●重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
*Definitionforbinarytree
*structTreeNode{
*intval;
*TreeNode*left;
*TreeNode*right;
*TreeNode(intx):
val(x),left(NULL),right(NULL){}
*};
*/
structTreeNode*reConstructBinaryTree(vector<
pre,vector<
in){
if(pre.size()==0||in.size()==0)returnNULL;
intsize=pre.size();
returnconstructTmp(pre,in,0,size-1,0,size-1);
TreeNode*constructTmp(vector<
pre,vector<
in,intprei,intprej,intini,intinj){
if(prei>
prej||ini>
inj)returnNULL;
TreeNode*node=newTreeNode(pre.at(prei));
inti=ini;
while(in.at(i)!
=pre.at(prei))i++;
intlen=i-ini;
node->
left=constructTmp(pre,in,prei+1,prei+len,ini,i-1);
right=constructTmp(pre,in,prei+len+1,prej,i+1,inj);
returnnode;
时间复杂度O(nlogn),n为节点个数。
递归的思想。
2016.06.05
●用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。
队列中的元素为int类型。
classSolution
{
voidpush(intnode){
stack1.push(node);
intpop(){
while(!
stack1.empty()){
stack2.push(stack1.top());
stack1.pop();
}
intret=stack2.top();
stack2.pop();
stack2.empty()){
stack1.push(stack2.top());
returnret;
private:
stack<
stack1;
stack2;
push时间复杂度为O
(1),pop时间复杂度为O(n)。
需要注意的是栈的操作push、pop(并不返回元素)、top、empty。
●旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:
给出的所有元素都大于0,若数组大小为0,请返回0。
intminNumberInRotateArray(vector<
rotateArray){
intsize=rotateArray.size();
if(size==0)return0;
intlow=0,high=size-1,mid;
while(low<
high){
if(rotateArray[low]<
rotateArray[high])break;
else{
mid=(low+high)/2;
if(rotateArray[mid]>
rotateArray[low])low=mid;
elseif(rotateArray[mid]<
rotateArray[mid-1]){
low=mid;
break;
}elsehigh=mid;
returnrotateArray[low];
线性扫描就不说了,此题的本意是利用二分查找,时间复杂度为O(logn)。
思想主要是根据arr[low]、arr[mid]、arr[high]的大小关系,判断最小元素在前半段还是后半段。
需要注意的是红色代码处,这种情况发生在:
5,6,1,2,3,4
此代码运行时间为20ms,感觉还是有点慢哎!
前面几题的运行时间都是0的。
下面用线性扫描试试,看看运行时间为多少:
intmin=rotateArray[0];
for(inti=1;
i<
size;
i++)
if(rotateArray[i]<
min)min=rotateArray[i];
returnmin;
结果时间也是20ms,呃呃呃。
。
不知什么情况了!
O(logn)和O(n)的运行时间是一样的。
●斐波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
intFibonacci(intn){
if(n==0)return0;
inttmp=1;
intret=0;
=n;
i++){
ret+=tmp;
tmp=ret-tmp;
1)拒绝用递归,因为会导致大量的重复计算。
2)初始条件为f(0)=0,f
(1)=1,f
(2)=1,递推公式f(n)=f(n-1)+f(n-2),n>
2
3)为了少用一个变量,采用了红色代码部分。
●跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。
求该青蛙跳上一个n级的台阶总共有多少种跳法。
intjumpFloor(intn){
if(n<
=0)return0;
递推(跟斐波那契数列一样)。
设共有f(n)种方法,则有:
1)如果第一次跳1级台阶,那么接下来有f(n-1)种跳法;
2)如果第一次跳2级台阶,那么接下来有f(n-2)种跳法;
第一次要么跳1级,要么跳2级,所以只用上面两种情况,故总跳法数f(n)=f(n-1)+f(n-2)
初始条件:
f
(1)=1,f
(2)=2
●变态跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。
求该青蛙跳上一个n级的台阶总共有多少种跳法
intjumpFloorII(intn){
if(n<
tmp=ret;
跟上一题一样道理,递推。
递推公式:
f(n)=f(n-1)+f(n-2)+f(n-3)+…..+f
(1)
f(0)=1,f
(1)=1,f
(2)=2,f(3)=4…,这里加了一个f(0)=1,使得更好的服从递推。
●矩形覆盖
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。
请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
intrectCover(intn){
intret=1;
for(inti=2;
tmp=ret-tmp;
递推公式f(n)=f(n-1)+f(n-2),f
(1)=1,f
(2)=2
●二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。
其中负数用补码表示。
intNumberOf1(intn){
intsum=0;
while(n){
sum++;
n&
=(n-1);
returnsum;
红色代码每执行一次,可以消去二进制中的一个1。
●数值的整数次方
给定一个double类型的浮点数base和int类型的整数exponent。
求base的exponent次方。
doublePower(doublebase,intexponent){
if((base<
=0.000001&
base>
=-0.000001)&
exponent<
0)return0.0;
if(exponent>
0)returnPowerTmp(base,exponent);
elsereturn1.0/PowerTmp(base,-exponent);
doublePowerTmp(doublebase,intex){
if(ex==0)return1.0;
if(ex==1)returnbase;
doubleret=Power(base,ex/2);
if(ex&
0x1)returnret*ret*base;
elsereturnret*ret;
时间复杂度为O(logn),n为指数。
1)首先判断base和exponent是否合法:
0的负数次幂不合法,0的0次幂默认为1.
2)考虑exponent正负,如果小于0,返回倒数;
3)如果exponent为奇数,则power(base,ex)=power(base,ex/2)*power(base,ex/2)*base;
如果exponent为偶数,则power(base,ex)=power(base,ex/2)*power(base,ex/2);
折半比循环快。
●调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
voidreOrderArray(vector<
array){
intsize=array.size();
if(size==0)return;
intodd=0,even=0;
//odd为奇数下标,even为第一个偶数下标
while(array[even]&
0x1)even++;
//找到第一个偶数
odd=even+1;
//从第一个偶数后面开始找到第一个奇数
while(odd<
size){
size&
!
(array[odd]&
0x1))odd++;
//找到下一个奇数
if(odd>
=size)break;
//如果没有奇数了,及时退出
//将[even,odd-1]之间的偶数向后移一位
inttmp=array[odd];
for(inti=odd;
i>
even;
i--)
array[i]=array[i-1];
array[even]=tmp;
//把奇数放到前面
even++;
odd++;
此题要是可以改变相对位置,则有时间复杂度为O(n)、空间复杂度为O
(1)的方法。
但是要保证相对位置不变,要么时间复杂度为O(n)、空间复杂度为O(n),要么时间复杂度为O(n*n)、空间复杂度为O
(1)。
本代码的时间复杂度为O(n*n),但运行时间为0ms。
那么此题的意义何在?
●链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点。
/*
structListNode{
intval;
structListNode*next;
ListNode(intx):
val(x),next(NULL){
ListNode*FindKthToTail(ListNode*pListHead,unsignedintk){
if(NULL==pListHead||k==0)returnNULL;
ListNode*p=pListHead;
inti=1;
while(i++<
k){
p=p->
next;
if(NULL==p)returnNULL;
//防止链表长度小于k
ListNode*node=pListHead;
while(p->
next){//p->
next为空时,p才指向最后一个节点
node=node->
时间复杂度O(n),n为节点个数。
思路:
先让一个指针走k-1步,然后另一个指针从头开始,两个指针同步向后走,当第一个指针走到最后一个节点时,第二个指针正好是倒数第k个节点。
注意一下几个地方:
1)链表为空;
2)k为0
3)链表长度小于k;
4)当第一个指针到达最后一个节点时,而不是指向空时,第二个指针才指向倒数第k个节点。
●反转链表
输入一个链表,反转链表后,输出链表的所有元素。
ListNode*ReverseList(ListNode*pHead){
if(NULL==pHead)returnNULL;
if(pHead->
next==NULL)returnpHead;
ListNode*node=pHead->
ListNode*head=ReverseList(node);
next=pHead;
pHead->
next=NULL;
returnhead;
递归实现。
精髓在于如何返回头结点(原来的尾节点):
一旦找到尾节点,就一直返回它,不让它参与任何操作。
下面是进一步简化的代码:
ListNode*head=ReverseList(pHead->
next);
next->
●合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
ListNode*Merge(ListNode*pHead1,ListNode*pHead2){
if(NULL==pHead1)returnpHead2;
if(NULL==pHead2)returnpHead1;