程序员面试题精选100题(01)-把二元查找树转变成排序的双向链表[数据结构]
题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。
比如将二元查找树
10
/ \
6 14
/ \ / \
4 8 12 16
转换成双向链表
4=6=8=10=12=14=16。
解:递归。左子树递归为排序双向链表,右子树递归为排序双向链表,然后和根节点连接。
此时左子树需返回最大节点,右子树需返回最小节点。
注意:返回左子树最大、右子树最小需在递归左右子树之前进行,这样找最大最小类似于折半;若采用何海涛解法先递归左右子树在查找最大或最小类似于顺序查找,增加查找次数。比如返回以10为root的最小,10-6-4即结束;否则需10-8-6-4。
递归函数定义为
// Input: pNode - the head of the sub tree
// asRight- whether pNode is the right child of its parent
// Output: if asRight is true, return the least node in the sub-tree
// else return the greatest node in thesub-treeBSTreeNode* BSTreeNode* ConvertNode(BSTreeNode*root,bool asRight);
结果为pLeast = ConvertNode(root,true);
代码:
struct BSTreeNode // a node in the binary search tree
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
BSTreeNode(int value):m_nValue(value),m_pLeft(NULL),m_pRight(NULL){}
};
/*if right return the least,else return the greatest*/
BSTreeNode* ConvertNode(BSTreeNode* root,bool isRight)
{
if(NULL == root)
return NULL;
BSTreeNode* ptmp=root ,*preturn;
if(isRight)
{
while(ptmp->m_pLeft)
{
ptmp = ptmp->m_pLeft;
}
preturn = ptmp;
}
else
{
while(ptmp->m_pRight)
{
ptmp = ptmp->m_pRight;
}
preturn = ptmp;
}
BSTreeNode* pgreast= NULL;
if(root->m_pLeft)
{
pgreast= ConvertNode(root->m_pLeft,false);
}
BSTreeNode* pleast= NULL;
if(root->m_pRight)
{
pleast= ConvertNode(root->m_pRight,true);
}
root->m_pLeft = pgreast;
if(pgreast)
pgreast->m_pRight = root;
root->m_pRight = pleast;
if(pleast)
pleast->m_pLeft = root;
return preturn;
}
程序员面试题精选100题(02)-设计包含min函数的栈[数据结构]
题目:定义栈的数据结构,要求添加一个min函数,能够得到栈的最小元素。要求函数min、push以及pop的时间复杂度都是O(1)。
解法:选取另一个栈存储最小元素的索引,因为栈元素很可能比较大,拷贝代价较大,用索引可以减小空间。例如入栈元素为,5-4-3-2-1,维护最小元素的栈将依次push 0-1-2-3-4;入栈为1-2-3-4-5,最小元素的栈将只push 0,这不同于何海涛解法,可以减少另一个栈空间使用,不过是用时间来换取的。Push函数中value将和stack[min_stack.top()]比较,如果大于不入min_stack栈,否则min_stack.push(stack.size()-1);pop函数中stack.top()也要和stack[min_stack.top()]比较,如果大于直接pop否则stack和min_stack都pop。答案中只有push中含有比较。
扩展:有这道题想起由两个stack构建queue;由两个queue构建stack。
如果能注意一些细节无疑能在面试中加分。比如我在上面的代码中做了如下的工作:
· 用模板类实现。如果别人的元素类型只是int类型,模板将能给面试官带来好印象;
· 两个版本的top函数。在很多类中,都需要提供const和非const版本的成员访问函数;
· min函数中assert。把代码写的尽量安全是每个软件公司对程序员的要求;
· 添加一些注释。注释既能提高代码的可读性,又能增加代码量,何乐而不为?
总之,在面试时如果时间允许,尽量把代码写的漂亮一些。说不定代码中的几个小亮点就能让自己轻松拿到心仪的Offer。
--------------------------------------------------------------------------------------------------------------------------------------
程序员面试题精选100题(03)-子数组的最大和[算法]
分析:1.暴力C(2,n)个子数组,每个子数组遍历求和,总复杂度O(n^3)
2.优化下,f(I,j)=f(I,j-1)用空间换时间填充矩阵右上角,下次求f(I,j)可以直接使用f(I,j-1)复杂度为O(n^2)
3.在优化下,递归分治,O(n*logn)
4.最终该问题是个动态规划问题O(n):
定义DP[i]为以i为结尾的最大连续和,状态转移方程为
结果为:max{DP[i]}0<=i<=n-1
代码:
#include <iostream> #include <climits> using namespace std; int maxsum(int arry[], size_t len, size_t& beg, size_t& end) { if(len <= 0) return INT_MIN; int *dp = NULL; dp = new int[len]; if(NULL == dp) { cout << "new failed..." << endl; return INT_MIN; } int max = arry[0]; dp[0] = arry[0]; beg = 0; end = 0; for(int i=1; i<len; ++i) { if(dp[i-1] >= 0) { dp[i] = dp[i-1] + arry[i]; } else { dp[i] = arry[i]; } if(max < dp[i]) { max = dp[i]; end = i; } } //check beg beg = end; while(beg>=0 && dp[beg]>=0)//= the longest { beg--; } if(beg<0) { beg++; } delete[] dp; dp = NULL; return max; } int main() { //int arry[] = {1,-2,3,10,-4,7,2,-5}; //int arry[] = {-1,-2,-3,-4,-5,-6,-7}; //int arry[] = {-7,-6,-5,-4,-3,-2}; int arry[] = {-7,-7,-7,-7}; int len = sizeof(arry)/sizeof(int); size_t beg,end; int max = maxsum(arry,len,beg,end); cout << "max: " << max << endl; cout << "range: " << beg << " to " << end << endl; return 0; }
int nCurSum = nGreatestSum = 0; for(unsigned int i = 0; i < nLength; ++i) { nCurSum += pData[i]; // if the current sum is negative, discard it if(nCurSum < 0) nCurSum = 0; // if a greater sum is found, update the greatest sum if(nCurSum > nGreatestSum) nGreatestSum = nCurSum; }
函数的返回值不是子数组和的最大值,而是一个判断输入是否有效的标志。如果函数返回值的是子数组和的最大值,那么当输入一个空指针是应该返回什么呢?返回0?那这个函数的用户怎么区分输入无效和子数组和的最大值刚好是0这两中情况呢?基于这个考虑,本人认为把子数组和的最大值以引用的方式放到参数列表中,同时让函数返回一个函数是否正常执行的标志。
针对这个问题我的代码如果输入无效返回INT_MIN。
//if all data are negative, find the greatest element in the array
if(nGreatestSum == 0)
{
nGreatestSum = pData[0];
for(unsigned int i = 1; i < nLength; ++i)
{
if(pData[i] > nGreatestSum)
nGreatestSum = pData[i];
}
}
针对这个问题可以最初赋给nGreatestSum=INT_MIN或者参考以上代码赋值arry[0],从1开始递推,而避免在循环全是负数的情况。
扩展问题: 如何求最大连续乘积?
----------------------------------------------------------------------------------------------------------------------------------
程序员面试题精选100题(04)-二元树中和为某一值的所有路径[数据结构]
分析:树的一般都是递归解法
代码:
void findPath(Tree* root,vector<int>& path, int sum) { if(NULL == root) return; path.push_back(root->data); if(root->left==NULL && root->right==NULL && root==sum)//find leaf node { print(path); path.pop_back(); return; } if(root->left) { findPath(root->left,path,sum-root->data); } if(root->right) { findPath(root->right,path,sum-root->data); } path.pop_back(); }
我还是习惯sum-root->data,而不是答案中的加,我的代码只需要三个参数,除了path是引用其他值传递就好了。记得最后path.pop_back(),而且题目没说是正整数哦,所以不能剪枝,也不能通过sum<0就停止递归左右子树。。。
----------------------------------------------------------------------------------------------------------------------------------
程序员面试题精选100题(05)-查找最小的k个元素[算法]
题目:输入n个整数,输出其中最小的k个。
例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。
分析:这个就是topk问题,无论从时间复杂度还是数字较多不能一次读进内存,减少IO角度,堆排序都是很好的选择(大顶堆)我们自己从头实现一个最大堆需要一定的代码。我们还可以采用红黑树来实现我们的容器。红黑树通过把结点分为红、黑两种颜色并根据一些规则确保树是平衡的,从而保证在红黑树中查找、删除和插入操作都只需要O(logk)。在STL中set和multiset都是基于红黑树实现的。如果面试官不反对我们用STL中的数据容器,我们就直接拿过来用吧。何海涛给出了一种简易实现方法-set