# 剑指offer——解决面试题的思路
1. 画图让抽象问题形象化
面试题19:二叉树的镜像
struct BinaryTreeNode{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
/*前序遍历树的每个结点,若遍历到的结点有子结点,就交换他的两个子结点;当交换完所有非叶子结点的左右子结点后,就得到了树的镜像。*/
void MirrorRecursively(BinaryTreeNode* pNode){
if (pNode==NULL)
return;
if (pNode->m_pLeft==NULL && pNode->m_pRight==NULL)
return;
BinaryTreeNode *pTemp = pNode->m_pLeft;
pNode->m_pLeft = pNode->m_pRight;
pNode->m_pRight = pTemp;
if (pNode->m_pLeft)
MirrorRecursively(pNode->m_pLeft);
if (pNode->m_pRight)
MirrorRecursively(pNode->m_pRight);
}
//循环解法
void Mirror(TreeNode *pRoot) {
if (pRoot == nullptr || (pRoot->left == nullptr && pRoot->right == nullptr))
return;
stack<TreeNode*> stackNodes;
stackNodes.push(pRoot);
while (stackNodes.size() > 0)
{
TreeNode *pNode = stackNodes.top();
stackNodes.pop();
TreeNode *pTemp = pNode->left;
pNode->left = pNode->right;
pNode->right = pTemp;
if (pNode->left != nullptr)
stackNodes.push(pNode->left);
if (pNode->right != nullptr)
stackNodes.push(pNode->right);
}
}
面试题20:顺时针打印矩阵
考虑情况:数组的行列数(相等;行数大于列数;行数小于列数)
void PrintMatrixClockwisely(int** number,int columns,int rows){
if (number==NULL || columns<=0 || rows<=0)
return;
int start=0;
while (rows>start*2 && columns>start*2){
PrintMatrixCircle(number,columns,rows,start);
start++;
}
}
void PrintMatrixCircle(int** number,int columns,int rows,int start){
int endY = columns-start-1;
int endX = rows-start-1;
//从左到右打印一行
for (int i=start;i<=endY;++i){
int num = number[start][i];
printNumber(number);
}
//从上到下打印一行
if (start<endX){
for (int i=start+1;i<=endX;++i){
int num = number[i][endY];
printNumber(number);
}
}
//从右到左打印一行
if (start<endX && start<endY){
for (int i=endY-1;i>=start;--i){
int num = number[endX][i];
printNumber(number);
}
}
//从下到上打印一行
if (start<endX && start<endY){
for (int i=endX-1;i>=start+1;--i){
int num = number[i][startY];
printNumber(number);
}
}
}
2. 举例让抽象问题具体化
面试题21:包含min函数的栈(定义栈的数据结构,调用min,pop,push的时间复杂度均为O(1))
/*使用辅助栈存储栈每次进栈后对应的最小元素*/
//m_data是数据栈,m_min是辅助栈
template <typename T> void StachWithMin<T>::push(const T &value){
m_data.push(value);
if (m_min.size()==0 || value <m_min.top())
m_min.push(value);
else
m_min.push(m_min.top());
}
template <typename T> void StachWithMin<T>::pop(){
assert(m_data.size()>0 && m_min.size()>0)
m_data.pop();
m_min.pop();
}
template <typename T> const T& StachWithMin<T>::min() const{
assert(m_data.size()>0 && m_min.size()>0)
return m_min.top();
}
面试题22:栈的压入,弹出序列(给定两个序列,压入序列,判断另一个是否为弹出序列)
/*建立辅助栈,按第一个序列将数字压入栈,并按第二个序列一次从栈中弹出数字。若即将弹出的元素是栈顶元素,则直接弹出;否则把压入序列的数字进栈直到栈顶元素是要弹出的数字;若所有数字均已入栈还没找到要弹出的数字,那么该序列不可能是一个弹出序列。*/
bool IsPopOrder(const int *pPush,const int *pPop,int nLength){
bool bPossible = false;
if (pPush!=NULL && pPop!=NULL && nLength>0){
const int* pPushNext = pPush;
const int* pPopNext = pPop;
stack<int> stackData;
while (pPopNext-pPop<nLength){
while (stackData.empty() || stackData.top()!=*pPopNext){
if (pPushNext-pPush)<nLength)
break;
stackData.push(*pPushNext++);
}
if (stackData.top()!=*pPopNext)
break;
stackData.pop();
pPopNext++;
}
if (stackData.empty() && pPopNext-pPop==nLength)
bPossible = true;
}
return bPossible;
}
面试题23:从上往下打印二叉树(层序遍历,广度优先遍历)
struct BinaryTreeNode{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
/*从根结点开始,若打印的结点有子结点,将子结点压入队尾;接下来打印队首结点元素,重复前面,直到队列为空。*/
void PrintFromTopToBottom(BinaryTreeNode* pTreeRoot){
if (pTreeRoot==NULL)
return;
deque<BinaryTreeNode*} dequeTreeNode;
dequeTreeNode.push(pTreeRoot);
while (!dequeTreeNode.empty()){
BinaryTreeNode* pNode = dequeTreeNode.front();
dequeTreeNode.pop_front();
printf("%d ",pNode->m_nValue);
if (pNode->m_pLeft!=NULL)
dequeTreeNode.push_back(pNode->m_pLeft);
if (pNode->m_pRight!=NULL)
dequeTreeNode.push_back(pNode->m_pRight);
}
}
举一反三:广度优先遍历有向图或树,利用队列:把起始结点(树的根结点)放入队列。接下来每一次从队列的头部取出一个结点,遍历这个结点能达到的结点(树中的子结点)都依次放入队列。重复上述过程,直到队列为空。
面试题24:判断数组是否是二叉搜索树的后序遍历序列
/*后序遍历的最后一个元素为根结点,前面的数字应该可以分为两部分:小于根结点(左子树),大于根结点(右子树);再递归判断两个子数组即可*/
bool VerifySquenceofBST(int sequence[],int lenght){
if (sequence==NULL || length<=0)
return false;
int root = squence[length-1];
//搜索左子树:在二叉搜索树中左子树结点小于根结点
int i=0;
for (;i<length-1;++i){
if (squence[i]>root)
break;
}
//搜索右子树:在二叉搜索树中右子树结点大于根结点
int j=i;
for (;j<length-1;++j){
if (squence[i]<root)
return false;
}
//判断左子树是不是二叉搜索树
bool left = true;
if (i>0)
left = VerifySquenceofBST(sequence,i);
//判断右子树是不是二叉搜索树
bool right = true;
if (i>0)
right = VerifySquenceofBST(sequence+i,length-1-i);
return (left && right);
}
面试题25:打印二叉树中和为某一值的所有路径(从根结点到叶子结点经过的结点形成一条路径)
/*采用前序遍历和栈:将遍历到结点压入栈,并累加值得和;若当前结点为叶子结点且累加和等于给定值,则打印出来;若当前结点不是叶子结点,继续访问其子结点;当前结点访问结束后,回到其父结点并从栈中弹出当前结点并减去其值*/
struct BinaryTreeNode{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
void FindPath(BinaryTreeNode *pRoot,int expectedSum){
if (pRoot==NULL)
return;
vector<int> path;
int currentSum = 0;
FindPath(pRoot,expectedSum,path,currentSum);
}
void FindPath(BinaryTreeNode *pRoot,int expectedSum,vect<int> & path,int currentSum){
currentSum += pRoot->m-nValue;
path.push_back(pRoot->m-nValue);
bool isLeafNode = (pRoot->m_pLeft==NULL) && (pRoot->m_pRight==NULL);
//若当前结点为叶子结点且累加和等于给定值,则打印出来
if (currentSum == exceptedSum && isLeaf){
printf("A Path is found: ");
vector<int>::iterator iter = path.begin();
for (;iter!=path.end();++iter)
printf("%d\t",*iter);
printf("\n");
}
//若当前结点不是叶子结点,则继续访问其子结点
if (pRoot->m_pLeft!=NULL)
FindPath(pRoot->m_pLeft,exceptedSum,path,currentSum);
if (pRoot->m_pRight!=NULL)
FindPath(pRoot->m_pRight,exceptedSum,path,currentSum);
//在返回父结点前,在路径上删除当前结点
path.pop_back();
currentSum -= pRoot->m-nValue;
}
3. 分解让复杂问题简单化(分治法)
面试题26:复杂链表的复制
struct ComplexListNode{
int m_nValue;
ComplexListNode *m_pnext;//下一个结点
ComplexListNode *m_pSibling;//兄弟
}
//第一步:复制原始链表的每个结点N创建新结点N'链接到N的后面
void CloneNodes(ComplexListNode *pHead){
ComplexListNode *pNode = pHead;
while (pHead!=NULL){
ComplexListNode *pCloned = new ComplexListNode();
pClone->m_nValue = pNode->m_nValue;
pClone->m_pNext = pNode->m_pNext;
pClone->m_pSibling = NULL;
pNode->m_pNext = pCloned;
pNode = pNode->m_pNext;
}
}
//第二步:如果原始链表的结点N的m_pSibling指向结点S,则它对应的复制结点N'的m_pSibling指向结点S'
void ConnectSiblingNodes(ComplexListNode *pHead){
ComplexListNode *pNode = pHead;
while (pNode!=NULL){
ComplexListNode *pCloned = pNode->m_pNext;
if (pNode->m_pSibling!=NULL){
pCloned->m_pSibling = p->m_pSibling->m_pNext;
}
pNode = pClone->m_pNext;
}
}
//第三步:将第二步得到的链表拆分为两个链表:奇数位置上的结点组成原始链表,偶数位置上的结点组成复制链表
ComplexListNode * ReConnectNodes(ComplexListNode* phead){
ComplexListNode *pNode = pHead;
ComplexListNode *pClonedHead = NULL;
ComplexListNode *pClonedNode = NULL;
if (pNode!=NULL){
pClonedHead = pClonedNode = pNode->m_pNext;
pNode->m_pNext = pClonedNode->m_pNext;
pNode = pNode->m_pNext;
}
while (pNode!=NULL){
pClonedNode->m_pNext = pNode->m_pNext;
pClonedNode = pClonedNode->m_pNext;
pNode->m_pNext = pClonedNode->m_pNext;
pNode = pNode->m_pNext;
}
}
//完整过程
ComplexListNode * Clone(ComplexListNode * pHead){
CloneNodes(pHead);
ConnectSiblingNodes(pHead);
return ReConnectNodes(phead);
}
面试题27:二叉搜索树调整指针指向构成排序的双向链表
struct BinaryTreeNode{
int m_nValue;
BinaryTreeNode * m_pLeft;
BinaryTreeNode * m_pRight;
};
BinaryTreeNode * Convert(BinaryTreeNode * pRootOfTree){
BinaryTreeNode * pLastNodeInList = NULL;
ConvertNode(pRootOfTree,&pLastNodeInList);
BinaryTreeNode * pHeadOfList = pLastNodeInList;
while (pHeadOfList!=NULL && pHeadOfList->m_pLeft!=NULL)
pHeadOfList = pHeadOfList->m_pLeft;
return pHeadOfList;
}
void ConvertNode(BinaryTreeNode * pNode,BinaryTreeNode **pLastNodeInList){
if (pNode==NULL)
return;
BinaryTreeNode * pCurrent = pNode;
if (pCurrent->m_pLeft!=NULL)
ConvertNode(pCurrent->m_pLeft,pLastNodeInList);
pCurrent->m_pLeft = *pLastNodeInList;
if (*pLastNodeInList!=NULL)
(*pLastNodeInList)->m_pRight = pCurrent;
*pLastNodeInList = pCurrent;
if (pCurrent->m_pRight!=NULL)
ConvertNode(pCurrent->m_pRight,pLastNodeInList);
}
面试题28:字符串的排列
/*将字符串分为两部分:第一个字符与剩余字符串,再求其他字符串的排列;逐个交换第一个字符与后面的字符*/
void Permutation(char* pStr){
if (pStr==NULL)
return;
Permutation(pStr,pStr);
}
void Permutation(char* pStr,char* pBegin){
if (*pBegin=='\0')
printf("%s\n",pStr);
else{
for (char *pCh = pBegin;*pCh!='\0';++pCh){
char temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
Permutation(pStr,pBegin+1);
temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
}
}
}
//abc acb bac bca cba cab
举一反三:如果面试题是按照一定要求摆放若干个数字,可以先求出这些数的所有排列,再一一判断哪些排列符合要求(比如:象棋的摆放)