面试->代码 非剑指offer(二)

2018 年 4 月 7 日 程序人生 ssopp24

点击上方“程序人生”,选择“置顶公众号”

第一时间关注程序猿(媛)身边的故事


1.模拟实现 strstr


[cpp] view plain copy
charMyStrstrconst char* str1, const char* str2 )
{    
   assert( NULL != str1 && NULL != str2 );  
     
   while ('\0' != *str1){    
       const char* p1 = str1;  
       const char* p2 = str2;  
         
       while (('\0' != *p1) && ('\0' != *p2) && (*p1 == *p2)){  
           ++p1;  
           ++p2;  
       }  
         
       if ('\0' == *p2)  
           return str1;    
         
       ++str1;  
   }  
     
   return NULL;    
}


2.模拟实现 strcpy


[cpp] view plain copy
charMyStrcpy(char* dest, const char* src)
{    
   assert( NULL != dest && NULL != src );  
     
   char* pAddress = dest;  
   while ('\0' != (*dest++ = *src++))  
       ;  
 
   return pAddress;  
}


3.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?   
least = MIN(*p++, b);  

:

[cpp] view plain copy
#define MIN(A,B) ((A) <= (B) ? (A) : (B))


注意:


[cpp] view plain copy
MIN(*p++, b)会产生宏的副作用  
 
宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换     
程序员对宏定义的使用要非常小心,特别要注意两个问题:  
(1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:    
#define MIN(A,B) (A) <= (B) ? (A) : (B)  
#define MIN(A,B) (A <= B ? A : B )  
都应判0分  
(2)防止宏的副作用    
宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:     
((*p++) <= (b) ? (*p++) : (b))     
这个表达式会产生副作用,指针p会作2次++自增操作  
除此之外,另一个应该判0分的解答是:  
#define MIN(A,B) ((A) <= (B) ? (A) : (B));   
这个解答在宏定义的后面加“;”,显示编写者对宏的概念模糊不清,只能被无情地判0分并被面试官淘汰


4.为什么标准头文件都有类似以下的结构?


[cpp] view plain copy
#ifndef __INCvxWorksh  
#define __INCvxWorksh   
#ifdef __cplusplus  
extern "C" {  
#endif   
/*...*/   
#ifdef __cplusplus  
}  
#endif   
#endif /* __INCvxWorksh */


头文件中的编译宏


[cpp] view plain copy
#ifndef __INCvxWorksh  
#define __INCvxWorksh  
#endif


的作用是防止被重复引用。   


作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:    


void foo(int x, int y);   


该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。   


为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C++语言中就可以调用C语言的函数了。

C++支持函数重载,C语言不支持函数重载。C++为了支持函数重载,按照不同编译器的函数修饰规则,对函数名进行了处理,加入函数的参数类型进行修饰。这里函数是被C编译器编译的,编译后函数名没有进行修饰,所以要加extern"c"告诉编译器使用c的方式进行查找链接。


5.编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:


[cpp] view plain copy
class String{   
public:   
   Stringconst char* str = NULL );           // 普通构造函数   
   String(const String &other);                // 拷贝构造函数   
   ~ String(void);                             // 析构函数   
   String & operator =(const String &other);   // 赋值函数   
 
private:   
   char *m_data;                               // 用于保存字符串   
};



[cpp] view plain copy
// 普通构造函数  
String::String( const char* str ){  
   if (NULL==str){  
       m_data = new char[1];           // 对空字符串自动申请存放结束标志'\0'的空间  
       *m_data = '\0';   
   }  
   else{  
       int length = strlen(str);   
       m_data = new char[length+1];  
         
       strcpy(m_data, str);   
   }  
}  
 
// String的析构函数  
String::~String( void ){  
   delete [] m_data;                   // 或delete m_data;  
}  
 
