继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

千岁不倒翁
关注TA
已关注
手记 362
粉丝 60
获赞 387

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


1赋值运算符函数


注意:


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


2. 传入参数常量引用


3. 释放自身已有空间


4. 判断是不是自身赋值


例:

[cpp] view plain copy
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. 创建临时实例,交换临时实例和原来的实例:

[cpp] view plain copy
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 查找范围为空


例:

[cpp] view plain copy
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} 这种 两个指针指向的数字及它们中间数字三者相同的时候,无法判断中间数字位于哪个子数组中,也就无法移动指针来缩小查找的范围,此时 --> 顺序查找:


[cpp] view plain copy
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. 循环:


[cpp] view plain copy
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) 

或者,我们多写几种情况,也可以发现规律,方法数为 前一次方法数 + 后一次方法数:


[cpp] view plain copy
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 );    
}


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)(接着用两个小矩形横着覆盖)

[cpp] view plain copy
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 就可以进行多少次这样的操作。


:

[cpp] view plain copy
int NumberOf1( int n ){  
   int count = 0;  
 
   while (0 != n){  
       ++count;  
       n = (n - 1) & n;  
   }  
 
   return count;  
}


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


[cpp] view plain copy
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,一个从前往后遍历,一个从后往前遍历


[cpp] view plain copy
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. 反转链表


:

[cpp] view plain copy
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. 合并两个排序的链表

[cpp] view plain copy
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) 的栈

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


[cpp] view plain copy
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. 从上往下打印二叉树 (层序遍历二叉树)

借助队列


[cpp] view plain copy
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. 数组中出现次数超过一半的数字


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


:

[cpp] view plain copy
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) 二分


[cpp] view plain copy
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. 二叉树的深度

:


[cpp] view plain copy
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. 需要重复遍历多次的解法


[cpp] view plain copy
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 );  
}


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



[cpp] view plain copy
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. 翻转每个单词中字符的顺序


[cpp] view plain copy
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"

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


[cpp] view plain copy
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. 二叉搜索树


[cpp] view plain copy
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. 普通树,含指向父节点指针


[cpp] view plain copy
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 开始以相同速度遍历链表,两个指针相遇点即为链表中环的入口结点


:

[cpp] view plain copy
// 首先判断单链表是否带环,若带环(此时快慢指针已经相遇),从此刻起计步,直到下次两个指针再相遇,快指针步数减去慢指针步数,即为环的长度  
  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;    
          }    
      }    
  }


-未完待续

原文出处

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP