面试题3:二维数组中的查找
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
通过分析发现规律:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数组所在的行。也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。
bool Find(int* matrix,int rows,int columns,int number)
{
bool found=false;
if(matrix != NULL && rows>0&&columns>0)
{
int row=0;
int column=columns-1;
while(row<rows&&column>=0)
{
if(matrix[row*columns+column]==number)
{
found=true;
break;
}
else if(matrix[row*columns+column]>number)
--column;
else
++row;
}
}
return found;
}
面试题4:替换空格
题目:请实现一个函数,把字符串中的每个空格替换成“%20”。例如输入“We are happy.”,则输出“We%20are%20happy.”。
时间复杂度为O(n)的解法:
我们可以先遍历一次字符串,这样就能统计出字符串中空格的总数,并可以由此计算出替换之后的字符串的总长度。每替换一个空格,长度增加2,因此替换以后字符串的长度等于原来的长度加上2乘以空格数目。
我们从字符串的后面开始复制和替换。首先准备两个指针,P1和P2。P1指向原始字符串的末尾,而P2指向替换之后的字符串的末尾。接下来我们向前移动指针P1,逐个把它指向的字符复制到P2指向的位置,直到碰到第一个空格为止。碰到第一个空格之后,把P1向前移动一格,在P2之前插入字符串“%20”。由于“%20”的长度为3,同时也把P2向前移动3格。接着向前执行。
//length 为字符数组string的总容量
void ReplaceBlank(char string[],int length)
{
if(string == NULL||length<=0)
return;
//originalLength为字符串string的实际长度
int originalLength = 0;
int numberOfBlank =0;
int i=0;
while(string[i]!='\0')
{
++originalLength;
if(string[i] == ' ')
++numberOfBlank;
++i;
}
/*newLength为把空格替换成‘%20’之后的长度*/
int newLength=originalLength+numberOfBlank*2;
if(newLength>length)
return;
int indexOfOriginal = originalLength;
int indexOfNew = newLength;
while(indexOfOriginal >= 0 && indexOfNew > indexOfOriginal)
{
if(string[indexOfOriginal]==' ')
{
string[indexOfNew--] = '0';
string[indexOfNew--] = '2';
string[indexOfNew--] = '%';
}
else
{
string[indexOfNew--]=string[indexOfOriginal];
}
-- indexOfOriginal;
}
}
链表
如果单向链表的结点定义如下:
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
那么往该链表的末尾中添加一个结点的C++代码如下:
void AddToTail(ListNode** pHead,int value)
{
ListNode* pNew = new ListNode();
pNew->m_nValue = value;
pNew->m_pNext = NULL;
if(*pHead == NULL)
{
*pHead = pNew;
}
else
{
ListNode* pNode = *pHead;
while(pNode->m_pNext != NULL)
pNode=pNode->pNext;
pNode->m_pNext = pNew;
}
}
在上面的代码中,函数的第一个参数pHead是一个指向指针的指针。当我们往一个空链表中插入一个结点时,新插入的结点就是链表的头指针。由于此时会改动头指针,因此必须把pHead参数设为指向指针的指针,否则出了这个函数pHead仍然是一个空指针。
下面是在链表中找到第一个含有某值的结点并删除该结点的代码:
void RemoveNode(ListNode** pHead,int value)
{
if(pHead == NULL || *phead == NULL)
return;
ListNode* pToBeDeleted = NULL;
if((*pHead)->m_nValue == value)
{
pToBeDeleted = *pHead;
*pHead=(*pHead)->m_pNext;
}
else
{
ListNode* pNode = *pHead;
while(pNode->m_pNext != NULL && pNode->m_pNext->m_nValue != value)
pNode = pNode->m_pNext;
if(pNode->m_pNext != NULL && pNode->m_pNext-m_nValue==value)
{
pToBeDeleted = pNode->m_pNext;
pNode->m_pNext=pNode->m_pNext->m_pNext;
}
}
if(pToBeDeleted != NULL)
{
delete pToBeDeleted;
pToBeDeleted = NULL;
}
}
面试题5:从尾到头打印链表
题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
链表定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
遍历的顺序是从头到尾的顺序,可输出的顺序却是从尾到头。也就是说第一个遍历到的结点最后一个输出,而最后一个遍历到的结点第一个输出。这就是典型的“后进先出”,我们可以用栈来实现这种顺序。这种思路的代码实现如下:
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
void PrintListReversingly_Iteratively(ListNode* pHead)
{
std::stack<ListNode*> nodes;
ListNode* pNode = pHead;
while(pNode != NULL)
{
nodes.push(pNode);
pNode = pNode->m_pNext;
}
while(!nodes.empty())
{
pNode=nodes.top();
printf("%d\t",pNode->m_nValue);
nodes.pop();
}
}
使用递归实现如下:
void PrintListReversingly_Recursively(ListNode* pHead)
{
if(pHead != NULL)
{
if(pHead->m_pNext != NULL)
{
PrintListReversingly_Recursively(pHead->m_pNext);
}
printf("%d\t",pHead->m_nValue);
}
}
上面的基于递归的代码看起来很简洁,但有个问题:当链表非常长的时候,就会导致函数调用的层级很深,从而有可能导致函数调用栈溢出。显式用栈基于循环实现的代码鲁棒性要好些。
参考书籍:《剑指Offer》