// 拷贝构造函数  
// 输入参数为const型   
String::String( const String& other ){  
   int length = strlen( other.m_data );   
   m_data = new char[length+1];   
     
   strcpy( m_data, other.m_data );   
}  
 
// 赋值函数  
// 输入参数为const型  
MyString& MyString::operator=( const MyString& str ){  
   // 检查自赋值  
   if ( &str != this ){  
       MyString strTemp( str );  
 
       char* pTemp = strTemp.m_pData;  
       strTemp.m_pData = m_pData;  
       m_pData = pTemp;  
   }  
 
   // 返回本对象的引用  
   return *this;  
}


当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数


如果出函数作用域后还存在则可以返回引用, 不存在则不能返回引用,要返回临时对象


6.请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1(这里的大小端问题重点关注的是如何判断处理器是大端还是小端(笔试)。 大小端问题要完整掌握,还需与另一篇关注为什么有大小端,大小端的区别是什么的面试博客一起学习 )

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。 

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

unsigned int value = 0x12345678
Big-Endian: 低地址存放高位,如下:


高地址
  ---------------
   (0x78) -- 低位
   (0x56)
   (0x34)
   (0x12) -- 高位
  ---------------
  低地址


Little-Endian: 低地址存放低位,如下:


高地址
  ---------------
   (0x12) -- 高位
   (0x34)
   (0x56)
   (0x78) -- 低位
  --------------
低地址


e684 6c4e

在大端模式下,前32位应该这样读: e6 84 6c 4e(一个字节为8个二进制位,为2个16进制数字,所以这样表示 e6而不是6e)
记忆方法: 地址的增长顺序与值的增长顺序相反

在小端模式下,前32位应该这样读: 4e 6c 84 e6
记忆方法: 地址的增长顺序与值的增长顺序相同

[cpp] view plain copy
int CheckCPU( )
{  
   union w{  
       int a;  
       char b;  
   } c;  
   c.a = 1;  
 
   return (1 == c.b);  
}


联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性获得了CPU对内存采用Little-endian还是Big-endian模式读写


联合体:各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值


7.写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)

[cppview plain copy
long Sum( long n )
{   
return ( (long)1 + n ) * n / 2;  
}


8.宏函数  -->  交换两个数

:

[cpp] view plain copy
#define SWAP( a, b )        \  
do {                        \  
   (a) = ((a)^(b));        \  
   (b) = ((a)^(b));        \  
   (a) = ((a)^(b));        \  
}while (0)


1.SWAP和括号间不能有空格,否则会被错误替换
2.续航符后面不能有空格,否则编译器会报错,但是新的一行开头可以有空格


do { } while( 0 )意义:

辅助定义复杂的宏,避免引用的时候出错:
假设你需要定义这样一个宏:


#define DOSOMETHING( ) \
               foo1( ); \
               foo2( );
这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:
if (a>0)
    DOSOMETHING( );
因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:
if (a>0)
    foo1( );
foo2( );
这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。


那么仅仅使用{}将foo1()和foo2()包起来行么?
我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:
if (a>0)
{
    foo1( );
    foo2( );
};
这样甚至不会编译通过。所以,很多人才采用了do {...}while (0);


#define DOSOMETHING( ) \
        do { \
          foo1( ); \
          foo2( ); \
        }while (0) \
 
if (a>0)
    DOSOMETHING( );
这样,宏被展开后,才会保留初始的语义。


8.模拟实现 strncpy

[cpp] view plain copy
charMyStrncpychar* dest, const char* src, size_t len )
{    
   assert( NULL != dest && NULL != src );  
 
   char* ret = dest;    
   while (0 != len--){  
       if (0 != (*dest = *src)){  
           ++dest;    
           ++src;  
       }  
   }  
     
   if (len > 0)    
       while (len--)    
           *dest++ = '\0';   
 
   return ret;    
}


