目录
前言
在面试过程中,面试官对代码的检查通常是会去检查1. 是否完成了基本功能。2. 输入边界值是否能得到正确的输出。3. 是否对各种不合规范的非法输入做出了合理的错误处理。
正文
经验
- 写代码的过程中,应跳出惯行思维,考虑全面。
- 三种测试方法:功能测试,边界测试和负面测试(考察各种错误输入)。
- 在写函数时,如果要检测调用接口是否成功,最好是有一个整数返回值,这样才能确定,调用接口是否成功。
- 可以在出问题时抛出一个异常。
- 位运算的效率比乘除法的运算效率要高的多。
- 查找一个东西,那么首先,你需要确定说这个东西确实是在该数据结构的。
- 鲁棒性是指判断程序是否合乎要求,并对不合要求的输入予以合理的处理。可以采用防御性编程。
- 对于浮点数,在比较大小的时候,注意,并不能直接和进行相比,而是要与0.000001这样一样较小的数进行比较,小于0.0000001大于-0.0000001这样的数,称之为0。
题目
面试题11:数值的整数次方
题目:
解法一:
有可能你一上来就是一个for循环,然后自以为是解决了,其实这道题要考察的知识点,恰恰是你对输入数据的敏感度,是否能较好的分析输入数据为正数,负数,或者是0的情况。从而对最终情况进行分类讨论。
首先,先分析指数,其有负数,0,正数三种情况。然后分析底数,也有这三种情况。
最终解法
code
bool g_InvalidInput = false;
double Power(double base,int exponent)
{
g_InvalidInput = false;
if(equal(base,0,0)&&exponent<0)
{
g_InvalidInput = true;
return 0.0;
}
unsigned int absExponent = (unsigned int)(exponent);
if(exponent<0)
absExponent = (unsigned int)(-exponent);
double result = PowerWithUnsignedExponent(base,absExponent);
if(exponent<0)
result = 1.0/result;
}
double PowerWithUnsignedExponent(double base,unsigned int exponent)
{
double result = 1.0;
for(int i = 1;i<=exponent;++i)
{
result *=base;
}
return result;
}
bool equal(double num1,double num2)
{
if((num1-num2>-0.0000001)&&(num1-num2<0.0000001))
return true;
else
return false;
}
总结:
反正就是要仔细考虑,对输入数据进行充分的考虑,越简单的题越要进行仔细的分析。还有一个细节就是在判断base是否等于0的时候,不能直接使用==0这种判断方式,因为计算机在表示小数时是有无法的,所以,要跟0.0000001进行比较,若比这种数要小,比-0.0000001要大,那就基本上可以证明是等于0了。
面试题12:打印1到最大的n位数
题目
注意:注意这个n到底有多大,有没有可能是大数问题。
考虑:既然有了上面对这个问题的考虑,于是我们选择采用字符串来解答这道题目。因为是n位十进制数,所以,我们需要使用n+1位的字符串来解决这个问题。若实际数字不够位的时候,我们采用在前面补0的方式,表达那个数字。
解法
code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
//打印1到最大的n位数(C语言)
/*
考虑一下大数问题
*/
//number =19
int Increment(char* number)//每次对Number这个字符串的数值+1 ,并返回是否已经超过最大的n位数
{
int isOverFlow = 0;//判断是否超过最大值
int nTakeOver = 0;
int nLength = strlen(number);//2
int i;
for (i=nLength-1;i>=0;i--)
{
int nSum = number[i]-'0'+nTakeOver; //9
if(i==nLength-1)
nSum++;//nSum = 2
if(nSum>=10)//若最末的那一位加完后超过10
{
if(i==0)
isOverFlow =1;//若是第0位,则置溢出标志
else
{
nSum -=10;
nTakeOver = 1;
number[i] = '0' +nSum;
}
}
}
return isOverFlow;
}
void PrintNumber(char *number)
{
int i =0;
int length = strlen(number);
while(number[i]=='0')
i++;
for (int j=i;j<length;j++)
cout<<number[j];
cout<<"\t";
}
void PrintToMaxN(int n)//采用更改字符串每一位的方式来打印出每一位
{
if(n<0)//首先,一定要注意进行输入数据的完美判断
return;
char number[n+1];
memset(number,'0',n);
number[n] = '\0';
while(Increment(number))//不断的在该字符串上更改每一位数字的大小,判断是否溢出
{
PrintNumber(number);//打印该字符串
}
}
int main()
{
int i;
int n;
printf("Please input the n number:");
scanf("%d",&n);
PrintToMaxN(n);
return 0;
}
总结:这道题的难点,我都觉得不在思路上面了,而是在不断的对字符串进行判断溢出,以及不断的对字符串进行加一的操作。
面试题13:在O(1)时间删除链表节点
题目
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
解法
思路:
关于删掉链表中某一个节点,我们不一定要找到这个节点的前一个节点进行删除,我们只需要找到当前节点的后一个节点,然后,把后一个节点的值复制到这个要删除的这个节点,然后,更改一下指针,删掉后面的这个节点。需要注意的是,有可能出现,要删除的节点是最后一个节点或者有可能是第一个节点,考虑了这两种情况之后,我们就可以开始写代码了。
code
//删除链表中的某一个节点,要求时间复杂度为O(1)
void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted)//一个是头指针,一个是要被删除的指针
{
if(!pListHead||pToBeDeleted)
{
return;
}
if(pToBeDeleted->m_pNext!=NULL)
{
ListNode* pNext = pToBeDeleted->m_pNext;
pToBeDeleted->m_nValue = pNext->m_nValue;
pToBeDeleted->m_pNext = pNext->m_pNext;
delete pNext;
pNext = NULL;
}
else if(*pListHead==pToBeDeleted)//当只有一个节点的时候
{
delete pToBeDeleted;
pToBeDeleted = NULL;
*pListHead = NULL;
}
else//若是最后一个节点的话,那么就得进行顺序查找,从头到尾进行查找
{
ListNode* pNode = *pListHead;
while(pNode->m_pNext!=pToBeDeleted)
{
pNode = pNode->m_pNext;
}
pNode->m_pNext = NULL;
delete pToDeleted;
pToBeDeleted = NULL;
}
}
总结:关于这道题,那个idea还是较为具有创新性的,就是删除的时候,不一定要按照固定思维,从前往后进行查找,而是可以直接改变下一个节点的位置,然后,删除,下个节点。还有一个点就是需要进行考虑全面,关于给的条件,也是需要考虑清楚,到底是有没有那个节点,到底是节点在第一个位置,还是在最后一个位置,这三种情况都是需要考虑的。
面试题14:调整数组顺序使奇数位于偶数前面
分析:将指针分别指向头和尾,移动头指针,直到是偶数,移动尾指针,直到是奇数。然后两个指针指向的值进行交换。不断的重复这个过程就可以了
code
void ReorderOddEven(int *pData,unsigned int length)
{
if(pData==NULL||length==0)
return;
int *pBegin = pData;
int *pEnd = pData+length-1;
while(pBegin<pEnd)
{
//向后移动pBegin,直到它指向偶数
while(pBegin<pEnd&&(*pBegin&0x1)!=0)
pBegin++;
//向前移动pEnd,直到它指向奇数
while(pBegin<pEnd&&(*pEnd&0x1)==0)
pEnd--;
if(pBegin<pEnd)
{
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
}
}
}
总结:总结来说,还是要利用好指针,才可以较为便利的解决好这种问题。
面试题15:链表中倒数第k个节点
题目:
struct ListNode{
int m_nValue;
ListNode *m_pNext;
}
思路:数一下整个链表有多少个节点,然后用两个距离为k的两个指针同时移动,直到第二个指针移动到最后的时候,我们就可以发现第一个指针已经指向了倒数第k个指针。这里需要注意的是,若链表节点数小于K,已经,传入值的健壮性。
面试题16:反转链表
题目
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
}
思路:在代码测试前,最好想好测试用例,这样就心里进行测试后,再进行代码的编写,就比较不会出现问题了。
code
ListNode* ReverseList(ListNode *pHead)
{
ListNode* pReversedHead = NULL;
ListNode* pNode = pHead;
ListNode* pPrev = NULL;
while(pNode!=NULL)
{
ListNode* pNext = pNode->m_pNext;//先确定下第二个节点,保存下这个节点,后面这个节点还要用来连接链表
if(pNext==NULL)//若移动到最末尾了,则最后一个节点,就是反转后的头节点
pReversedHead = pNode;
pNode->m_pNext = pPrev;//调转枪头,指向pre
pPrev = pNode;//然后,进行下一个节点的调转,上面的主角现在成了配角pre了
pNode = pNext; //然后把next指向主角。完成2个节点
}
return pReversedHead;
}
总结:这道题总结来说还是比较有难度的,毕竟涉及到指针的转向,其实很多人对这个东西都是迷迷糊糊的。注意,先保存下主角的下一个指针,然后,再进行转向的操作,转向目的指针可以弄为pre。
面试题17:合并两个排序的链表
题目
code
ListNode* Merge(ListNode* pHead1,ListNode* pHead2)
{
if(pHead1==NULL)//若第一个链表为空,那合并后的链表一定是第二个链表
return pHead2;
else if(pHead2==NULL)
return pHead1;
ListNode* pMergeHead = NULL;
if(pHead1->m_nValue<=pHead2->m_nValue)
{
pMergedHead = pHead1;
pMergedHead->m_pNext = Merge(pHead1->m_pNext,pHead2);
}
else
{
MergedHead = pHead2;
pMergedHead->m_pNext = Mergw(pHead1,pHead2->m_pNext);
}
}
总结:这肯定是一个合并的过程。就是挑选出两个链表中,符合要求的那个节点,跟在新的那个要返回的合并链表的后面。然后,继续这样的操作,可使用递归了。递归要出去出去的条件。
面试题18:树的子结构
题目
code
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
}
思路:我本来的思考是可以对这A树进行遍历,得到一个字符串,然后再对B树进行遍历,只要在A字符串中找到这个B子字符串,就可以判断B是不是A的子结构了。
code
//判断第二棵子树有没有可能是第一棵子树的子树。
bool HasSubtree(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2)
{
bool result = false;
if(pRoot1!=NULL&*pRoot2!=NULL)
{
if(pRoot1->m_nValue==pRoot2->m_bValue)
result = DoesTree1HaveTree2(pRoot1,pRoot2);//一个节点如果相等,那就递归下去比较下一个节点
if(!result)
result = HasSubtree(pRoot1->m_pLeft,pRoot2);//如果该节点不相等,那就递归下去找左节点
if(!result)
result = HasSubtree(pRoot2->m_nRight,pRoot2);//如果该节点不相等,那就递归下去找右节点
}
}
//这就是递归下去找下一个节点
bool DoesTree1HaveTree2(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2)
{
if(pRoot2==NULL)
return true;
if(pRoot1==NULL)
return false;
if(pRoot1->m_nValue!=pRoot2->m_nValue)
return false;
return DoesTree1HaveTree2(pRoot2->m_pLeft,pRoot2->m_pLeft)&& DoesTree1HaveTree2(pRoot1->m_pRight,pRoot2->m_pRight);
}
总结:关于这个的解决,主要还是这个递归思想,就是不断的往下传,只要它传下去比较的东西是固定的,那么就可以采用递归的方法进行操作。