面试-;代码

Posted ssopp24

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试-;代码相关的知识,希望对你有一定的参考价值。

一.剑指offer

代码鲁棒性(输入参数检查等)贯穿所有代码当中,不会特别强调. 


1.赋值运算符函数

注意:

1.返回值类型为该类型引用(链式赋值),返回*this

2.传入参数常量引用

3.释放自身已有空间

4.判断是不是自身赋值


例:

MyString& MyString::operator=( const MyString& str )
	if ( this == &str )
		return *this;

	delete[] m_pData;
	m_pData = NULL:

	m_pData = new char[strlen(str.m_pData) + 1];
	strcpy( m_pData, str.m_pData );

	return *this;

不足:若内存不足,new申请内存时抛出异常,则m_pData已经被delete,此时其为一个空指针,非常容易导致程序奔溃。 在赋值运算符函数内部抛异常,CMyString实例不再保持有效的状态,违背异常安全性原则.

改进:1.先new再delete    2.创建临时实例,交换临时实例和原来的实例

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;


3.二维数组中的查找:二维数组,每一行,从左到右递增。每一列,从上到下递增。 输入一个二维数组和一个整数,判断数组中是否含有该整数

思路:选取数组右上角数字,等于->结束。 大于->剔除这一列。 小于->剔除这一行。  这样每比较一次剔除一列或一行,缩小范围,直到找到要查找的数字or查找范围为空

例:

bool Find( int* matrix, int rows, int cols, int number )
	bool found = false;

	if ( NULL != matrix && rows > 0 && cols > 0 )
		int row = 0;
		int col = cols - 1;

		while ( row < rows && col >= 0 )
			if ( number == matrix[row * cols + col] )
				found = true;
				return found;
			
			else if ( matrix[row * cols + col] > number )
				--col;
			else
				++row;
		
	

	return found;

二维数组在内存中占据连续的空间。在内存中从上到下存储各行元素,每行从左到右顺序存储


4.替换空格

1.在原串上操作  -->  保证原串后面有足够的空间  -->  从后往前替换

2.开辟新空间  -->  直接替换

两种思路都需遍历原串一次获取空格个数 --> O(N)

相关题目:两个排序数组A1,A2。A1后有足够空间容纳A2,将A2中数字插入A1中使所有数字依旧有序

思路:遍历两个数组各一次得到两数组长度,三个指针,一个指针指向lengthA1+lengthA2-1,其余两个分别指向A1与A2末尾。 依次比较,将合适数字放到第一个指针所指位置

合并两个数组(字符串)时,如果从前往后赋值每个数字or字符需要重复移动数字or字符多次  -->  从后往前复制,减少移动次数,提高效率


5.从尾到头打印链表

1.栈

2.递归


7.用两个栈实现队列

思路:栈S1和S2,入“队列”入栈S1。出“队列”,若S2有元素S2.pop( ), 否则,将S1中元素压入栈S2

相关题目:两个队列实现栈


 8.旋转数组中最小的数字  -->  把数组最开始的若干个元素搬到数组的末尾(旋转数组),输入一个递增数组的旋转,输出最小数字

思路:数组旋转后可将其视为两个排序的子数组,并且前面子数组元素都大于等于后面子数组元素,最小的元素是第二个子数组的第一个元素(我们要找最小的元素) --> 二分(O(logn))(每移动一次指针,查找范围缩小为原来的一半)  -->  p1指向数组第一个元素,p2指向数组最后一个元素。 如果中间元素大于p1指向数据,即中间元素在第一个子数组中,将p1指向这。 否则如果 中间元素小于p2指向元素,将p2指向中间元素。 两个指针慢慢靠近  直到  第一个指针指向第一个子数组最后一个元素,第二个指针指向第二个子数组第一个元素, 两指针相邻,此时第二个指针指向数组中最小的元素。 循环结束

特殊情况:

1.如果移动元素个数为0,此时第一个元素就是最小的元素,代码中我们将 indexMid初始化为index1,如果第一个数字小于最后一个数字,直接返回。

2. 1, 0, 1, 1, 1  与  1, 1, 1, 0, 1 这种 两个指针指向的数字及它们中间数字三者相同的时候,无法判断中间数字位于哪个子数组中,也就无法移动指针来缩小查找的范围,此时 --> 顺序查找