在安全性方面,显然strncpy要比strcpy安全得多,strcpy无法控制拷贝的长度,不小心就会出现dest的大小无法容纳src的情况,就会出现越界的问题,程序就会崩溃。而strncpy就控制了拷贝的字符数避免了这类问题,但是要注意的是dest依然要注意要有足够的空间存放src,而且src 和 dest 所指的内存区域不能重叠,

[cpp] view plain copy
<span style="font-family:Arial, Helvetica, sans-serif;background-color:rgb(255,255,255);">9.不创建临时变量,交换两个数</span>


:

[cpp] view plain copy
a = a + b;  
b = a - b;  
a = a - b;  
//  
a = a * b;  
b = a / b;  
a = a / b;  
// 以上两种方法可能会溢出  
 
a = a ^ b;  
b = a ^ b;  
a = a ^ b;  
// 优化版本( 可读性低, 效率低 )


10.数组除一个数字出现一次外别的数字都出现了两次,求这个数字

[cpp] view plain copy
int FindOneTimeNumber( int* arr, size_t len ){  
   assert( NULL != arr );  
 
   int tmp = arr[0];  
   for (int i = 1; i < len; ++i)  
       tmp = tmp ^ arr[i];  
 
   return tmp;  
}


11.数组中有两个数字只出现了一次,其余数字都出现了两次,找出这个数字

[cpp] view plain copy
void FindNumbers( int* arr, int lenint* num1, int* num2 ){  
   assert( NULL != arr && len > 0 );  
 
   int tmp = arr[0];  
   for (int i = 1; i < len; ++i)  
       tmp = tmp ^ arr[i];  
 
   unsigned int flag = 1;  
   while (1){  
       if (1 == ( tmp & flag ))  
           break;  
 
       flag <<= 1;  
   }  
 
   *num1 = 0;  
   *num2 = 0;  
   for (int i = 0; i < len; ++i){  
       if (1 == ( arr[i] & flag ))  
           *num1 = *num1 ^ arr[i];  
       else  
           *num2 = *num2 ^ arr[i];  
   }  
}


12.每瓶汽水1元,两个空瓶换一瓶。现有20元,最多喝几瓶

[cppview plain copy
int DrinkNumbers( int money )
{  
   assert( money >= 0 );  
 
   int sumDrinks = 0;  
   int emptyBottle = 0;  
 
   while (1){  
       if (0 == money && emptyBottle <= 1)  
           break;  
         
       sumDrinks += money;  
 
       if (0 == money % 2)  
           money /= 2;  
       else{  
           money /= 2;  
           ++emptyBottle;  
       }  
 
       if (2 == emptyBottle){  
           ++sumDrinks;  
           emptyBottle = 1;  
       }  
   }  
 
   return sumDrinks;  
}


13.获取一个数二进制序列的奇数位和偶数位

[cpp] view plain copy
int a[32];  
for (int i = 0; i < 32; ++i){  
   a[i] = data % 2;  
   data /= 2;  
}  
 
// 偶数  
for (int i = 1; i <= 31; i += 2)  
   cout << arr[i];  
 
// 奇数  
for (int i = 0; i <= 20; i += 2)  
   cout << arr[i];


14.输入一个非负整数,返回组成它的数字之和

[cppview plain copy
int Sum( int num )
{  
   assert( num >= 0 );  
 
   int sum = 0;  
   int m = 0;  
 
   while (0 != num){  
       m = num % 10;  
       num /= 10;  
 
       sum += m;  
   }  
 
   return sum;  
}


15.

:

[cpp] view plain copy
// 1 - 1/2 + 1/3 - 1/4 + ... 1/100  
double sum = 0.0;  
int flag = 1;  
 
for (int i = 1; i <= 100; ++i){  
   sum += ( 1 / (i * flag) );  
   flag *= -1;  
}  
 
// 求两个数的平均值,不用 (a+b) / 2  
average = (a & b) + ((a ^ b) >> 1);  
 
// 1-100中,9出现次数  
int times = 0;  
for (int i = 1; i <= 100; ++i){  
   if (9 == i % 10)  
       ++times;  
 
   if (9 == i / 10)  
       ++times;  
}  
 
// 将一个二进制数的奇偶位交换(中间的 或 换成 + 也可以)  
#define CHANGE(num) ( (((num) & (0X55555555)) << 1) | ((num) & (0XAAAAAAAA) >> 1) )  
 
// 判断一个字符串是否为另外一个字符串旋转之后的字符串  
bool IsRotateconst char* str1, const char* str2 ){  
   assert( NULL != str1 && NULL != str2 );  
 
   if (strlen( str1 ) != strlen( str2 ))  
       return false;  
 
   int newLen = strlen( str1 ) * 2 + 1;  
   char* pNew = (char*)malloc(newLen);  
   if (NULL == pNew)  
       return false;  
 
   strcpy( pNew, str1 );  
   strcat( pNew, str1 );  
 
   if (NULL == strstr( pNew, str2 ))  
       return false;  
 
   return true;  
}  
 
// 实现一个函数,将一个数的二进制翻转并输出翻转后的值  
unsigned int ReverseBitunsigned int val ){  
   unsigned int a = 0;  
   unsigned int ret = 0;  
 
   for (int i = 0; i < 32; ++i){  
        a = val & 1;  
        val >>= 1;  
 
        // 注意顺序,先移后或  
        ret <<= 1;  
        ret |= a;  
   }  
}


