剑指offer——面试中的各项能力
1. 知识迁移能力
面试题38:数字在排序数组中出现的次数
/*采用二分查找:分别查找数组中第一个和最后一个待查找的数字*/
int GetNumberOfK(int *data,int length,int k){
if (data==NULL && length<=0)
return 0;
int number = 0;
int firstKIndex = GetFirstK(data,length,k,0,length-1);
int endKIndex = GetLastK(data,length,k,0,length-1);
if (firstKIndex > -1 && endKIndex>-1)
number = last-fisrt+1;
return number;
}
int GetFistK(int *data,int length,int k,int start,int end){
if (start > end)
return -1;
int middleIndex = (start+end)/2;
int middleData = data[middleIndex];
if (middleData==k){
if (middleIndex>0 && data[middleIndex-1]!=k || middleIndex==0)
return middleIndex;
else
end = middleIndex-1;
}
else if (middleData > k)
end = middleIndex-1;
else
start = middleIndex+1;
return GetFirstK(data,length,k,start,end);
}
int GetLastK(int *data,int length,int k,int start,int end){
if (start > end)
return -1;
int middleIndex = (start+end)/2;
int middleData = data[middleIndex];
if (middleData==k){
if (middleIndex>0 && data[middleIndex+1]!=k || middleIndex==length-1)
return middleIndex;
else
start = middleIndex+1;
}
else if (middleData > k)
end = middleIndex-1;
else
start = middleIndex+1;
return GetFirstK(data,length,k,start,end);
}
面试题39:二叉树的深度
struct BinaryTreeNode{
int m_nVlaue;
BinaryTreeNode *m_pLeft;
BinaryTreeNode *m_pRight;
};
int TreeDepth(BinaryTreeNode *pRoot){
if (pRoot == NULL)
return 0;
int nLeft = TreeDepth(pRoot->m_pLeft);
int nRight = TreeDepth(pRoot->m_pRight);
return (nLeft>nRight)?(nLeft+1):(nRight+1);
}
判断二叉树是不是平衡二叉树(每个结点的左右子树深度相差不超过1)
//解法一:递归比较每个结点,需要重复遍历结点多次(自顶向下)
bool IsBalanced(BinaryTreeNode *pRoot){
if (pRoot == NULL)
return true;
int left = IsBalanced(pRoot->m_pLeft);
int right = IsBalanced(pRoot->m_pRight);
int diff = left-right;
if (diff>1 || diff <-1)
return false;
return (IsBalanced(pRoot->m_pLeft) && IsBalanced(pRoot->m_pRight));
}
//解法二:后序遍历树,记录每个结点的深度(自底向上)
bool IsBalanced(BinaryTreeNode *pRoot){
int depth = 0;
return IsBalanced(pRpoot,&depth);
}
bool IsBalanced(BinaryTreeNode *pRoot,int *pDepth){
if (pRoot == NULL){
*pDepth = 0;
return true;
}
int left,right;
if (IsBalanced(pRppt->m_pLeft,&left) && IsBalanced(pRppt->m_pRight,&right)){
int diff = left-right;
if (diff<=1 || diff >=-1){
*pDepth = 1+(left>right?left:right);
return true;
}
}
return false;
}
面试题40:数组中只出现一次的数字(只有两个数字出现一次,其余数字出现了两次。要求时间复杂度O(n),空间复杂度O(1))
/*若数组中只有一个数字只出现了一次,其余数字均出现了两次,可以通过从头到尾异或数组中每个数字,最终的结果就是那个只出现一次的数字;所以我们可以通过将数组分为两组只包含一个只出现一次数字的数组,再分别查找*/
/*通过异或的结果最右边是1的位,将数组数字该位是不是1分为两个数组*/
void FindNumsAppearOnce(int data[],int length,int *num1,int *num2){
if (data==NULL || length<2)
return;
int resultExclusiveOR = 0;
for (int i=0;i<length;++i)
resultExclusiveOR ^= data[i];
unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);
*num1 = *num2 = 0;
for (int j=0;j<length;++j){
if (IsBit1(data[j],indexOf1))
*num1 ^= data[j];
else
*num2 ^= data[j];
}
//*num1,*num2就是那两个只出现一次的数字
}
//在整数num的二进制表示中找到最右边是1的位
int FindFirstBitIs1(int resultExclusiveOR){
int indexBit = 0;
while (((num & 1)==0) && (indexBit < 8*sizeof(int))){
num = num >> 1;
++indexBit;
}
return indexBit;
}
//判断整数num的二进制表示中从右数起indexBit位是不是1
bool IsBit1(int num,unsigned int indexBit){
num = num >> indexBit;
return (num & 1);
}
面试题41:和为s的两个数字 VS 和为s的连续正数序列
/*题目一:输入一个递增排序的数组和一个数字s,在数组中查找两个数其和为s*/
//运用两个指针,分别指向数组第一个和最后一个数字,时间复杂度O(n)
bool FindNumbersWithSum(int data[],int length,int sum,int *num1,int *num2){
bool found = false;
if (data==NULL || length<2)
return found;
int ahead = length-1;
int behind = 0;
while (ahead>behind){
long long curSum = data[ahead] + data[behind];
if (curSum == sum){
*num1 = data[ahead];
*num2 = data[behind];
found = true;
}
else if (curSum<sum)
behind++;
else
ahead--;
}
return found;
}
/*题目二:输入一个正整数s,输出所有和为s的连续正整数序列(至少含有两个数)*/
//类似于题目一,考虑用两个数表示序列的最小值和最大值,初始为1和2,依次往里加或减一个数
void FindContinuousSequence(int sum){
if (sum <3)
return;
int small = 1;
int big = 2;
int middle = (1+sum)/2;
int curSum = small+big;
while (small < big){
if (curSum == sum)
PrintContinuousSequence(small,big);
while (curSum > sum && small < big){
curSum -= small;
small ++;
if (curSum == sum)
PrintContinuousSequence(small,big);
}
big++;
curSum +=big;
}
}
void PrinContinuousSequence(int small,int big){
for (int i=small;i<=big;++i)
printf("%d",i);
printf("\n");
}
面试题42:翻转单词顺序 VS 左旋转字符串
/*题目一:输入一个英文句子:翻转单词的顺序但保持单词内部字符顺序不变*/
char* ReverseSentence(char *pData){
if (pData==NULL)
return NULL;
char *pBegin = pData;
char *pEnd = pData;
while(*pEnd!='\0')
pEnd++;
pEnd--;
//翻转整个句子所有的字符
Reverse(pBegin,pEnd);
//翻转每个单词
pBegin = pEnd = pData;
while (*pBegin !='\0'){
if (*pBegin==' '){
pBegin++;
pEnd++;
}
else if (*pEnd=' ' || pEnd=='\0'){
Reverse(pBegin,--pEnd);
pBegin = ++pEnd;
}
else
pEnd++;
}
return pData;
}
void Reverse(char *pBegin,char *pEnd){
if (pBeign==NULL || pEnd==NULL)
return;
while (pBegin<pEnd){
char temp =*pBegin;
*pBeign = *pEnd;
*pEnd = temp;
pBegin++;
pEnd--;
}
}
/*题目二:左旋转字符串,将字符串前面的若干位移到字符串尾部*/
//将字符串分为前若干位和剩余字符两部分,分别翻转这两部分,再将翻转后的整个字符串翻转
char* LeftRotateString(char *pStr,int n){
if (pStr!=NULL){
int nLength = static_cast<int>(strlen(pStr));
if (nLength>0 && n>0 && n<nLength){
char *pFirstStart = pStr;
char *pFirstEnd = pStr + n -1;
char *pSecondStart = pStr + n;
char *pSecondEnd = pStr + nLength -1;
Reverse(pFirstStart,pFirstEnd);
Reverse(pSecondStart,pSecondEnd);
Reverse(pFirstStart,pSecondEnd);
}
}
return pStr;
}
2. 抽象建模能力
选择合理的数据结构表述问题——>分析模型中的内在规律,并用编程语言表述这种规律
面试题43:n个骰子的点数(n个骰子扔在地上,朝上一面的点数之和为s。打印出s的所有可能值及对应概率)
**分析:**n个骰子的最小和是n,最大的和是6n,则共有6n-n+1中s的情况;总共6^n中组合情况
/*解法一:基于递归求骰子点数,将n个骰子分为两部分:1个和剩余n-1骰子,再将n-1个骰子分为两部分:1个和剩余n-2骰子。。。*/
int g_maxVlaue = 6;
void PrintProbability(int number){
if (number<1)
return;
int maxSum = number*g_maxValue;
int *pProbabilities = new int[maxSum-number+1];
for (int i=number;i<=maxSum;++i)
pProbabilities[i-number] = 0;
Probability(number,pProbabilities);
double total = pow((double)g_maxValue,number);
for (int i=number;i<=maxSum;++i){
double ratio = (double)pProbabilities[i-number]/total;
printf("%d:\%e\n",i,ratio);
}
delete[] pProbabilities;
}
void Probability(int number,int *pProbabilities){
for (int i=1;i<=g_maxValue;++i)
Probability(number,number,i, Probabilities);
}
void Probability(int original,int current,int sum,int *pProbabilities){
if (current == 1)
pProbabilities[sum-original]++;
else{
for (int i=1;i<=g_maxValue;++i)
Probability(original,current-1,i+sum,Probabilities);
}
}
//解法二:基于循坏求骰子点数
void PrintProbability(int number){
if (number<1)
return;
int *pProbabilities[2];
pProbabilities[0] = new int [g_maxValue*number+1];
pProbabilities[1] = new int [g_maxValue*number+1];
for (int i=0;i<g_maxValue*number+1;++i){
pProbabilities[0][i] = 0;
pProbabilities[1][i] = 0;
}
int flag = 0;
for (int i=1;i<=g_maxValue;++i)
pProbabilities[0][i] = 1;
for (int k=2;k<=number;++k){
for (int i=0;i<k;++i)
pProbabilities[1-flag][i] = 0;
for (int i=k;i<=k*g_maxValue;++i){
pProbabilities[1-flag][i] = 0;
for (int j=1;j<=i && j<=g_maxValue;++j)
pProbabilities[1-flag][i] += pProbabilities[flag][i-j];
}
flag = 1-flag;
}
double total = pow((double)g_maxValue,number);
for (int i=number;i<=maxSum;++i){
double ratio = (double)pProbabilities[flag][i]/total;
printf("%d:\%e\n",i,ratio);
}
delete[] pProbabilities[0];
delete[] pProbabilities[1];
}
面试题44:判断给定的5个数是不是扑克牌的顺子(大王小王可以看作任意数字)
/*数字排序,统计0(大王小王)的个数以及数组相邻数字之间的空缺数;如果有重复数字(除0以外)出现就不可能是顺子*/
bool IsContinuous(int *numbers,int length){
if (numbers==NULL || lenfth<5)
return false;
qsort(numbers,length,sizeof(int),compare);
int numberOfZero = 0;
int numberOfGap = 0;
for (int i=0;i<length && numbers[i]==0;++i)
++numberOfZero;
int small = numberOfZero;
int big = small + 1;
while (big>small){
if (numbers[small] == numbers[big])
return false;
numberOfGap += numbers[big] - numbers[small] -1;
samll = big;
++big;
}
return (numberOfGap>numberOfZero)?false:true;
}
int compare(const void *num1,const void *num2){
return *(int *)num1-*(int*)num2;
}
面试题45:圆圈中最后剩下的数字
/*解法一:使用list构建环形链表,时间复杂度O(m+n),空间复杂度O(n)*/
int LastRemaining(unsigned int n,unsigned int m){
if (n<1 || m><1)
return -1;
unsigned int i=0;
list<int> numbers;
for (unsigned 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){
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);
}
/*解法二:归纳法推导递归公式f(n,m)=0,n=1;f(n,m)=[f(n-1,m)+m]%n,n>1,时间复杂度O(n),空间复杂度O(1)*/
int LastRemaining(unsigned int n,unsigned int m){
if (n<1 || m><1)
return -1;
int last = 0;
for (int i=2;i<=n;++i)
last = (last+m)%i;
return last;
}
3. 发散思维能力
从不同的方向,侧面和层次提出创新的解法,跳出常规。
面试题46:求1+2+…+n(不能使用乘除法,for,while,if,switch等条件控制语句以及条件判断语句)
//解法一:利用类的构造函数和staic成员性质实现循环
class Temp{
private:
static unsigned int N;
static unsigned int Sum;
public:
Temp(){++N;Sum+=N;}
static void Reset(){N=0;Sum=0;}
static unsigned int GetSum(){return Sum;}
}
unsigned int Temp::N = 0;
unsigned int Temp::Sum = 0;
unsigned int Sum_Solution1(unsigned int n){
Temp::Reset();
Temp *a = new Temp[n];
delete[] a;
a = NULL;
return Temp::GetSum();
}
//解法二:利用虚函数实现递归
class A;
A *Array[2];
class A{
public:
virtual unsigned int Sum(unsigned int n){
return 0;
}
}
class B:public A{
public:
virtual unsigned int Sum(unsigned int n){
return Array[!!n]->Sum(n-1)+n;
}
}
unsigned int Sum_Solution2(unsigned int n){
A a;
B b;
Array[0] = &a;
Array[1] = &b;
unsigned int value = Array[1]->Sum(n);
return value;
}
//解法三:利用函数指针实现递归
typedef unsigned int (*fun)(unsigned int);
unsigned int Solution_Teminator(unsigned int n){
return 0;
}
unsigned int Sum_solution3(unsigned int n){
static fun f[2] = {Solution_Teminator,Sum_Solution};
return n+f[!!n](n-1);
}
//解法四:利用模板类型实现递归
template <unsigned int> struct Sum_Solution4{
enum Value {N=Sum_Solution4<n-1>::N+n};
};
template<> struct Sum_Solution4<1>{
enum Value {N=1};
}
//Sum_Solution<n>::N就是所求结果,但是n不能太大,n必须是在编译期间确定的常量,不能动态输入
//解法五:利用逻辑与的短路特性实现递归终止
int Sum_Solution5(unsigned int n) {
unsigned int sum = n;
bool ans = (n>0) && ((sum += Sum_Solution(n - 1))>0);
return sum;
};
面试题47:不用加减乘除实现加法
//利用位运算:第一步利用异或实现相加不进位;第二步利用与运算只记进位;第三步:重复前两步直至不产生进位
int Add(int num1,int num2){
int sum,carry;
do{
sum = num1 ^ num2;
carry = (num1 & num2) <<1;
num1 = sum;
num2 = carry;
}while(num2!=0);
return num1;
}
/*不用新变量交换两个变量的值:
解法一:基于加减法:a=a+b;b=a-b;a=a-b;
解法二:基于异或:a=a^b;b=a^b;a=a^b*/
面试题48:不能被继承的类
C#中sealed关键字修饰的类不能被继承;Java中final关键字修饰的类不能被继承
/*将构造函数和析构函数设为私有函数*/
//解法一:通过静态函数实现构造和析构,只能在堆上创建实例
class SealedClass1{
private:
SealedClass1(){}
~SealedClass1(){}
public:
static SealedClass1* GetInstance(){return new SealedClass1();}
static void DeleteInstance(SealedClass1* pInstance){ delete pInstance;}
}
//解法二:利用虚拟继承和友元,可移植性不好
template <class T> class MakeSealed{
friend T;
private:
MakeSealed(){}
~MakeSealed(){}
}
class SealedClass2 : virtual public MakeSealed<SealedClass2>{
public:
SealedClass2(){}
~ SealedClass2(){}
}