int Min( int* arr, int len )
	if (NULL == arr || len <= 0)
		throw new std::exception( "Invalid parameters" );

	int index1 = 0;
	int index2 = len - 1;
	int indexMid = index1;

	while (arr[index1] >= arr[index2])
		if (1 == index2 - index1)
			return arr[index2];

		indexMid = (index1 + index2) / 2;

		// 顺序查找
		if (arr[index1] == arr[index2] && arr[index1] == arr[indexMid])
			return MinInOrder( arr, index1, index2 );

		if (arr[indexMid] >= arr[index1])
			index1 = indexMid;
		else if (arr[indexMid] <= arr[index2])
			index2 = indexMid;
	


int MinInOrder( int* arr, int index1, int index2 )
	int result = arr[index1];

	for (int i = index1 + 1; i <= index2; ++i)
		if (arr[i] < result)
			result = arr[i];

	return result;


9.斐波那契数列: 

n    f(n)

0    0

1    1

>1  f(n-1)+fr(n-2)


1.递归

2.循环:

long long Fibonacci( unsigned n )
	int result[2] = 0, 1;
	if (n < 2)
		return result[n];

	long long fibOne = 0;
	long long fibTwo = 1;
	long long fibN = 0;

	// 注意这里是 i<=n, 而不是i<n
	for (unsigned int i = 2; i <= n; ++i)
		fibN = fibOne + fibTwo;

		fibOne = fibTwo;
		fibTwo = fibN;
	

	return fibN;


相关题目:

1.一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
 n = 1, 1种方法(一次1级台阶)  
 n = 2, 2种方法( 两次1级, 或者一次2级 )  
前两种为基准情况  
  
 n = 3, 3( 三次1级, 或一次1级一次2级(顺序不同,两个方法) )  
我们通过 台阶数为3来分析。 青蛙一次只能跳一级或者两级  
也就是说: 青蛙跳上三级台阶最后一跳只有两种情况,跳一级或者跳两级,所以青蛙跳三级台阶总的方法数位: 青蛙跳至只剩一级台阶和只剩两级台阶的方法数之和  
即 F(n) = F(n-1) + F(n-2)  
或者,我们多写几种情况,也可以发现规律,方法数为 前一次方法数 + 后一次方法数

:

int jumpFloor( int number )    
    if (number <= 0)  
        return 0;  
    else if (1 == number)  
        return 1;  
    else if (2 == number)  
        return 2;  
          
    return jumpFloor( number-2 ) + jumpFloor( number-1 );  
  

2.青蛙一次可以跳上1级台阶,也可以跳上2级……跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。  
思路: 假设台阶为n级,则青蛙可以 跳一次或者多次。  一次: 直接跳n级,这是一种方法。  多次: 青蛙跳到 1到n-1级 任一级(不管怎样跳,跳几次)后再跳一次到n级。  
或者这样分析,青蛙最后一跳,有可能是从起点直接跳到终点,或者从起点跳了若干步后(到达 1到n-1级中间任一级 )再跳到n级。  
所以总的方法数为:青蛙 跳到 1级到n-1级 每级可能的方法数(再跳到n级) + 1(直接跳到n级)  
F(n) = F(n-1) + F(n-2) + ... + F(1) + 1;  
可得: F(n-1) = F(n-2) + F(n-3) +..._F(1) + 1;  
则F(n) = F(n-1) + F(n-1)  
即F(n) = 2F(n-1)  

:

int jumpFloorII( int number )  
    if (number <= 0)  
        return 0;  
          
    if (1 == number)  
        return 1;  
      
    return 2 * jumpFloorII( number-1 );  
  
3.我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
思路:  对于n>=3的情况, 不管前面矩形是怎样覆盖的。我们只考虑最后一次怎么覆盖。
最后一次只有两种覆盖方式:1.用一个小矩形竖着覆盖。   2.用两个小矩形横着覆盖。
所以总的方法数无外乎   -->  你用各种方法覆盖到只剩1个再竖着覆盖或者你用各种方法覆盖到只剩两个再横着覆盖
即:总的方法数F(n) = n-1次的方法数F(n-1)(接着用一个小矩形竖着覆盖) + n-2次的方法数F(n-2)(接着用两个小矩形横着覆盖)

int rectCover( int number ) 
	if ( number <= 0 )
		return 0;
        
	if ( 1 == number )
		return 1;
        
	if ( 2 == number )
		return 2;
	
	return rectCover( number-1 ) + rectCover( number-2 );