16.请写一个模板类,来求两个数中的最大值

注意:求两个"数"的最大值,若两个"数"不是内置类型,而是自定义类型比较大小。则1.看原自定义类型是否重载operator <( )运算符 2.仿函数


[cpp] view plain copy
template <typename T>  
class MaxNumber{  
public:  
   MaxNumber( )  
       : _a( a )  
       , _b( b )  
   {}  
 
   Max( ){  
       return _a > _b ? _a : _b;  
   }  
 
private:  
   T _a;  
   T _b;  
};  
 
int main( ){  
   int a;  
   int b;  
   cin >> a >> b;  
 
   MaxNumber<int> m( a, b );  
   m.Max( );  
 
   return 0;  
}


17.写一个函数来判断一个字符串是否是对称的 

[cpp] view plain copy
bool Funconst char* str )
{  
   bool ret = true;  
     
   if (NULL == str || ('\0' == *str))  
       return ret;  
 
   char* pBegin = str;  
   char* pEnd = str + strlen(str) - 1;  
 
   // 注意此处不能用 pBegin != pEnd 判断. 想想字符串为偶数个字符的情形(不包括'\0'),是否两个指针刚好错过了.  
   while (pBegin < pEnd){  
       if (*pBegin == *pEnd){  
           ++pBegin;  
           --pEnd;  
       }  
       else{  
           ret = false;  
           break;  
       }  
   }  
 
   return ret;  
}


18.二分查找的算法


[cpp] view plain copy
int BinarySearch( int* arr, int lenint key ){  
   assert( NULL != arr && len > 0 );  
 
   int left = 0;    
   int right = len - 1;    
   int mid = 0;  
 
   while (left<=right){    
       mid = left + ( (right-left)>>1 );// 不直接用 (left+right)/2 是 防止越界 和 提高效率    
 
       if (arr[mid] < key)    
           left = mid + 1;    
       else if (arr[mid] > key)    
           right = mid - 1;    
       else     
           return mid;  
   }  
 
   return -1;    
}


right = n - 1  =>  while (left <= right) (利用只有一个元素时情景去想, 如果是 left(0) < rightr(1 - 1 == 0) 循环根本进不去。 所以是<=)  =>  right = middle - 1(middle索引的元素已经被判断,又因为是 left <= right, 所以 right = mid - 1 如果是 right = mid, 这mid索引这个位置元素又会在下次循环被遍历一次);  


