class Singleton
{
public:
static Singleton& Instance()
{
static Singleton singleton;
return singleton;
}
private:
Singleton() { };
};
2. 考虑重用。
template <typename T> class Singleton { public: static T& Instance() { static T s_Instance; return s_Instance; } protected: Singleton(void) {} ~Singleton(void) {} private: Singleton(const Singleton& rhs) {} Singleton& operator = (const Singleton& rhs) {} }; class SingletonInstance : public Singleton<SingletonInstance> //“在需要重用该Singleton实现时,我们仅仅需要从Singleton派生并将Singleton的泛型参数设置为该类型即可。”
二. 二维数组查找(面试题3)3. 考虑多线程性能。
template <typename T> class Singleton { public: static T& Instance() { if (m_pInstance == NULL) { Lock lock; if (m_pInstance == NULL) { m_pInstance = new T(); atexit(Destroy); } return *m_pInstance; } return *m_pInstance; } protected: Singleton(void) {} ~Singleton(void) {} private: Singleton(const Singleton& rhs) {} Singleton& operator = (const Singleton& rhs) {} void Destroy() { if (m_pInstance != NULL) delete m_pInstance; m_pInstance = NULL; } static T* volatile m_pInstance; }; template <typename T> T* Singleton<T>::m_pInstance = NULL;
“写得很精彩。那你是否能逐行讲解一下你写的这个Singleton实现呢?”
“好的。首先,我使用了一个指针记录创建的Singleton实例,而不再是局部静态变量。这是因为局部静态变量可能在多线程环境下出现问题。”
“我想插一句话,为什么局部静态变量会在多线程环境下出现问题?”
“这是由局部静态变量的实际实现所决定的。为了能满足局部静态变量只被初始化一次的需求,很多编译器会通过一个全局的标志位记录该静态变量是否已经被初始化的信息。那么,对静态变量进行初始化的伪码就变成下面这个样子:”。
bool flag = false; if (!flag) { flag = true; staticVar = initStatic(); }
“那么在第一个线程执行完对flag的检查并进入if分支后,第二个线程将可能被启动,从而也进入if分支。这样,两个线程都将执行对静态变量的初始化。因此在这里,我使用了指针,并在对指针进行赋值之前使用锁保证在同一时间内只能有一个线程对指针进行初始化。同时基于性能的考虑,我们需要在每次访问实例之前检查指针是否已经经过初始化,以避免每次对Singleton的访问都需要请求对锁的控制权。”
“同时,”我咽了口口水继续说,“因为new运算符的调用分为分配内存、调用构造函数以及为指针赋值三步,就像下面的构造函数调用:”
SingletonInstance pInstance = new SingletonInstance();
“这行代码会转化为以下形式:”
SingletonInstance pHeap = __new(sizeof(SingletonInstance)); pHeap->SingletonInstance::SingletonInstance(); SingletonInstance pInstance = pHeap;
“这样转换是因为在C++标准中规定,如果内存分配失败,或者构造函数没有成功执行, new运算符所返回的将是空。一般情况下,编译器不会轻易调整这三步的执行顺序,但是在满足特定条件时,如构造函数不会抛出异常等,编译器可能出于优化的目的将第一步和第三步合并为同一步:”
SingletonInstance pInstance = __new(sizeof(SingletonInstance)); pInstance->SingletonInstance::SingletonInstance();
“这样就可能导致其中一个线程在完成了内存分配后就被切换到另一线程,而另一线程对Singleton的再次访问将由于pInstance已经赋值而越过if分支,从而返回一个不完整的对象。因此,我在这个实现中为静态成员指针添加了volatile关键字。该关键字的实际意义是由其修饰的变量可能会被意想不到地改变,因此每次对其所修饰的变量进行操作都需要从内存中取得它的实际值。它可以用来阻止编译器对指令顺序的调整。只是由于该关键字所提供的禁止重排代码是假定在单线程环境下的,因此并不能禁止多线程环境下的指令重排。”
“最后来说说我对atexit()关键字的使用。在通过new关键字创建类型实例的时候,我们同时通过atexit()函数注册了释放该实例的函数,从而保证了这些实例能够在程序退出前正确地析构。该函数的特性也能保证后被创建的实例首先被析构。其实,对静态类型实例进行析构的过程与前面所提到的在main()函数执行之前插入静态初始化逻辑相对应。”
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路:从二维数组的右上角的元素开始判断,因为此元素是它所在行的最大数,是它所在的列的最小数。如果它等于要查找的数字,则查找过程结束。如果它大于要查找的数字,则可以排除它所在的列。如果它小于要查找的数字,则可排除它所在的行。这样如果要查找的数字不在数组的右上角,则每次判断都可以排除一行或一列以缩小查找范围,直到找到要查找的数字,或者查找范围为空。
下图是在二维数组中查找7的示意图:
// 二维数组matrix
// 每一行都从左到右递增排序
// 每一列都从上到下递增排序
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;
}
题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值
思路:
1,栈,根据面试官需要是否要改变链表结构
2,递归,原理用到栈
3,可以改变链表结构,就把链头变链尾,改变指针方向
源程序
#include <stdio.h> #include <stdlib.h> #include "stack.h" //尾插法建立链表 void create_list(LinkList &L){ LinkList p,q; int e; L=(LinkList)malloc(sizeof(Node)); L->next=NULL; q=L; printf("建立链表以0结束\n"); scanf("%d",&e); while(e) { p=(LinkList)malloc(sizeof(Node)); p->data=e; p->next=NULL; q->next=p; q=p; scanf("%d",&e); } } //递归方法输出链表 void PrintReverse(LinkList &L) { if(L!=NULL) { if(L->next!=NULL) { PrintReverse(L->next); printf("%d\t",L->next->data); } } } int main() { LinkList L,q; int m; create_list(L); q=L; LiStack S; InitStack(S); L=L->next; printf("原链表:\n"); while(L) { Push(S,L->data); printf("%d\t",L->data); L=L->next; } printf("\n递归输出新链表:\n"); PrintReverse(q); printf("\n栈链表:\n"); while(!StackEmpty(S)) { Pop(S,m); printf("%d\t",m); } return 0; }
题目
输入某二叉树的前序遍历和中序遍历,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含有重复的数字。
例如,前序遍历序列:{1,2,3,7,3,5,6,8},中序遍历序列:{4,7,2,1,5,3,8,6}
答案
前序遍历:
前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
中序遍历:
中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根结点,最后遍历右子树。
#include <iostream>
using namespace std;
struct binary_tree_node{
int value;
binary_tree_node* left;
binary_tree_node* right;
};
binary_tree_node* binary_tree_constuct(int* preorder, int* inorder, int length);
int main()
{
int pre[8] = {1,2,4,7,3,5,6,8};
int in[8] = {4,7,2,1,5,3,8,6};
binary_tree_node* root = binary_tree_constuct(pre, in, 8);
}
binary_tree_node* construct_method(int* preorder, int* endpreorder, int* inorder, int* endinorder)
{
int root_value = preorder[0];
binary_tree_node* root = new binary_tree_node();
root->left = NULL;
root->right = NULL;
cout<<root_value<<" ";
if(preorder == endpreorder && inorder == endinorder)
return root;
int* rootIndex = preorder;
rootIndex++;
while(*rootIndex != root_value && rootIndex < endpreorder)
rootIndex++;
int left_len = rootIndex - preorder;
int* left_preorder_end = preorder + left_len;
//left
if(left_len > 0)
{
root->left = construct_method(preorder+1, left_preorder_end, inorder, rootIndex-1);
}
//right
if(left_len < endpreorder - preorder)
{
root->right = construct_method(left_preorder_end+1, endpreorder, rootIndex+1, endinorder);
}
return root;
}
binary_tree_node* binary_tree_constuct(int* preorder, int* inorder, int length)
{
if(preorder == NULL || inorder == NULL || length <= 0)
{
return NULL;
}
return construct_method(preorder, preorder+length-1, inorder,inorder+length-1);
}
五. 用两个栈实现一个队列,用两个队列实现一个栈(面试题7)
template <typename T>class CQueue
{
public:
CQueue(void);
~CQueue(void);
void appendtail(const T& node);
T deleteHead();
private:
stack<T> stack1;
stack<T> stack2;
};
解题思路:
插入操作在stack1中进行,删除操作在stack2中进行,如果stack2为空,则将stack1中的所有元素转移到stack2中。
代码实例:
#include<iostream>
#include<stdlib.h>
#include<stack>
using namespace std;
template <typename T>class CQueue
{
public:
CQueue(void);
~CQueue(void);
void appendtail(const T& node);
T deleteHead();
private:
stack<T> stack1;
stack<T> stack2;
};
//构造函数
template <typename T> CQueue<T>::CQueue(void)
{
}
//析构函数
template <typename T> CQueue<T>::~CQueue(void)
{
}
//插入元素
template <typename T> void CQueue<T>::appendtail(const T& node)
{
stack1.push(node);
}
//删除元素并返回
template <typename T> T CQueue<T>::deleteHead()
{
if(stack2.size()<=0)
{
while(stack1.size()>0)
{
stack2.push(stack1.top());
stack1.pop();
}
}
if(stack2.size()==0)
throw new exception("队列为空");
T head=stack2.top();
stack2.pop();
return head;
}
void main()
{
CQueue<int> queue;
queue.appendtail(1);
queue.appendtail(2);
queue.appendtail(3);
queue.appendtail(4);
int len=4;
while(len>0)
{
cout<<queue.deleteHead()<<endl;
--len;
}
system("pause");
}
#include<iostream>
#include<stdlib.h>
#include<stack>
#include<queue>
using namespace std;
template <typename T>class CStack
{
public:
CStack(void){};
~CStack(void){};
void push(const T& node);
T pop();
private:
queue<T> queue1;
queue<T> queue2;
};
//插入元素
template <typename T> void CStack<T>::push(const T& element)
{
if(queue1.size()>0)//如果queue1不为空则往queue1中插入元素
queue1.push(element);
else if(queue2.size()>0)//如果queue2不为空则往queue2中插入元素
queue2.push(element);
else//如果两个队列都为空,则往queue1中插入元素
queue1.push(element);
}
//删除元素
template <typename T> T CStack<T>::pop()
{
if(queue1.size()==0)//如果queue1为空
{
while(queue2.size()>1)//保证queue2中有一个元素,将其余元素保存到queue1中
{
queue1.push(queue2.front());
queue2.pop();
}
T& data=queue2.front();
queue2.pop();
return data;
}
else//如果queue2为空
{
while(queue1.size()>1)//保证queue2中有一个元素,将其余元素保存到queue1中
{
queue2.push(queue1.front());
queue1.pop();
}
T& data=queue1.front();
queue1.pop();
return data;
}
}
void main()
{
CStack<int> stack;
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
int len=4;
while(len>0)
{
cout<<stack.pop()<<" ";
--len;
}
system("pause");
}
题目1:写一个函数,输入n,其斐波那契数列的第n项。
斐波那契数列的定义如下:
方法1:使用递归解,时间复杂度是n的指数级别
斐波那契数列的定义就是递归的,我们根据定义可以很简单的写出代码。代码如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
//f(n)={0,1,1,2,3...} n>=0
int Fibonacci(int n)
{
if(n<=0)
return 0;
if(n==1)
return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
void main()
{
int f=Fibonacci(30);
cout<<f<<endl;
system("pause");
}
但是这样的方法存在明显的不足,该方法的时间复杂度是n的指数级别,随着n的增大,运算时间不可想象,比如说f(50)就要很久。时间复杂度之所以这么大,是因此计算过程中存在着重复计算。以f(10)为例,f(10)=f(9)+f(8),f(9)=f(8)+f(7)。其中的f(8)就是重复计算的。
方法2:开辟一个长度为(n+1)的数组,时间复杂度为O(n),空间复杂度为O(n)
前面我们计算斐波那契数列是从后往前计算的,就是计算f(n)=f(n-1)+f(n-2),然后再递归计算f(n-1),又是从后往前计算,就是因为这样的从后往前计算,所以才会有很多的重复计算。那么我们可以逆转思路,考虑从前往后计算。比如我们要计算f(4),那么我们就计算f(0)、f(1)、f(2)、f(3),将这些计算出来的值保存在一个数组arry[n+1]上,这样计算斐波那契数列就相当于是一个填表的过程。时间复杂度大大降低。代码实例如下:
int Fibonacci(int n)
{
if(n<=0)
return 0;
else if(n==1)
return 1;
else
{
//动态创建一个长度为(n+1)的数组
int *arry=new int[n+1];
arry[0]=0;
arry[1]=1;
for(int i=2;i<=n;i++)
{
arry[i]=arry[i-1]+arry[i-2];
}
int result=arry[n];
//因为动态创建的数组不会因为出了作用域,内存就会被释放。
//动态分配的数组将一直存在,直到程序显示释放它为止,因此这里使用delete []
//c++提供delete []表达式释放指针所指向的数组空间。
delete [] arry;
return result;
}
}
注意点:
- 因为不知道要求的f(n)中的n有多大,因此不能事先开辟一个数组,需要动态创建数组。而动态数组与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止。普通的数组变量,只要出了数组的作用于,其内存会自动释放。
- c++提供delete []表达式释放指针所指向的数组空间。delete [] arry;该语句回收了arry所指向的数组,把相应的内存返回给自由存储区。在关键字delete和指针arry之间的方括号[]是必不可少的:它告诉编译器该指针指向的是自由存储区中的数组,而非单个对象。delete arry只释放了arry指针所指向的内存地址,理论上来说会少释放了内存空间,从而产生内存泄露。
方法3:优化方法2,空间复杂度为O(1),时间复杂度为O(n)
在方法2中,我们保存了每一个中间变量,但是仔细观察我们可以发现没有必要保存每一个中间变量,我们只需要保存两个临时变量即可完成斐波那契数列的计算。具体代码试下如下:
int Fibonacci(int n)
{
if(n<=0)
return 0;
else if(n==1)
return 1;
else
{
//当n>=2时,初始化pre=f(0)=0,post=f(1)=1,f(n)=0;
int pre=0;
int post=1;
int fn=0;
//采用循环计算斐波那契数列,通过两个临时变量pre和post保存中间结果,避免重复计算
for(int i=2;i<=n;i++)
{
fn=pre+post;//fn等于其前面两个元素值的和
//然后让pre和post分别直线他们后面的元素。
pre=post;
post=fn;
}
return fn;
}
}
题目2:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法。
这道题目的本质就是斐波那契数列。假设只有一个台阶,那么只有一种跳法,那就是一次跳一级,f(1)=1;如果有两个台阶,那么有两种跳法,第一种跳法是一次跳一级,第二种跳法是一次跳两级,f(2)=2。如果有大于2级的n级台阶,那么假如第一次跳一级台阶,剩下还有n-1级台阶,有f(n-1)种跳法,假如第一次条2级台阶,剩下n-2级台阶,有f(n-2)种跳法。这就表示f(n)=f(n-1)+f(n-2)。将上面的斐波那契数列代码稍微改一下就是本题的答案。这列略之。
题目3:我们可以用2X1(2行1列)的小矩形横着或者竖着去覆盖更大的矩形。请问用8个2X1的小矩形无重复地覆盖一个2X8的大矩形,总共有多少种方法。
#include <iostream>
using namespace std;
//定义一个数flag从1开始,与number做&操作,flag依次左移,int会左移32位
int GetNumberOf1InBinary(int number)
{
int count = 0;
unsigned int flag = 1;
while(flag)
{
if(number & flag)
{
count++;
}
flag <<= 1;
}
return count;
}
//第二种方式,比较巧妙,可以做number中二进制的1的个数次比较就可以得出结果
//因为n&(n-1)会把n的最后一个1化为0,这样有几个1就循环几次
int GetNumberOf1InBinary1(int number)
{
int count = 0;
while(number)
{
count++;
number = (number & (number-1));
}
return count;
}
int main()
{
cout<<GetNumberOf1InBinary(10)<<endl;
cout<<GetNumberOf1InBinary(-10)<<endl;
cout<<GetNumberOf1InBinary1(10)<<endl;
cout<<GetNumberOf1InBinary1(-10)<<endl;
return 10;
}
转自http://www.cnblogs.com/taoxu0903/archive/2011/03/11/1981389.html
参考:
Comparing floating point numbers
总结几点:
0. float占4byte,精度是6~7位;double占8byte,精度是15~16位。
1. 因为double类型或float类型都是有精度的,其实都是取的近似值,所以有个误差。和一个很小的数比如0.00000001(1e-8)比较就是为了在这个误差范围内进行比较。
举个例子如<strong>double</strong> b = 0.123456可能是0.1234561的四舍五入后得到的结果。最后的0.0000001就表示误差范围了。
<span style="color: rgb(51, 51, 51);">无论是float还是double类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式 。</span>
double 与 0 比较: doubel a; if ( a>-0.000001 && a< 0.000001 )对。 而 if( a == 0 )不对!
</pre><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; color: rgb(85, 85, 85); font-family: 'microsoft yahei'; font-size: 15px; line-height: 35px;"></p><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; color: rgb(85, 85, 85); line-height: 19px; font-size: 13px; font-family: verdana, 'ms song', 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(254, 254, 242);">2. C/C++的浮点数据类型有float和double两种。它们在内存中是以科学计数法的结果来存储的。 <br style="margin: 0px; padding: 0px;" /></p><div style="color: rgb(85, 85, 85); margin: 0px; padding: 0px; font-family: verdana, 'ms song', 宋体, Arial, 微软雅黑, Helvetica, sans-serif; font-size: 13px; line-height: 19px; background-color: rgb(254, 254, 242);"><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; line-height: 1.5em;">类型float大小为4字节,即32位,内存中的存储方式如下:</p><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; line-height: 1.5em;"></p><table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border: 1px solid rgb(192, 192, 192); margin: 0px auto; padding: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"><td valign="top" width="121" style="border-collapse: collapse; border: 1pt solid black; margin: 0px; padding: 0in 5.4pt; width: 90.9pt;"><br style="margin: 0px; padding: 0px;" /> 符号位(1 bit)</td><td valign="top" width="138" style="border-collapse: collapse; border-width: 1pt 1pt 1pt medium; border-style: solid solid solid none; border-color: rgb(192, 192, 192); margin: 0px; padding: 0in 5.4pt; width: 103.5pt;"><br style="margin: 0px; padding: 0px;" />指数(8 bit)</td><td valign="top" width="331" style="border-collapse: collapse; border-width: 1pt 1pt 1pt medium; border-style: solid solid solid none; border-color: rgb(192, 192, 192); margin: 0px; padding: 0in 5.4pt; width: 3.45in;"><br style="margin: 0px; padding: 0px;" />尾数(23 bit)</td></tr></tbody></table><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; line-height: 1.5em;"><br style="margin: 0px; padding: 0px;" />类型double大小为8字节,即64位,内存布局如下:</p><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; line-height: 1.5em;"></p><table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border: 1px solid rgb(192, 192, 192); margin: 0px auto; padding: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"><td valign="top" width="121" style="border-collapse: collapse; border: 1pt solid black; margin: 0px; padding: 0in 5.4pt; width: 90.9pt;"><br style="margin: 0px; padding: 0px;" />符号位(1 bit)</td><td valign="top" width="138" style="border-collapse: collapse; border-width: 1pt 1pt 1pt medium; border-style: solid solid solid none; border-color: rgb(192, 192, 192); margin: 0px; padding: 0in 5.4pt; width: 103.5pt;"><br style="margin: 0px; padding: 0px;" />指数(11 bit)</td><td valign="top" width="331" style="border-collapse: collapse; border-width: 1pt 1pt 1pt medium; border-style: solid solid solid none; border-color: rgb(192, 192, 192); margin: 0px; padding: 0in 5.4pt; width: 3.45in;"><br style="margin: 0px; padding: 0px;" />尾数(52 bit)</td></tr></tbody></table> <br style="margin: 0px; padding: 0px;" />符号位决定浮点数的正负,0正1负。 <br style="margin: 0px; padding: 0px;" />指数和尾数均从浮点数的二进制科学计数形式中获取。</div><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; color: rgb(85, 85, 85); line-height: 19px; font-size: 13px; font-family: verdana, 'ms song', 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(254, 254, 242);">3. 关于比较大小</p><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; color: rgb(85, 85, 85); line-height: 19px; font-size: 13px; font-family: verdana, 'ms song', 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(254, 254, 242);">一般情况下用一个absolute epsilon value来比较(<span style="margin: 0px; padding: 0px; line-height: 1.5; color: blue;">if</span><span style="margin: 0px; padding: 0px; line-height: 1.5;"> (fabs(result - expectedResult) < 0.00001)</span>)就够了。但是在某些对数字精度有特殊要求的domain,比如graphic,需要用更合理的方法。具体,参见引用文献2.</p><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; color: rgb(85, 85, 85); line-height: 19px; font-size: 13px; font-family: verdana, 'ms song', 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(254, 254, 242);"><span style="margin: 0px; padding: 0px; line-height: 1.5;">Comparing for equality</span></p><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; color: rgb(85, 85, 85); line-height: 19px; font-size: 13px; font-family: verdana, 'ms song', 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(254, 254, 242);"><span style="margin: 0px; padding: 0px; line-height: 1.5;">Comparing with epsilon – absolute error</span></p><p style="margin: 5px auto; padding-top: 0px; padding-bottom: 0px; color: rgb(85, 85, 85); line-height: 19px; font-size: 13px; font-family: verdana, 'ms song', 宋体, Arial, 微软雅黑, Helvetica, sans-serif; background-color: rgb(254, 254, 242);"><span style="margin: 0px; padding: 0px; line-height: 1.5;">Comparing with epsilon – relative error</span></p><div style="color: gray;"><small>来源: <<a target=_blank href="http://blog.youkuaiyun.com/summer_liuwei/article/details/7390783">http://blog.youkuaiyun.com/summer_liuwei/article/details/7390783</a><small>></small></small></div><small><small> </small></small></div><div><span style="font-size: 12pt; line-height: 1.5;"><span style="color:#ff0000;">九. 整数的整数次方(面试题11)</span><span style="color:#ff0000;"></span></span></div><div><pre style="font-size: 15px; word-wrap: break-word; margin-top: 0px; margin-bottom: 0px; padding: 0px; white-space: pre-wrap; line-height: 21.6000003814697px; font-family: 'Courier New' !important;">题目:实现函数double Power(double base,int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
这道题目有以下几点需要注意:
- 0的0次方是无意义的,非法输入
- 0的负数次方相当于0作为除数,也是无意义的,非法输入
- base如果非0,如果指数exponent小于0,可以先求base的|exponent|次方,然后再求倒数
- 判断double类型的base是否等于0不能使用==号。因为计算机表述小树(包括float和double型小数)都有误差,不能直接使用等号(==)判断两个小数是否相等。如果两个数的差的绝对值很小,那么可以认为两个double类型的数相等。
根据以上4个注意点,我们可以写出求指数的程序,代码如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
bool isInvalidInput=false;
double PowerWithUnsingedExponent(double base,unsigned int absExp)
{
double result=1.0;
for(int i=0;i<absExp;i++)
result*=base;
return result;
}
//由于精度原因,double类型的变量不能用等号判断两个数是否相等,因此需要写equsl函数
bool equal(double a,double b)
{
if((a-b>-0.000001)&&(a-b<0.000001))
return true;
else
return false;
}
double Power(double base,int exponent)
{
//如果底数为0且指数小于0,则表明是非法输入。
if(equal(base,0.0) && exponent<=0)
{
isInvalidInput=true;
return 0;
}
unsigned int absExp;
//判断指数正负,去指数的绝对值
if(exponent<0)
absExp=(unsigned int)(-exponent);
else
absExp=(unsigned int)exponent;
double result=PowerWithUnsingedExponent(base,absExp);
//如果指数小于0则取倒数
if(exponent<0)
result=1/result;
return result;
}
void main()
{
double a=Power(2.0,13);
cout<<a<<endl;
system("pause");
}
更优的解法:
假设我们求2^32,指数是32,那么我们需要进行32次循环的乘法。但是我们在求出2^16以后,只需要在它的基础上再平方一次就可以求出结果。同理可以继续分解2^16。也就是a^n=a^(n/2)*a^(n/2),(n为偶数);或者a^n=a^((n-1)/2)*a^((n-1)/2)*a,(n为奇数)。这样就将问题的规模大大缩小,从原来的时间复杂度O(n)降到现在的时间复杂度O(logn)。可以用递归实现这个思路,代码如下:
double PowerWithUnsingedExponent(double base,unsigned int absExp)
{
if(absExp==0)
return 1;
else if(absExp==1)
return base;
double result=PowerWithUnsingedExponent(base,absExp/2);
result*=result;//指数减少一倍以后用底数来乘
if(absExp%2==1)//如果指数为奇数,还得再乘一次底数
result*=base;
return result;
}
bool Increment(char* number)
{
bool isOverflow=false;
int nTakeOver=0;
int nLength=strlen(number);
for(int i=nLength-1; i>=0; i--)
{
int nSum=number[i]-'0'+nTakeOver;
if(i==nLength-1)
nSum++;
if(nSum>=10)
{
if(i==0)
isOverflow=true;
else
{
nSum-=10;
nTakeOver=1;
number[i]='0'+nSum;
}
}
else
{
number[i]='0'+nSum;
break;
}
}
return isOverflow;
}
void PrintNumber(char* number)
{
bool isBeginning0=true;
int nLength=strlen(number);
for(int i=0; i<nLength; i++)
{
if(isBeginning0 && number[i]!='0')
isBeginning0=false;
if(!isBeginning0)
{
cout<<number[i];
}
}
cout<<" ";
}
void Print1ToMaxOfNDigits2(int n)
{
if(n<=0)
return;
char *number=new char[n+1];
memset(number, '0', n);
number[n]='\0';
while(!Increment(number))
{
PrintNumber(number);
}
cout<<endl;
delete[] number;
}
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted);
删除结点的操作我们经常碰到,比如一个链表A->B->C->D->E->F->G。如果我们要删除结点E,那么我们只需要让结点D的指针指向结点F即可,但是我们现在只给出链表头结点的指针以及结点E的指针,而又是单项链表,不能在O(1)时间内得到被删除结点前面的那一个结点的指针,所以我们原先的方法是不能在O(1)时间内删除结点E的。
那么既然我们不能获得被删除结点的前一个结点的指针,我们就需要转变思路来考虑是否能够用过被删除结点后一个结点的指针来解决方法。因此被删除结点E的后一个结点指针是很容易得到的,也就是E->m_pNext。
那么我们可能会想到如下方法:获得F的指针,将F的数据赋值给E,然后让E指向F的下一个结点。这里虽然删除的是结点F,但是相当于删除的是结点E。并且是O(1)时间复杂度。下面给出代码实例:
#include<iostream> #include<stdlib.h> #include<stack> using namespace std; //链表结构 struct ListNode { int m_nValue; ListNode* m_pNext; }; //创建一个链表结点 ListNode* CreateListNode(int value) { ListNode *pNode=new ListNode(); pNode->m_nValue=value; pNode->m_pNext=NULL; return pNode; } //遍历链表中的所有结点 void PrintList(ListNode* pHead) { ListNode *pNode=pHead; while(pNode!=NULL) { cout<<pNode->m_nValue<<" "; pNode=pNode->m_pNext; } cout<<endl; } void ConnectListNode(ListNode* pCurrent,ListNode* pNext)//连接两个结点 { if(pCurrent==NULL) { cout<<"前一个结点不能为空"<<endl; exit(1); } else { pCurrent->m_pNext=pNext; } } void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted) { 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 pToBeDeleted; pToBeDeleted=NULL; } } void main() { //创建结点 ListNode* pNode1=CreateListNode(1);//创建一个结点 ListNode* pNode2=CreateListNode(2);//创建一个结点 ListNode* pNode3=CreateListNode(3);//创建一个结点 ListNode* pNode4=CreateListNode(4);//创建一个结点 ListNode* pNode5=CreateListNode(5);//创建一个结点 ListNode* pNode6=CreateListNode(6);//创建一个结点 ListNode* pNode7=CreateListNode(7);//创建一个结点 //连接结点 ConnectListNode(pNode1,pNode2);//连接两个结点 ConnectListNode(pNode2,pNode3);//连接两个结点 ConnectListNode(pNode3,pNode4);//连接两个结点 ConnectListNode(pNode4,pNode5);//连接两个结点 ConnectListNode(pNode5,pNode6);//连接两个结点 ConnectListNode(pNode6,pNode7);//连接两个结点 //打印链表 PrintList(pNode1);//打印 //删除结点 DeleteNode(&pNode1,pNode3); //打印链表 PrintList(pNode1);//打印 system("pause"); }
输出结果:
1 2 3 4 5 6 7
1 2 4 5 6 7在O(1)的情况下需哟由函数调用者确认将被删除的节点存在于链表中,是否存在的查找耗时O(h),如果在删除时校验则不能达到O(1)的效果。
文 / 何海涛
作者总结自己多年面试他人以及被他人面试的经验,发现应聘者可以从代码的规范性、完整性和鲁棒性三个方面提高代码的质量。
程序员在职业生涯中难免要接受编程面试。有些程序员由于平时没有养成良好的编程习惯,在面试时写出的代码质量不高,最终遗憾地与心仪的公司和职位失之交臂。因此,如何在面试时能写出高质量的代码,是很多程序员关心的问题。
代码的规范性
面试官是根据应聘者写出的代码来决定是否录用一个应聘者的。应聘者首先要把代码写得规范,才可以避免很多低级错误。如果代码写得不够规范,会影响面试官阅读代码的兴致,至少印象分会打折扣。书写、布局和命名都决定着代码的规范性。
规范的代码书写清晰。绝大部分面试都要求应聘者在白纸或者白板上书写。由于现代人已经习惯了敲键盘打字,手写变得越发不习惯,因此写出来的字潦草难辨。虽然应聘者没有必要为了面试特意去练字,但在面试过程中减慢写字速度、尽量把每个字母写清楚还是很有必要的。不用担心没有时间去写代码。通常编程面试的代码量都不会超过50行,书写不用花多少时间,关键是在写代码之前形成清晰的思路并能把思路用编程语言清楚地书写出来。
规范的代码布局清晰。平时程序员在集成开发环境如Visual Studio里面写代码,依靠专业工具调整代码的布局,加入合理的缩进并让括号对齐成对呈现。离开这些工具,应聘者就要格外注意布局问题。当循环、判断较多逻辑较复杂时,缩进的层次可能比较多。如果布局不够清晰,缩进也不能体现体现代码的逻辑,这样的代码将会让人头晕脑胀。
规范的代码命名合理。很多初学编程的人在写代码时总是习惯用最简单的名字来命名,变量名是i、j、k,函数名是f、g、h。由于这样的名字不能告诉读者对应的变量或者函数的意义,代码一长就会变得非常晦涩难懂。强烈建议应聘者在写代码时,用完整的英文单词组合命名变量和函数,比如函数需要传入一个二叉树的根结点作为参数,则可以把该参数命名为BinaryTreeNode* pRoot。不要因为这样会多写几个字母而觉得麻烦。如果一眼能看出变量、函数的用途,应聘者就能避免自己搞混淆而犯一些低级的错误。同时合理的命名也能让面试官一眼就能读懂代码的意图,而不是让他去猜变量到底是数组中的最大值还是最小值。
代码的完整性
在面试的过程中,面试官会非常关注应聘者考虑问题是否周全。面试官通过检查代码是否完整来考查应聘者的思维是否全面。通常面试官会检查应聘者的代码是否完成了基本功能、输入边界值是否能得到正确的输出、是否对各种不合规范的非法输入做出了合理的错误处理。
三种测试用例确保代码的完整性
应聘者在写代码之前,首先要把可能的输入都想清楚,从而避免在程序中出现各种各样的质量漏洞。也就是说在编码之前要考虑单元测试。如果能够设计全面的单元测试用例并在代码中体现出来,那么写出的代码自然也就是完整正确的了。通常程序员可以从功能测试、边界测试和负面测试三方面设计测试用例,以确保代码的完整性。
- 首先要考虑的普通功能测试的测试用例。应聘者首先要保证写出的代码能够完成面试官要求的基本功能。比如面试题要求完成的功能是把字符串转换成整数,应聘者就可以考虑输入字符串“123”来测试自己写的代码。这里要把零、正数(比如123)和负数(比如-123)都考虑进去。
考虑功能测试时,应聘者要尽量突破常规思维的限制,避免忽视某些隐含的功能需求。比如“打印从1到最大的n位数”,很多人觉得很简单。最大的3位数是999、最大的4位数是9999。这些数字很容易就能算出来。但最大的n位数都能用int型表示吗?如果超出int的范围可以考虑long long类型。超出long long能够表示的范围呢?面试官是不是要求考虑任意大的数字?如果面试官确认题目要求的是任意大的数字,那么这个题目就是一个大数问题。此时需要特殊的数据结构来表示数字,比如用字符串或者数组来表示大的数字,才能确保不会溢出。
- 其次需要考虑各种边界值的测试用例。很多代码都包含有循环或者递归。如果代码是基于循环,那么结束循环的边界条件是否正确?基于循环的代码要特别注意开区间和闭区间的使用(也就是区分<与<=、>与>=)。如果代码是基于递归,递归终止的边界值是否正确?这些都是边界测试时要考虑的用例。还是以字符串转换成整数的问题为例,应聘者写出的代码应该确保能够正确转换最大的正整数和最小的负整数。
- 再次还需要考虑各种可能的错误的输入,也就是负面测试的测试用例。应聘者写出的函数除了要顺利地完成要求的功能之外,当输入不符合要求时,面试官还希望他能做出合理的错误处理。在设计把字符串转换成整数的函数时,应聘者就要考虑当输入的字符串不是一个数字,比如“1a2b3c”,怎么告诉函数的调用者这个输入是非法的。
前面讨论的都是要全面考虑当前需求对应的各种可能输入。在软件开发过程中,永远不变的就是需求会一直改变。如果应聘者在面试时写出的代码能够把将来需求可能的变化都考虑进去,在需求发生变化时能够尽量减少代码改动的风险,那他就向面试官展示了自己对程序可扩展性和可维护性的理解,必定能得到面试官的青睐。如果应聘者在解答面试题“调整数组顺序使奇数位于偶数前面”时能够考虑可扩展性,他写出的代码不仅仅只是解决调整奇数和偶数的问题,还能考虑到把调整数字顺序的功能和判断一个数字是奇数还是偶数的功能解耦。这样当今后需求功能扩展要求解决类似的问题,比如调整负数和非负数的顺序、调整能被3整除的数字和不能被3整除的数字的顺序,只需要添加很少的代码都能做到,于是提高了代码的可扩展性和可维护性。
三种错误处理的方法
通常有三种方式把错误信息传递给函数调用者。
- 函数用返回值来告知调用者是否出错。比如很多Windows的API就是这个类型。Windows中很多API的返回值为0表示API调用成功,而返回值不为0表示在API调用的过程中出错了。微软为不同的非零返回值定义了不同的意义,调用者可以根据这些返回值判断出错的原因。这种方式最大的问题是使用不便,因为函数不能直接把计算结果通过返回值直接赋值给其他变量,同时也不能把这个函数计算的结果直接作为参数传递给其他函数。
- 当发生错误时设置一个全局变量。此时可以在返回值中传递计算结果了。这种方法比第一种方法使用起来更加方便,因为调用者可以直接把返回值赋值给其他变量或者作为参数传递给其他函数。Windows的很多API运行出错之后,也会设置一个全局变量。函数调用者可以通过调用函数GetLastError分析这个表示错误的全局变量从而得知出错的原因。但这个方法有个问题:调用者很容易就会忘记去检查全局变量,因此在调用出错时忘记做相应的错误处理,从而留下安全隐患。
- 异常。当函数运行出错时,程序就抛出一个异常。程序员可以根据不同的出错原因定义不同的异常类型。因此函数的调用者可以根据异常的类型就能知道出错的原因,从而可以做相应的处理。另外,由于显式划分了程序正常运行的代码块(try模块)和处理异常的代码块(catch模块),代码的逻辑比较清晰。异常在高级语言如C#中是强烈推荐的错误处理方式,但有些早期的语言比如C语言还不支持异常。另外,当抛出异常时,程序的执行会打乱正常的顺序,对程序的性能有很大的影响。
上述三种错误处理的方式各有优缺点。那么面试时应聘者该采用哪种方式呢?这要看面试官的需求。在听到面试官的题目之后,应聘者要尽快分析出可能存在哪些非法输入,并和面试官讨论该如何处理这些非法输入。和面试官进行这样的讨论对应聘者是有益的,因为面试官会觉得他对错误处理有着全面的了解,并且还会觉得他有很好的沟通能力。
代码的鲁棒性
鲁棒性是指程序能够判断输入是否合乎规范要求,并对不合要求的输入予以合理的处理。容错性是鲁棒性的一个重要体现。不鲁棒的软件在发生异常事件时,比如用户输入错误的用户名、试图打开的文件不存在或者网络不能连接,就会出现不可预见的诡异行为,或者干脆整个软件崩溃。这样的软件对于用户而言,不亚于一场灾难。
由于鲁棒性对软件开发非常重要,面试官在招聘时对应聘者写出的代码是否鲁棒也非常关注。提高代码的鲁棒性的有效途径是进行防御性编程。防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,并为这些可能出现的问题制定处理方式。
在面试时,最简单也最实用的防御性编程就是在函数入口添加代码以验证用户输入是否符合要求。通常面试要求的是写一两个函数,应聘者需要格外关注这些函数的输入参数。如果输入的是一个指针,那指针是空指针怎么办?如果输入的是一个字符串,那么字符串的内容为空怎么办?如果应聘者能把这些问题都提前考虑到,并作相应的处理,那么面试官就会觉得他有防御性编程的习惯,能够写出鲁棒的软件。
当然并不是所有与鲁棒性相关的问题都只是检查输入的参数这么简单。应聘者看到问题时,要多问几个“如果不……那么……”这样的问题。比如面试题“链表中倒数第k个结点”,这里隐含着一个条件就是链表中结点的个数大于k。应聘者就要问自己如果链表中的结点不是大于k个,那么代码会出什么问题?这样的思考方式,能够帮助发现潜在的问题并提前解决问题。这比事后让面试官发现问题之后应聘者再去慌忙分析代码查找问题的根源要好很多。
小结
本文从规范性、完整性和鲁棒性三方面介绍了应聘者如何在面试时写出高质量代码(如下图所示)。
第一,应聘者在白纸或者白板上手写代码时要注意规范性,尽量清晰地书写每个字母,通过缩进和对齐括号让代码布局合理,同时还要合理命名代码中的变量和函数。
第二,应聘者最好在编码之前全面考虑所有可能的输入,确保写出的代码在完成了基本功能之外,还考虑了边界条件,并做好了错误处理。只有全面考虑到这三方面的代码才是完整的代码。
第三,应聘者要重视代码的鲁棒性,确保自己写出的程序不会轻易崩溃。平时在写代码时,应聘者最好养成防御式编程的习惯,在函数入口判断输入是否有效并对各种无效输入做好相应的处理。
应聘者如果能够做到这三点,自然就能写出高质量的代码,最终通过面试拿到Offer也将是水到渠成的事情。
作者何海涛,思科高级软件工程师,之前先后任职于Autodesk和微软。主要关注C++/C#的开发技术,并对设计模式和项目管理也很感兴趣。
题目:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分
实现这个功能很简单,关键是考虑问题要考虑周全,数组为空,数组只有一个奇数,数组只有一个偶数,数组有两个数前奇后偶,数组有两个数前偶后奇,数组有多个数字
有很多的此类的问题,例如:
(1)把负数放到正数前面
(2)能被3整除的放在不能被3整除的数前面
等等,这只是里面的判断规则不一样,这样的话我们提供一个模板就可以了,就可以使用一个函数来实现这些操作,当然要借助函数指针,具体实现如下:
#include <iostream>
using namespace std;
//修改为可以适合其他规则的一个模板,这就需要使用函数指针
void ReOrderOddEven(int data[], int length, bool (*func)(int ))
{
if(data==NULL || length<0)
return ;
int pOdd = 0;
int pEven = length-1;
while(pEven > pOdd)
{
//定位pOdd的位置,指向第一个偶数
while((pOdd < pEven) && (*func)(data[pOdd]))
++pOdd;
//定位pEven的位置,指向第一个奇数
while((pOdd < pEven) && !(*func)(data[pEven]))
--pEven;
if(pEven > pOdd)
{//交换
int temp = data[pEven];
data[pEven--] = data[pOdd];
data[pOdd++] = temp;
}
}
}
bool isOdd(int number)
{
return number&1;
}
void printArray(int data[], int length)
{
for(int i=0; i<length; ++i)
{
cout<<data[i]<<endl;
}
cout<<endl;
}
int main()
{
int data[] = {1};
ReOrderOddEven(data,1,isOdd);
printArray(data,1);
int data1[] = {2};
ReOrderOddEven(data1,1,isOdd);
printArray(data1,1);
int data2[] = {1,2};
ReOrderOddEven(data2,2,isOdd);
printArray(data2,2);
int data3[] = {2,1};
ReOrderOddEven(data3,2,isOdd);
printArray(data3,2);
int data4[] = {1,2,3,4,5};
ReOrderOddEven(data4,5,isOdd);
printArray(data4,5);
ReOrderOddEven(NULL,0,isOdd);
return 0;
}
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
假设有链表A->B->C->D->E->F->G。在反转链表过程中的某一阶段,其链表指针指向为:A<-B<-C<-D E->F->G。也就是说在结点D之前的所有结点都已经反转,而结点D后面的结点E开始的所有结点都没有反转。这样D跟E之间存在了断裂。我们如果要实现链表的反转,会有以下几个重要步骤:
- D->E变为D->C,指针反转
- 指针往后移动一个,操作下一个结点E
- 结合1.2我们发现需要操作3个指针,分别是C,D,E。
因此可以考虑存储C/D/E三个结点的指针,通过这三个结点的指针实现反转。
代码实例:
#include<iostream>
#include<stdlib.h>
#include<stack>
using namespace std;
//链表结构
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
//创建一个链表结点
ListNode* CreateListNode(int value)
{
ListNode *pNode=new ListNode();
pNode->m_nValue=value;
pNode->m_pNext=NULL;
return pNode;
}
//遍历链表中的所有结点
void PrintList(ListNode* pHead)
{
ListNode *pNode=pHead;
while(pNode!=NULL)
{
cout<<pNode->m_nValue<<" ";
pNode=pNode->m_pNext;
}
cout<<endl;
}
//往链表末尾添加结点
/*
注意这里pHead是一个指向指针的指针,在主函数中一般传递的是引用。
因为如果要为链表添加结点,那么就会修改链表结构,所以必须传递引用才能够保存修改后的结构。
*/
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->m_pNext;
pNode->m_pNext=pNew;
}
}
ListNode* ReverseList(ListNode* pHead)
{
ListNode* pNode=pHead;//当前结点
ListNode* pPrev=NULL;//当前结点的前一个结点
ListNode* pReversedHead=NULL;//反转链表头结点
while(pNode!=NULL)
{
ListNode* pNext=pNode->m_pNext;
if(pNext==NULL)//如果当前结点的下一个结点为空,那么反转链表的头结点就是当前结点。
pReversedHead=pNode;
pNode->m_pNext=pPrev;//当前结点指向前一个结点
pPrev=pNode;//pPrev和pNode往前移动。
pNode=pNext;//这里要使用前面保存下来的pNext,不能使用pNode->m_pNext
}
return pReversedHead;//返回反转链表头指针。
}
void main()
{
//创建结点
ListNode* pNode1=CreateListNode(1);//创建一个结点
PrintList(pNode1);//打印
//往链表中添加新结点
AddToTail(&pNode1,2);//为链表添加一个结点
AddToTail(&pNode1,3);//为链表添加一个结点
AddToTail(&pNode1,4);//为链表添加一个结点
AddToTail(&pNode1,5);//为链表添加一个结点
AddToTail(&pNode1,6);//为链表添加一个结点
AddToTail(&pNode1,7);//为链表添加一个结点
//打印链表
PrintList(pNode1);//打印
//反转链表
ListNode* pReversedHead=ReverseList(pNode1);
PrintList(pReversedHead);//打印
system("pause");
}
ListNode* ReverseList2(ListNode* pHead)
{
ListNode* pNode=pHead;//当前结点
ListNode* pPrev=NULL;//当前结点的前一个结点
while(pNode!=NULL)
{
ListNode* pNext=pNode->m_pNext;
pNode->m_pNext=pPrev;//当前结点指向前一个结点
pPrev=pNode;//pPrev和pNode往前移动。
pNode=pNext;//这里要使用前面保存下来的pNext,不能使用pNode->m_pNext
}
return pPrev;//返回反转链表头指针。
}
题目:输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是按照递增排序的。
方法一:非递归 代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
struct ListNode
{
int m_nValue;
ListNode *m_pNext;
};
//合并两个有序链表,非递归方法
ListNode *MergeTwoList(ListNode *pListOneHead, ListNode *pListTwoHead)
{
if (pListOneHead == NULL)
{
return pListTwoHead;
}
if (pListTwoHead == NULL)
{
return pListOneHead;
}
ListNode *pNode1 = pListOneHead;
ListNode *pNode2 = pListTwoHead;
ListNode *pMergeListHead = NULL;
ListNode *pCurLastNode = NULL;
if (pNode1->m_nValue < pNode2->m_nValue)
{
pMergeListHead = pListOneHead;
pNode1 = pNode1->m_pNext;
pCurLastNode = pMergeListHead;
}
else
{
pMergeListHead = pListTwoHead;
pNode2 = pNode2->m_pNext;
pCurLastNode = pMergeListHead;
}
while (pNode1 != NULL || pNode2 != NULL)//应该是只有俩都为空,才会跳出
{
if (pNode1 == NULL || pNode1->m_nValue > pNode2->m_nValue) //短路效应
{
pCurLastNode->m_pNext = pNode2;
pCurLastNode = pNode2;
pNode2 = pNode2->m_pNext;
}
else if (pNode2 == NULL || pNode1->m_nValue < pNode2->m_nValue)
{
pCurLastNode->m_pNext = pNode1;
pCurLastNode = pNode1;
pNode1 = pNode1->m_pNext;
}
}
return pMergeListHead;
}
//创建一个链表,输入从头到尾结点的值,输入-1表示结束
void CreateList(ListNode *& pHead)
{
ListNode *pListNode = NULL;
ListNode *pCurLastNode = NULL;
bool isHead = true;
while (1)
{
if (isHead)
{
pHead = new ListNode();
cin >> pHead->m_nValue;
if (pHead->m_nValue == -1)
{
pHead = NULL;
break;
}
pHead->m_pNext = NULL;
isHead = false;
pCurLastNode = pHead;
}
else
{
pListNode = new ListNode();
cin >> pListNode->m_nValue;
if (pListNode->m_nValue == -1)
{
break;
}
pListNode->m_pNext = NULL;
pCurLastNode->m_pNext = pListNode;
pCurLastNode = pListNode;
}
}
}
//从头到尾打印链表
void PrintList(ListNode *&pHead)
{
if (pHead != NULL)
{
ListNode *pCur = pHead;
while (pCur != NULL)
{
cout << pCur->m_nValue << " ";
pCur = pCur->m_pNext;
}
cout << endl;
}
else
{
cout << "链表为空!" << endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
ListNode *pList1Head = NULL;
CreateList(pList1Head);
PrintList(pList1Head);
ListNode *pList2Head = NULL;
CreateList(pList2Head);
PrintList(pList2Head);
ListNode *pMergeListHead = MergeTwoList(pList1Head, pList2Head);
if (pMergeListHead != NULL)
{
cout << pMergeListHead->m_nValue << endl;
}
PrintList(pMergeListHead);
system("pause");
return 0;
}
方法二:递归 代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
struct ListNode
{
int m_nValue;
ListNode *m_pNext;
};
//合并两个有序链表,递归方法
ListNode *MergeTwoList(ListNode *pListOneHead, ListNode *pListTwoHead)
{
if (pListOneHead == NULL)
{
return pListTwoHead;
}
if (pListTwoHead == NULL)
{
return pListOneHead;
}
ListNode *pMergeListHead = NULL;
if (pListOneHead->m_nValue < pListTwoHead->m_nValue)
{
pMergeListHead = pListOneHead;
pMergeListHead->m_pNext = MergeTwoList(pMergeListHead->m_pNext, pListTwoHead);
}
else
{
pMergeListHead = pListTwoHead;
pMergeListHead->m_pNext = MergeTwoList(pListOneHead, pMergeListHead->m_pNext);
}
return pMergeListHead;
}
//创建一个链表,输入从头到尾结点的值,输入-1表示结束
void CreateList(ListNode *& pHead)
{
ListNode *pListNode = NULL;
ListNode *pCurLastNode = NULL;
bool isHead = true;
while (1)
{
if (isHead)
{
pHead = new ListNode();
cin >> pHead->m_nValue;
if (pHead->m_nValue == -1)
{
pHead = NULL;
break;
}
pHead->m_pNext = NULL;
isHead = false;
pCurLastNode = pHead;
}
else
{
pListNode = new ListNode();
cin >> pListNode->m_nValue;
if (pListNode->m_nValue == -1)
{
break;
}
pListNode->m_pNext = NULL;
pCurLastNode->m_pNext = pListNode;
pCurLastNode = pListNode;
}
}
}
//从头到尾打印链表
void PrintList(ListNode *&pHead)
{
if (pHead != NULL)
{
ListNode *pCur = pHead;
while (pCur != NULL)
{
cout << pCur->m_nValue << " ";
pCur = pCur->m_pNext;
}
cout << endl;
}
else
{
cout << "链表为空!" << endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
ListNode *pList1Head = NULL;
CreateList(pList1Head);
PrintList(pList1Head);
ListNode *pList2Head = NULL;
CreateList(pList2Head);
PrintList(pList2Head);
ListNode *pMergeListHead = MergeTwoList(pList1Head, pList2Head);
if (pMergeListHead != NULL)
{
cout << pMergeListHead->m_nValue << endl;
}
PrintList(pMergeListHead);
system("pause");
return 0;
}
题目解析:
可以分两步来进行:第一步在A中找到和B的根结点相同的结点(需要通过遍历树来得到);第二步判断树A中以根结点相同的子树是不是包含和B一样的结构。
struct BinaryTreeNode
{
int value;
BinaryTreeNode *left,*right;
};
bool doestree1havetree2(BinaryTreeNode *root1,BinaryTreeNode *root2);
bool hassubtree(BinaryTreeNode *root1,BinaryTreeNode *root2)
{
bool result=false;
if(root1!=NULL && root2!=NULL)
{
if(root1->value==root2->value)
{
result=doestree1havetree2( root1, root2 );
}
if(!result)
result=hassubtree(root1->left, root2);
if(!result)
result=hassubtree(root1->right,root2);
}
return result;
}
bool doestree1havetree2(BinaryTreeNode *root1,BinaryTreeNode *root2)
{
if(root2==NULL)
return true;
if(root1==NULL)
return false;
if(root1->value!=root2->value)
return false;
return doestree1havetree2(root1->left,root2->left)&&doestree1havetree2(root1->right,root2->right);
}
题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
#include <stdio.h>
#include <stdlib.h>
typedef struct BiTNode{
int data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
void MirrorRecursively(BiTree T);
void PreOrderTraverse(BiTree T);
void CreateBiTree(BiTree *T);
int main()
{
BiTree T;
int n;
printf("input a number:");
while(scanf("%d",&n) == 1){
CreateBiTree(&T);
PreOrderTraverse(T);
MirrorRecursively(T);
PreOrderTraverse(T);
}
return 0;
}
//镜像变换
void MirrorRecursively(BiTree T)
{
if(T){
BiTree temp = T->lchild;
T->lchild = T->rchild;
T->rchild = temp;
MirrorRecursively(T->lchild);
MirrorRecursively(T->rchild);
}
}
//前序遍历输出
void PreOrderTraverse(BiTree T)
{
if(T){
printf("%d ",T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
//创建二叉树,输入0表示数据位空,产生空指针
void CreateBiTree(BiTree *T)
{
int data;
scanf("%d",&data);
if(data == 0)
*T = NULL;
else{
*T = (BiTree)malloc(sizeof(BiTNode));
if(!*T)
exit(-1);
(*T)->data = data;
CreateBiTree(&((*T)->lchild));
CreateBiTree(&((*T)->rchild));
}
}
题目:给定一个矩阵,从外向内顺时针打印矩阵中的每一个数字。
例如:给定矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
输出应该为:{1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10}
分析:这道题的意思非常直观,给人的感觉也是so easy,然而实际去做的时候会发现,如果结构划分的不好,会出现很多的循环,而且包括对各种边界条件的判定,题目不难,但是给人的感觉——很烦!我分享一下,我做这道题的思路。
循环条件怎么判定?一个矩阵,给定起点(startX,startY)和终点(endX,endY)(即位于对角线上的两个点)就可以打印一周,然后向里进一周(即++startX,++startY,--endX,--endY)即可,如果起始点坐标<终止点坐标(即startX<endX或者startY<endY)循环结束。
给定起点和终点,打印一周的结束条件是什么?我画了三种情况的矩阵4×4矩阵,3×5矩阵,5×3矩阵(所有矩阵无非就这三种类型,正方形的,偏“胖”的,偏“瘦”的),很快发现,只有三种情况:一直循环到结束,只剩下一行,只剩下一列。所以我们的函数首先判定:只有一行?打印该行;只有一列,打印该列。都不是,打印四条边上的数字。
好了到这里为止:我们可以写出完整的代码了,如下:
#include<iostream>
#include<string>
using namespace std;
void PrintMatrixCircle(int **num,int sX,int sY,int eX,int eY);
//给定矩阵,给定行列,由外向内顺时针打印数字
void PrintMatrixClockwisely(int **matrix,int rows,int columns)
{
if(matrix == NULL || rows < 0 || columns < 0)
return;
int startX = 0;
int startY = 0;
int endX = rows - 1;
int endY = columns - 1;
while(1)
{
if(startX > endX && startY > endY)
break;
if(startX == endX && startY > endY)
break;
if(startX > endX && startY == endY)
break;
PrintMatrixCircle(matrix,startX,startY,endX,endY);
++startX;
++startY;
--endX;
--endY;
}
}
//对于给定矩阵,给定对角线上两点,打印这一周的元素
void PrintMatrixCircle(int **num,int sX,int sY,int eX,int eY)
{
//只有一行的情况,直接打印,返回。
if(sX == eX)
{
for(int j = sY;j <= eY;++j)
{
cout<<*(*(num+sX)+j)<<"\t";
}
return;
}
//只有一列的情况,直接打印,返回。
if(sY == eY)
{
for(int i = sX;i <= eX;++i)
{
cout<<*(*(num+i)+sY)<<"\t";
}
return;
}
//一般的情况打印四行
for(int p = sY;p < eY;++p)
{
cout<<*(*(num+sX)+p)<<"\t";
}
for(int q = sX;q < eX;++q)
{
cout<<*(*(num+q)+eY)<<"\t";
}
for(int m = eY;m > sY;--m)
{
cout<<*(*(num+eX)+m)<<"\t";
}
for(int n = eX;n > sX;--n)
{
cout<<*(*(num+n)+sY)<<"\t";
}
}
int main()
{
int example[6][6];
int *point1[6] = {example[0],example[1],example[2],example[3],example[4],example[5]};
int **point = point1;
int cnt=1;
//初始化
for(int i = 0;i < 6;i++)
{
for(int j = 0;j < 6;j++)
{
example[i][j] = cnt;
cnt++;
}
}
cout<<"the original matrix is:"<<endl;
for(i = 0;i < 6;i++)
{
for(int j = 0;j < 6;j++)
{
cout<<example[i][j]<<"\t";
}
cout<<endl;
}
cout<<"the clockwisely output of the matrix is:"<<endl;
PrintMatrixClockwisely(point,6,6);
cout<<endl;
return 0;
}
这是正方形矩阵,我也测试了偏”胖“型矩阵和偏”瘦型矩阵“,要测试的话,大家需要对main函数的矩阵和指针做适当修改;
题目来源:
何海涛博客:http://zhedahht.blog.163.com/blog/static/254111742010111112236313/
这一题实际上需要一个辅助栈存储最小值:
1.在模板类定义两个栈类型私有成员变量,一个为保存数据的栈另外一个为保存最小值的栈
2.当栈为空的时候直接将数据同时压入数据栈和最小值栈
3.当栈不为空的时候,将数据先压入数据栈同时比较该数据和最小值栈栈顶元素的大小,若大于最小值栈栈顶元素,则向最小值栈压入其栈顶元素,否则压入该数据到最小值栈栈顶
4.当我们使用方法的时候直接取最小值的栈顶即为栈中的最小值
5.当我们要pop栈的时候应同时pop最小值栈的栈顶元素
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<stack>
using namespace std;
template<class type> class StackWithMin
{
public:
void pop();
void push(const type & value);
type minValue();
private:
stack<type> Stack;
stack<type> MinStack;
};
template<class type> void StackWithMin<type>::pop()
{
if(!Stack.empty()&&(!MinStack.empty()))
{
Stack.pop();
MinStack.pop();
}
}
template<class type> void StackWithMin<type>::push(const type &value)
{
Stack.push(value);
if(MinStack.empty()||value<MinStack.top())
{
MinStack.push(value);
}
else
{
MinStack.push(MinStack.top());
}
}
template<class type> type StackWithMin<type>::minValue()
{
if(!MinStack.empty())
{
return MinStack.top();
}
}
int main()
{
StackWithMin<int> testStack;
//3,4,2,1 入栈
testStack.push(3);
cout<<"the min value is:"<<testStack.minValue()<<endl;
testStack.push(4);
cout<<"the min value is:"<<testStack.minValue()<<endl;
testStack.push(2);
cout<<"the min value is:"<<testStack.minValue()<<endl;
testStack.push(1);
cout<<"the min value is:"<<testStack.minValue()<<endl;
//1,2,4,3 出栈
testStack.pop();
cout<<"the min value is:"<<testStack.minValue()<<endl;
testStack.pop();
cout<<"the min value is:"<<testStack.minValue()<<endl;
testStack.pop();
cout<<"the min value is:"<<testStack.minValue()<<endl;
testStack.pop();
cout<<"the min value is:"<<testStack.minValue()<<endl;
return 0;
}
题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。
例如序列1、2、3、4、5是某栈的压栈序列,序列4、5、3、2、1是该压栈序列对应的一个弹出序列,但4、3、5、1、2就不可能是该该压栈序列的弹出序列。
本题的题解步骤如下:
1.设置一个辅助栈S,用于模拟出栈入栈顺序,设入栈顺序序列为pPush,出栈顺序序列为pPop
2.设置两个索引或指针分别指向入栈序列和出栈序列的第一个元素
3.顺序索引入栈元素,当入栈元素不等于出栈元素的时候,将入栈元素依次压入辅助栈S
4.当两者相等的时候,压入该元素到辅助栈S中同时将栈顶元素出栈,出栈入栈序列的索引均向后移动一个位置
5.当入栈序列索引结束之后,pPush剩余的元素全部已压入栈S
6.依次索引pPop如果pPop与辅助栈顶元素比较如果相等这将辅助栈顶弹出
7.当栈中所有的元素均弹出的时候则说明出栈顺序序列与正好是入栈顺序序列对应的出栈顺序。否则出栈顺序与入栈顺序不匹配。
#include "stdafx.h"
#include <iostream>
#include <stack>
using namespace std;
bool IsPopOrder(int *pPush, int *pPop, int nLength)
{
if (pPush == NULL || pPop == NULL || nLength <= 0)
{
return false;
}
stack<int> s;
s.push(pPush[0]);
int nPop_index = 0;
int nPush_index = 1;
while (nPop_index < nLength)
{
while (s.top() != pPop[nPop_index] && nPush_index < nLength)
{
s.push(pPush[nPush_index]);
nPush_index++;
}
if (s.top() == pPop[nPop_index])
{
s.pop();
nPop_index++;
}
else
{
return false;
}
}
return true;
}
int main( int argc )
{
int nPush[5] = {1,2,3,4,5};
int nPop1[5] = {4,5,3,2,1};
int nPop2[5] = {4,3,5,1,2};
int nPop3[5] = {5,4,3,2,1};
int nPop4[5] = {4,5,2,3,1};
cout << IsPopOrder(nPush, nPop1, 5) << endl;
cout << IsPopOrder(nPush, nPop2, 5) << endl;
cout << IsPopOrder(nPush, nPop3, 5) << endl;
cout << IsPopOrder(nPush, nPop4, 5) << endl;
system("pause");
return 0;
}