10.二进制中1的个数  输入一个整数,输出该整数二进制表示中1的个数

思路:一个整数减去1再和原整数做与行算,会把该整数二进制形式最右边一个1变为0. 那么二进制表示有多少个1就可以进行多少次这样的操作。

:

int NumberOf1( int n )
	int count = 0;

	while (0 != n)
		++count;
		n = (n - 1) & n;
	

	return count;

常规思路:将1依次左移判断数字每一位是否是1(注意不能将数字右移 与 1进行&运算,因为负数右移补符号位,数字最终变为0XFFFFFFFF,死循环)

int NumberOf1( int n )
	int count = 0;
	unsigned int flag = 1;

	while (0 != flag)
		if (0 != (flag & n))
			++count;

		flag = flag << 1;
	

	return count;

相关题目:

1.用一条语句判断一个整数是不是2的整数次方

思路:如果一个整数是2的整数次方,则它二进制表示中只有一位为1, 把这个整数减去1和它自己做与运算,这个整数中唯一的1就会变为0


2。输入两个整数m,n。 计算需要改变m的二进制表示中多少位才能得到n

思路:第一步 将m与n异或, 第二部求异或结果中1的位数


13.在O(1)时间删除链表结点

思路:1.如果链表只有一个结点(头节点),直接删除  2.如果要删除结点为尾结点,则用常规方法(O(N))  3.删除多个节点的非尾结点(将下一个节点 value值赋给被删结点, 删除下个节点)


14.调整数组顺序使奇数位于偶数前面

两个指针p1,p2, 一个从前往后遍历,一个从后往前遍历

void Reorder( int* arr, unsigned int len, bool (*func)(int) )
	if (NULL == arr || len == 0)
		return;

	int* pBegin = arr;
	int* pEnd = arr + len - 1;

	while (pBegin < pEnd)
		while (pBegin < pEnd && !func(*pBegin))
			++pBegin;

		while (pBegin < pEnd && func(*pEnd))
			--pEnd;

		if (pBegin < pEnd)
			int temp = *pBegin;
			*pBegin = *pEnd;
			*pEnd = temp;
		
	


bool IsEven( int n )
	return 0 == ( n & 1 );


void ReorderOddEven( int* arr, unsigned int len )
	Reorder( arr, len, IsEven );

15.链表倒数第k个结点

想象有一把长度为k的尺子放在链表上,两个指针p1(尺子头),p2(尺子尾)。  当p2到达链表尾部时p1正好指向倒数第k个结点

注意这个题重点考虑鲁棒性


相关题目:

1.求链表中间结点

思路:两个指针,p1,p2。 p1一次走一步,p2一次走两步。  当p2指向链表最后一个结点时, p1正好指向链表中间节点


2.判断链表是否带环

思路:快慢指针,相遇->带环。 快指针到链表尾->不带环


当我们用一个指针遍历链表不能解决问题时,可以尝试两个指针来遍历链表。 可以让其中一个指针走的快些(如一次走两步),或者让它先在链表上走若干步


16.反转链表

:

ListNode* ReverseList( ListNode* pHead )   
  
	// 链表为空和链表只有一个结点在这个语句进行处理
    if (NULL == pHead || NULL == pHead->next)  
        return pHead;  
      
    ListNode* pPrev = NULL;  
    ListNode* pCur = pHead;  
    ListNode* pReverseHead = NULL;  
          
    while (NULL != pCur)  
		ListNode* pNext = pCur->next;  

		if (NULL == pNext)
			pReverseHead = pCur;

		pCur->next = pPrev;

        pPrev = pCur;  
        pCur = pNext;  
      
      
    return pReverseHead;  
  

17.合并两个排序的链表

ListNode* Merge( ListNode* pHead1, ListNoe* pHead2 )    
    if (NULL == pHead1)  
        return pHead2;  
    else if (NULL == pHead2)  
        return pHead1;  
  
    ListNode* pMergedHead = NULL;  
  
    if (pHead1->val < pHead2->val)  
        pMergeHead = pHead1;  
        pMergeHead->next = Merge( pHead1->next, pHead2 );  
      
    else  
        pMergeHead = pHead2;  
        pMergeHead->next = Merge( pHead1, pHead2->next );  
      
  
    return pMergedHead;  
  