right = n  =>  while (left < right)(数组只有一个元素  0 < 1  -->  循环进去一次,<=循环进去两次)  =>  right = middle(middle已经被遍历, 又因为left < right, 若right = middle - 1则middle - 1那个元素被略过, 而right = middle -> left < right --> middle不会被重复遍历,middle - 1的元素也不会被略过);  
middle = ((left+right) >> 1);  这样的话 left 与 right 的值比较大的时候,其和可能溢出。  -->  middle = left + ( (right - left) >> 1 )


19.快速排序

[cpp] view plain copy
template <typename T>  
void QuickSortvector<T>& a ){  
   QuickSort( a, 0, a.size( )-1 );  
}  
 
template <typename T>  
const T& Median3vector<T>&a, int left, int right ){  
   int center = left + ((right - left) >> 1);  
 
   if (a[center] < a[left])  
       swap( a[left], a[center] );  
   if (a[right] < a[left])  
       swap( a[left], a[right] );  
   if (a[right] < a[center])  
       swap( a[center], a[right] );  
 
   swap( a[center], a[right - 1] );  
   return a[right - 1];  
}  
 
template <typename T>  
void QuickSortvector<T>& a, int left, int right ){  
   if (left + 10 <= right){  
       T pivot = median3( a, left, right );  
 
       int i = left;  
       int j = right - 1;  
       for ( ; ; ){  
           while (a[++i] < pivot)  
               ;  
 
           while (a[--j] > pivot)  
               ;  
 
           if ( i < j )  
               swap( a[i], a[j] );  
           else  
               break;  
       }  
 
       swap( a[i], a[right-1] );  
 
       QuickSort( a, left, i - 1 );  
       QuickSort( a, i + 1, right );  
   }  
   else  
       InsertSort( a, left, right );  
}


20.插入排序

插入排序由N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。事实:位置0到位置p-1上的元素是已经排过序的。

在第p趟,我们将位置p上的元素向左移动至它在前p+1个元素中的正确位置上。

[cpp] view plain copy
template <typename T>  
void InsertSortvector<T>& a ){  
   int j;  
 
   for (int p = 1; p < a.size( ); ++p){  
       T tmp = a[p];  
       for (j = p; j > 0 && tmp < a[j-1]; ++j)  
           a[j] = a[j-1];  
 
       a[j] = tmp;  
   }  
}


21.冒泡、选择


[cppview plain copy
void BubbleSort( void )
{    
   int i = 0;    
   int j = 0;    
   
   for (i = 0; i < n - 1; ++i){    
       for ( j = 0; j < n - i - 1; ++j ){    
               /*      */    
           }  
   }    
}    
 
void SelectSort( void ){    
   int maxIndex = 0;    
   int i = 0;    
   int j = 0;    
   
   for (i = 0; i < n - 1; ++i)  {    
       maxIndex = i;    
   
       for ( j = i; j < n - 1; ++j )// j 从 i 开始;下来依次为 j < n - 1; arr[j + 1];//j 从 i + 1 开始,则依次为 j < n; arr[j]  
       {    
           if ( arr[j + 1] > arr[maxIndex] )    
               maxIndex = j + 1;  
       }    
   
       swap ( arr[i], arr[maxIndex] );    
   }    
}


22.堆排序


[cpp] view plain copy
// 根据数组构建大堆  
void HeapAdjust( int* arr, int i, int len ){  
   int child;  
   int tmp;  
 
   for (; 2 * i + 1 < len; i = child){  
       // 子结点 = 2 * 父结点 + 1  
       child = 2 * i + 1;  
 
       // 得到子结点中数值较大的结点  
       if (child < len-1 && arr[child+1] > arr[child])  
           ++child;  
 
       // 如果较大的子结点值大于父结点的值那么把较大的子结点往上移动,替换它的父结点  
       if (arr[i] < arr[child]){  
           tmp = arr[i];  
           arr[i] = arr[child];  
           arr[child] = tmp;   
       }  
       else   
           break;   
   }  
}  
 
//堆排序算法  
void HeapSort( int* arr, int len ){  
   int i;  
 
   // length/2-1是最后一个非叶节点  
   // 构建大堆  
   for (i = len/2-1; i >= 0; --i)  
       HeapAdjust( arr, i, len );  
 
   // 从最后一个元素开始对序列进行调整,不断缩小调整范围直到第一个元素  
   for (i = len-1; i > 0; --i){  
       // 把第一个元素和当前的最后一个元素交换  
       // 保证当前最后一个位置的元素都是现在这个序列之中最大的元素  
       arr[i] = arr[0] ^ arr[i];  
       arr[0] = arr[0] ^ arr[i];  
       arr[i] = arr[0] ^ arr[i];  
 
       // 不断缩小调整范围,每一次调整完毕后保证第一个元素是当前序列的最大值  
       HeapAdjust( arr, 0, i );  
   }  
}


23.希尔排序(插入排序的改进版本),希尔排序的步长现在还没有最好的选择,不过已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...)

操作步骤:


初始时,有一个大小为 10 的无序序列。
(1)在第一趟排序中,我们不妨设 gap1 = N / 2 = 5,即相隔距离为 5 的元素组成一组,可以分为 5 组。
(2)接下来,按照直接插入排序的方法对每个组进行排序。
在第二趟排序中,我们把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。
(3)按照直接插入排序的方法对每个组进行排序。
(4)在第三趟排序中,再次把 gap 缩小一半,即gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。
(5)按照直接插入排序的方法对每个组进行排序。此时,排序已经结束。

时间复杂度:


最好情况:由于希尔排序的好坏和步长gap的选择有很多关系,因此,目前还没有得出最好的步长如何选择(现在有些比较好的选择了,但不确定是否是最好的)。所以,不知道最好的情况下的算法时间复杂度。
最坏情况下:O(N*N)


空间复杂度:


由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为O(1)。


算法稳定性


希尔排序中相等数据可能会交换位置,所以希尔排序是不稳定的算法。
 在使用 增量k的一趟排序后,对于每一个i,我们有 a[i] <= a[i + k]。所有相隔k的元素都被排序。希尔排序一个重要性质:一个k排序的文件保持它的k排序性。如果情况不是这样,该排序就没有意义了,因为后面各趟排序会打乱前面各趟排序的成果。

k排序的一般做法:对于i,i+k,i+2k...中的每一个位置,都把其上的元素放到前面序列中的正确位置上。

一趟k排序作用是对k个独立的子数组进行一次插入排序。

[cpp] view plain copy
template <typename T>  
void ShellSortvector<T>& a ){  
   for (int gap = a.size( ) / 2; gap > 0; gap /= 2)  
       for (int i = gap; i < a.size( ); ++i){  
           T tmp = a[i];  
           int j = i;  
 
           for (; j >= gap && tmp < a[j - gap]; j-= gap)  
               a[j] = a[j - gap];  
 
           a[j] = tmp;  
       }  
}


24.归并排序

合并两个已排序的表时间是线性的,最多进行N-1次比较,N是两个表元素的总数,因为每次比较都把一个元素添加到临时数组中,但最后一次比较除外,它至少添加两个元素。

如果对Merge的每个递归调用均局部声明一个临时数组,那么在任一时刻就可能有 logN个临时数组处在活动期。   由于Merge是MergeSort的最后一行,因此在任一时刻只需要一个临时数组活动,而且这个临时数组可以在MergeSort驱动程序中建立。还可以使用临时数组的任意部分。我们将使用与输入数组a相同的部分。

[cpp] view plain copy
template <typename T>  
void MergeSortvector<T>& a ){  
   vector<T> tmpArr( a.size( ) );  
 
   MergeSort( a, tmpArr, 0, a.size( ) - 1 );  
}  
 