21.包含min函数的栈     定义一个可以使min, push, pop都为O(1)的栈

思路:借用辅助栈,每次数据栈元素入栈时,将所有入栈元素中最小值入辅助栈(保证辅助栈和数据栈->同入同出)

   void Push( const int& data )  
        dataS.push( data );  

		if (0 == minS.size( ) || data < minS.top( ))
			minS.push( data );
		else
			minS.push( minS.top( ) );
      
  
    void Pop( )
		assert( dataS.size( ) > 0 && minS.size( ) > 0 );

		dataS.pop( );
		minS.pop( );
      
  
    const T& Min( ) const 
		assert( dataS.size( ) > 0 && minS.size( ) > 0 );

		return minS.top( );
      

23.从上往下打印二叉树(层序遍历二叉树)

借助队列

void LevelPrint( Node* pRoot )  
    if (NULL == pRtoot)  
		return;
  
    queue<Node*> queueNode;  
    queueNode.push( pRoot );  
  
    while (0 != queueNode.size( ))    
        Node* pNode = queueNode.front( );  
        queueNode.pop( );
  
        Visit( pNode );  
  
        if (NULL != pNode->pLeft)  
            queueNode.push(pNode->pLeft);  
  
        if (NULL != pNode->pRight)  
            queueNode.push(pNode->pRight);  
	  
  

29.数组中出现次数超过一半的数字

思路:如果有一个数字出现次数超过数组元素总个数的一半,则它出现次数比其他数字出现的次数总和还要多。

:

bool g_inputInvalid = false;


bool CheckInvalidArr( int* arr, int len )
	g_inputInvalid = false;

	if (NULL == arr || len <= 0)
		g_inputInvalid = true;

	return g_inputInvalid;



bool CheckMoreThanHalf( int* arr, int len, int number )
	int times = 0;

	for (int i = 0; i < len; ++i)
		if (number == arr[i])
			++times;

	bool isMoreThanHalf = true;
	if (times * 2 <= len)
		g_inputInvalid = true;
		isMoreThanHalf = false;
	

	return isMoreThanHalf;



int MoreThanHalfNum( int* arr, int len )
	// 如果只返回0存在二义性,是无效参数返回0还是0出现次数超过一半呢?
	// 定义一个函数 和 一个全局变量,即可区分这两种情况
	if (CheckInvalidArray( arr, len ))
		return 0;

	int result = arr[0];
	int times = 1;

	for (int i = 1; i < len; ++i)
		if (0 == times)
			result = arr[i];
			times = 1;
		
		else if (result == arr[i])
			++times;
		else
			--times;
	
	
	// 此时result中保存的数字并不一定是数组中出现次数超过数组长度一半的数字,需要验证
	// 11234
	if (!CheckMoreThanHalf( arr, len, result ))
		result = 0;

	return result;


37.两个链表的第一个公共结点

思路:

1.借助栈,两个链表都从后往前遍历,直到找到最后一个相同结点

2.遍历两个链表一次,得到链表长度,len1,len2。假设len1 为5, len2 为4。 则在链表1上先走(len1 - len2)步,接着同时开始走,第一个相同节点


38.数字在排序数组中出现的次数

思路:

因为是排序数组,找到第一个k和最后一个k即可知道k出现的次数  O(log n)二分

int GetFirstK( int* arr, int len, int k, int start, int end )
	// 这段区间内没找到k, 返回-1
	if (start > end)
		return -1;

	int midIndex = (start + end) / 2;
	int midData = arr[midIndex];

	if (k == midData)
		if ((midIndex > 0 && k != arr[midIndex - 1])
			|| 0 == midIndex)
			return midIndex;
		else
			end = midIndex - 1;
	
	else if (midData > k)
		end = midIndex - 1;
	else
		start = midIndex + 1;

	return GetFirstK( arr, len, k, start, end );



int GetLastK( int* arr, int len, int k, int start, int end )
	if (start > end)
		return -1;

	int midIndex = (start + end) / 2;
	int midData = arr[midIndex];

	if (k == midData)
		if ((midIndex < len - 1 && k != arr[midIndex + 1])
			|| len - 1 == midIndex)
			return midIndex;
		else
			start = midIndex + 1;
	
	else if (midData > k)
		end = midIndex - 1;
	else
		start = midIndex + 1;

	return GetLastK( arr, len, k, start, end );