template <typename T>  
void MergeSortvector<T>& a, vector<T>& tmpArr, int left, int right ){  
   if (left < right){  
       int center = ( left + right ) / 2;  
       MergeSort( a, tmpArr, left, center );  
       MergeSort( a, tmpArr, left+1, right );  
       Merge( a, tmpArr, left, center+1, right );  
   }  
}  
 
template <typename T>  
void Mergevector<T>& a, vector<T>& tmpArr, int leftPos, int rightPos, int rightEnd ){  
   int leftEnd = rightPos - 1;  
   int tmpPos = leftPos;  
   int num = rightPos - leftPos + 1;  
 
   while (leftPos <= leftEnd && rightPos <= rightEnd)  
       if (a[leftPos] <= a[rightPos])  
           tmpArr[tmpPos++] = a[leftPos++];  
       else  
           tmpArr[tmpPos++] = a[rightPos++];  
 
   while (leftPos <= leftEnd)  
       tmpArr[tmpPos++] = a[leftPos++];  
 
   while (rightPos <= rightEnd)  
       tmpArr[tmpPos++] = a[rightEnd++];  
 
   for (int i = 0; i < num; i++, rightEnd--)  
       a[rightEnd] = tmpArr[rightEnd];  
}


25. 实现时间复杂度为O(nlogn)的链表排序算法

http://blog.csdn.net/mbuger/article/details/70259940


26.模拟实现strlen

[cpp] view plain copy
int MyStrlen( const char* str ){    
   assert( NULL != str );   
     
   int len = 0;  
   while ('\0' != *str++)  
       len++;  
     
   return len;    
}


不用变量版本:

[cpp] view plain copy
int MyStrlenconst char* src )
{  
   assert( NULL != src );  
 
   if ('\0' == *src)  
       return 0;  
   else  
       return ( 1 + MyStrlen( ++str ) );  
}


27.模拟实现memcpy、memmove和memset

strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容(内存中的内容),例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

[cpp] view plain copy
voidMyMemcpyvoid* dest, const void* src, size_t n )
{    
   assert( NULL != dest );    
   assert( NULL != src );  
 
   char* pDest = (char*)dest;    
   const char* pSrc = (const char*)src;  
   while (n--)  
       *pDest++ = *pSrc++;  
 
   return dest;    
}


[cpp] view plain copy
voidMyMemmovevoid* dest, const void* src, size_t count )
{    
   assert( NULL != dest );    
   assert( NULL != src );   
 
   char* pDest = (char*)dest;    
   const char* pSrc = (const char*)src;    
   
   if (pDest <= pSrc || pSrc + count <= pDest)// 正常情况下,从前往后拷贝     
       while (count--)    
           *pDest++ = *pSrc++;    
   else    
       while (count--)    
           *(pDest + count - 1) = *(pSrc + count - 1);  
   
   return dest;    
}


[cpp] view plain copy
voidMyMemsetvoid* dest, int c, size_t count )  
{    
   assert( NULL != dest );  
 
   char* tmp = (char*)dest;  
   while (count--){    
       *tmp = (char)c;    
       tmp++;  
   }  
 
   return dest;    
}


memcpy
内存拷贝函数
source和destin所指的内存区域可能重叠,但是如果source和destin所指的内存区域重叠,那么这个函数并不能够确保source所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。函数返回指向destin的指针。


memmove
内存移动函数
如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后src内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。memmove函数能够反向拷贝,不用在意memcpy函数会遇到的问题。


memset
将dest中从当前位置开始的count个字节,用 c 替换并返回 dest 。

在strcpy,strncpy,memcpy和memmove这四个库函数中,安全性是递增的,前三个函数均没有考虑到内存重叠的问题,所以相对来说memmove函数的安全性最高。


28.从键盘输入一个十进制正整数,将其转换成八进制后输出