int GetTimesOfK( int* arr, int len, int k )
	int times = 0;

	if (NULL != arr && len > 0 )
		int first = GetFirstK( arr, len, k, 0, len - 1 );
		int last = GetLastK( arr, len, k, 0, len - 1 );

		if (first > -1 && last > -1)
			times = last - first + 1;
	

	return times;


39.二叉树的深度

:

int TreeDepth( BinaryTreeNode* pRoot )
	if (NULL == pRoot)
		return 0;
	 
	int depthLeft = TreeDepth( pRoot->left );
	int depthRight = TreeDepth( pRoot->right );

	return (depthleft > depthRight) ? (depthLeft + 1) : (depthRight + 1);

相关题目:判断一棵树是不是平衡二叉树。 左右子树的深度相差不超过1.

1.需要重复遍历多次的解法

bool IsBalanced( BinaryTreeNode* pRoot )
	if (NULL == pRoot)
		return true;
	 
	int depthLeft = TreeDepth( pRoot->left );
	int depthRight = TreeDepth( pRoot->right );
	int diff = left - right;

	if (diff > 1 || diff < -1)
		return false;

	return IsBalanced( pRoot->left ) && IsBalanced( pRoot->right );

后序遍历,每个节点只遍历一次

bool IsBalanced( BinaryTreeNode* pRoot, int* pDepth )
	if (NULL == pRoot)
		*pDepth = 0;
		return true;
	

	int left;
	int right;

	if (IsBalanced( pRoot->left, &left ) && IsBalanced( pRoot->tight, &right ))
		int diff = left - right;
		if (diff >= -1 && diff <= 1)
			*pDepth = left > right ? (left + 1) : (right + 1);
			return true;
		
	

	return false;



bool IsBlanced( BinaryTreeNode* pRoot )
	int depth = 0;
	
	return IsBlanced( pRoot, &depth );

42.翻转单词顺序 vs 左旋转字符串

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。 标点符号和普通字母一样处理。  -->  "I am a student." 变为 "student. a am I"

思路:

1.翻转句子中所有的字符。此时不但翻转了句子中单词的顺序,连单词内的字符的顺序也翻转了

2.翻转每个单词中字符的顺序

void Reverse( char* pBegin, char* pEnd )
	if (NULL == pBegin || NULL == pEnd)
		return;

	while (pBegin < pEnd)
		char temp = *pBegin;
		*pBegin = *pEnd;
		*pEnd = temp;

		++pBegin;
		--pEnd;
	



char* ReverseSentence( char* pData )
	if (NULL == pData)
		return NULL;

	char* pBegin = pData;
	char* pEnd = pData;

	while ('\\0' != *pEnd)
		++pEnd;

	--pEnd;

	// 翻转整个句子
	Reverse( pBegin, pEnd );

	// 翻转句子中每个单词
	pBegin = pEnd = pData;
	while ('\\0' != *pBegin)
		if (' ' == *pBegin)
			++pBegin;
			++pEnd;
		
		else if (' ' == *pEnd || '\\0' == *pEnd)
			Reverse( pBegin, --pEnd );
			pBegin = ++pEnd;
		
		else
			++pEnd;
	

	return pData;

相关题目:字符串左旋/右旋是把字符串前面的若干个字符转移到字符串的尾部。 “abcdefg” 和 数字2  -->  "cdefgab"

思路:将要左旋的字符串和剩余字符串分别翻转,再翻转整个字符串

char* LeftRotateString( char* pStr, int n )
	if (NULL != pStr)
		int nLength = strlen( pStr );
		if (nLength > 0 && n > 0 && n < nLength)
			char* pFirstStart = pStr;
			char* pFirstEnd = pStr + n - 1;
			char* pSecondStart = pStr + n;
			char* pSecondEnd = pStr + nLength - 1;

			Reverse( pFirstStart, pFirstEnd );
			Reverse( pSecondStart, pSecondEnd );
			Reverse( pFirstStart, pSecondEnd );
		
	

50.树中两个结点的最低公共祖先

1.二叉搜索树

BSTNode* GetLastCommonNode( BSTNode* pRoot, BSTNode* pNode1, BSTNode* pNode2 )
	if (NULL == pRoot)
		return NULL;
	else if (NULL == pNode1)
		return pNode2;
	else if (NULL == pNode2)
		return pNode1;
	else if (pNode1 == pNode2)
		return pNode1;

	int val = pRoot->val;
	int val1 = pNode1->val;
	int val2 = pNode2->val;

	if (val1 < val && val2 < val)
		// 注意,递归时,有返回值,调用函数一定要加 return。 无返回值则可以只递归调用,做你想做的事情, 不用加return
		return GetLastCommonNode( pRoot->left, pNode1, pNode2 );
	else if (val1 > val && val2 > val)
		return GetLastCommonNode( pRoot->right, pNode1, pNode2 );
	else
		if (val1 == val)
			return pNode1;
		else if (val2 == va1)
			return pNode2;
		else
			return pRoot;
	


2.普通树,含指向父节点指针

BTreeNode* FindFirstCommonNode( TreeNode* pNode1, TreeNode* pNode2 )
	if (NULL == pNode1)
		return pNode2;
	else if (NULL == pNode2)
		return pNode1;

	stack<TreeNode*> s1;
	stack<TreeNode*> s2;

	// 注意:入栈第一个结点分别是这两个结点,而不是它们的父结点
	while (NULL != pNode1)
		s1.push( pNode1 );
		pNode1 = pNode1->parent;
	

	while (NULL != pNode2)
		s2.push( pNode2 );
		pNode2 = pNode2->parent;
	

	TreeNode* firstCommonNode = NULL;

	// 如果两个结点没在同一颗二叉树中, 返回NULL
	if (s1.top( ) != s2.top( ))
		return NULL;

	while (0 != s1.size( ) && 0 != s2.size( ))
		if (s1.top( ) == s2.top( ))
			firstCommonNode = s1.top( );

			s1.pop( );
			s2.pop( );
		
		else
			break;
	

	return firstCommonNode;

56.链表中环的入口结点

思路:定义指针P1,P2。 P1在链表上走环大小步后P2开始以相同速度遍历链表,两个指针相遇点即为链表中环的入口结点

:

	// 首先判断单链表是否带环,若带环(此时快慢指针已经相遇),从此刻起计步,直到下次两个指针再相遇,快指针步数减去慢指针步数,即为环的长度
    ListNode* EntryNodeOfLoop( ListNode* pHead )  
      
        if (NULL == pHead)  
            return NULL;  
  
        // 定义快慢指针判断是否有环  
        ListNode* pFast = pHead;  
        ListNode* pSlow = pHead;  
  
        // 如果有环,通过这两个变量求得环长度  
        int fastLength = 0;  
        int slowLength = 0;  
        int loopLength = 0;  
          
        // 求出环长度后通过这两个结点求出环入口节点  
        ListNode* p1 = pHead;  
        ListNode* p2 = pHead;  
         
        // 设置个死循环,如果链表不带环,我们在循环里直接退出即可  
        while ( 1 )  
            // 在两个指针走之前必须判断pFast和pFast的next是否为空,两个条件都必须判断而且判断顺序不能出错  
            // 因为快指针走得快,我们只需要判断快指针而不需要判断慢指针。如果为空,即链表不带环,返回NULL
            // 检测是否带环同时也避免指针越界访问  
            if (NULL == pFast || NULL == pFast->next)  
                return NULL;  
              
            pFast = pFast->next->next;  
            pSlow = pSlow->next;  
            
            // 此时我们再判断两个指针是否相遇,不能在指针还没走的时候判断,因为最开始,两个指针就是相遇的  
            if (pFast == pSlow)  
                break;
          
          
        // 程序运行到此处,两个指针已经相遇,此时我们求环的长度  
        while ( 1 )  
            pFast = pFast->next->next;  
            fastLength += 2;  
              
            pSlow = pSlow->next;  
            ++slowLength;  
              
            if (pFast == pSlow)  
                break;  
          
          
        loopLength = fastLength - slowLength;  
        // 环长度已知,此时我们来找环的入口结点  
        while ( 1 )  
            // p1先走环的长度步 loopLength  
            for ( int i = 0; i < loopLength; ++i )  
                p1 = p1->next;  
              
            // 因为这个带环链表入口结点有可能就是第一个结点,所以如果此时p1走了环的长度步后回到了原点(即回到环的入口处(链表的第一个结点))与p2相遇了,则直接返回p1。即为环入口结点  
            if ( p1 == p2 )  
                return p1;  
              
            //否则p1 p2以相同速度开始走  
            while ( 1 )  
                p1 = p1->next;  
                p2 = p2->next;  
                  
                //同样的,先走后判断  
                //此时即找到了环的入口结点  
                if ( p1 == p2 )  
                    return p1;  
              
          
      