[cpp] view plain copy
#include <stdlib.h>  
#include <stdio.h>  
 
 
int mainvoid ){  
   int number = 123456;  
   char string[25];  
     
   itoa( number, string8 );  
   printf"string = %s\n"string );  
     
   return 0;  
}  
 
 
#include <stdio.h>  
#include <math.h>  
 
 
int dtoeint a ){   
   if (a < 8)   
       return a;  
   else   
       return ( dtoe(a / 8) * 10 + a % 8 );   
 
   return 0;  
}   
 
 
int mainvoid ){   
   int tmp;  
 
   scanf"%d", &tmp );   
   tmp = dtoe( tmp );  
   printf"%d\n", tmp );  
 
   return 0;   
}


上期回顾:

面试->代码 剑指offer(一)


点击图片get往期内容

登录查看更多
0

相关内容

一份简明有趣的Python学习教程,42页pdf
专知会员服务
76+阅读 · 2020年6月22日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
161+阅读 · 2020年5月14日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
49篇ICLR2020高分「图机器学习GML」接受论文及代码
专知会员服务
61+阅读 · 2020年1月18日
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
深度学习面试100题(第81-85题)
七月在线实验室
16+阅读 · 2018年8月6日
深度学习面试100题(第71-75题)
七月在线实验室
5+阅读 · 2018年8月2日
深度学习面试100题(第41-45题)
七月在线实验室
15+阅读 · 2018年7月18日
开发、调试计算机视觉代码有哪些技巧?
AI研习社
3+阅读 · 2018年7月9日
机器学习面试题精讲(一)
七月在线实验室
4+阅读 · 2018年1月11日
BAT机器学习面试题及解析(266-270题)
七月在线实验室
6+阅读 · 2017年12月13日
BAT题库 | 机器学习面试1000题系列(第211~215题)
七月在线实验室
9+阅读 · 2017年11月22日
python数据分析师面试题选
数据挖掘入门与实战
6+阅读 · 2017年11月21日
BAT题库 | 机器学习面试1000题系列(第196~200题)
七月在线实验室
17+阅读 · 2017年11月16日
BAT机器学习面试1000题系列(第76~80题)
七月在线实验室
5+阅读 · 2017年10月13日
EfficientDet: Scalable and Efficient Object Detection
Arxiv
6+阅读 · 2019年11月20日
Efficient and Effective $L_0$ Feature Selection
Arxiv
5+阅读 · 2018年8月7日
Arxiv
4+阅读 · 2018年5月10日
VIP会员
相关VIP内容
一份简明有趣的Python学习教程,42页pdf
专知会员服务
76+阅读 · 2020年6月22日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
161+阅读 · 2020年5月14日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
49篇ICLR2020高分「图机器学习GML」接受论文及代码
专知会员服务
61+阅读 · 2020年1月18日
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
相关资讯
深度学习面试100题(第81-85题)
七月在线实验室
16+阅读 · 2018年8月6日
深度学习面试100题(第71-75题)
七月在线实验室
5+阅读 · 2018年8月2日
深度学习面试100题(第41-45题)
七月在线实验室
15+阅读 · 2018年7月18日
开发、调试计算机视觉代码有哪些技巧?
AI研习社
3+阅读 · 2018年7月9日
机器学习面试题精讲(一)
七月在线实验室
4+阅读 · 2018年1月11日
BAT机器学习面试题及解析(266-270题)
七月在线实验室
6+阅读 · 2017年12月13日
BAT题库 | 机器学习面试1000题系列(第211~215题)
七月在线实验室
9+阅读 · 2017年11月22日
python数据分析师面试题选
数据挖掘入门与实战
6+阅读 · 2017年11月21日
BAT题库 | 机器学习面试1000题系列(第196~200题)
七月在线实验室
17+阅读 · 2017年11月16日
BAT机器学习面试1000题系列(第76~80题)
七月在线实验室
5+阅读 · 2017年10月13日
Top
微信扫码咨询专知VIP会员