二.非剑指offer


1.模拟实现 strstr

char* MyStrstr( const 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

char* MyStrcpy(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);  

:

#define MIN(A,B) ((A) <= (B) ? (A) : (B))   
注意:

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.为什么标准头文件都有类似以下的结构?

#ifndef __INCvxWorksh
#define __INCvxWorksh 
#ifdef __cplusplus
extern "C" 
#endif 
/*...*/ 
#ifdef __cplusplus

#endif 
#endif /* __INCvxWorksh */
头文件中的编译宏   
#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的原型为:
class String 
public: 
	String( const char* str = NULL );			// 普通构造函数 
	String(const String &other);				// 拷贝构造函数 
	~ String(void);								// 析构函数 
	String & operator =(const String &other);	// 赋值函数 

private: 
	char *m_data;								// 用于保存字符串 
;


:

// 普通构造函数
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
记忆方法: 地址的增长顺序与值的增长顺序相同

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的值(假定结果不会超过长整型变量的范围)

long Sum( long n ) 
 return ( (long)1 + n ) * n / 2;


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

:

#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

char* MyStrncpy( char* 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 所指的内存区域不能重叠,



9.不创建临时变量,交换两个数

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.数组除一个数字出现一次外别的数字都出现了两次,求这个数字

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.数组中有两个数字只出现了一次,其余数字都出现了两次,找出这个数字

void FindNumbers( int* arr, int len, int* 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元,最多喝几瓶

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.获取一个数二进制序列的奇数位和偶数位

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.输入一个非负整数,返回组成它的数字之和

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.

// 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 IsRotate( const 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 ReverseBit( unsigned 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.仿函数

template <typename T>
class MaxNumber
public:
	MaxNumber( )
		: _a( a )
		, _b( b )
	

	T 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. 写一个函数来判断一个字符串是否是对称的
bool Fun( const 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.二分查找的算法

int BinarySearch( int* arr, int len, int 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.快速排序

template <typename T>
void QuickSort( vector<T>& a )
	QuickSort( a, 0, a.size( )-1 );


template <typename T>
const T& Median3( vector<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 QuickSort( vector<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 );
http://blog.sina.com.cn/s/blog_4dff8712010136jh.html

20.插入排序

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

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

template <typename T>
void InsertSort( vector<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.冒泡、选择

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.堆排序

// 根据数组构建大堆
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个独立的子数组进行一次插入排序。

template <typename T>
void ShellSort( vector<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相同的部分。

template <typename T>
void MergeSort( vector<T>& a )
	vector<T> tmpArr( a.size( ) );

	MergeSort( a, tmpArr, 0, a.size( ) - 1 );


template <typename T>
void MergeSort( vector<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 Merge( vector<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

int MyStrlen( const char* str )  
	assert( NULL != str ); 
	
	int len = 0;
	while ('\\0' != *str++)
		len++;
	
	return len;  
  

不用变量版本:

int MyStrlen( const 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

void* MyMemcpy( void* 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;  
  
void* MyMemmove( void* 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;  
  

void* MyMemset( void* 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.从键盘输入一个十进制正整数,将其转换成八进制后输出

#include <stdlib.h>
#include <stdio.h>


int main( void )
	int number = 123456;
	char string[25];
	
	itoa( number, string, 8 );
	printf( "string = %s\\n", string );
	
	return 0;



#include <stdio.h>
#include <math.h>


int dtoe( int a ) 
	if (a < 8) 
        return a;
    else 
        return ( dtoe(a / 8) * 10 + a % 8 ); 

	return 0;
 


int main( void ) 
    int tmp;

    scanf( "%d", &tmp ); 
    tmp = dtoe( tmp );
    printf( "

以上是关于面试-;代码的主要内容,如果未能解决你的问题,请参考以下文章

剑指Offer面试题10- II. 青蛙跳台阶问题

剑指Offer-循环和递归面试题10.2:青蛙跳台阶

#yyds干货盘点# 面试必刷TOP101:跳台阶

LeetCode面试题10-2. 青蛙跳台阶问题

剑指OFFER----面试题10- II. 青蛙跳台阶问题

LeetCode-面试题10.2-青蛙跳台阶问题