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

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

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


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


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;    
          }    
      }    
  }


-未完待续


如果你有好的原创文章想与大家分享,欢迎投稿。


征稿要求:

①稿件字数以800~1500字左右为宜,多于2000字的文章在手机上阅读起来比较麻烦,少于800字的文章看起来不过瘾;

②你有自己拍的适合做文章插图的照片也可一并附上~如果不方便,程序和小七也会帮你配图~


加程序人生编辑们的微信,备注#投稿#:


程序 微信ID:druidlost  

小七 微信ID:duoshangshuang


点击图片get往期内容

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Offer》 1. 赋值运算函数 2. 单例设计模式 3. 二维数组中查找目标值 4. 替换字符串中的空格 5. 从尾到头打印链表 6. 由前序和中序遍历重建二叉树 7. 用两个栈实现队列 8. 求旋转数组的最小数字 9. 斐波那契数列的第n项(青蛙跳台阶) 10. 二进制中1的个数 11. 数值的整数次方 12. 打印1到最大的n位数 13. O(1)时间删除链表节点 14. 使数组中的奇数位于偶数前面 15. 找链表中倒数第K个节点 16. 输出反转后的链表 17. 合并两个有序链表 18. 判断二叉树A中是否包含子树B 19. 二叉树的镜像 20. 顺时针打印矩阵 21. 包含min函数的栈 22. 判断一个栈是否是另一个栈的弹出序列 23. 层序遍历二叉树 24. 后序遍历二叉搜索树 25. 二叉树中和为某值的路径 26. 复杂链表的复制 27. 二叉搜索树转换为双向链表 28. 打印字符串中所有字符的排列 29. 数组中出现次数超过一半的数字 30. 找出最小的K个数 31. 连续子数组的最大和 32. 从1到整数n中1出现的次数 33. 把数组中的数排成一个最小的数 34. 求第N个丑数 35. 第一个出现一次的字符 36. 数组中逆序对的个数 37. 两个链表的第一个公共节点 38. 数字在排序数组中出现的次数 39. 二叉树的深度 40. 数组中只出现一次的两个数,而其他数都出现两次 41. 和为s的连续整数序列 42. 翻转字符串 43. n个骰子的点数及出现的概率 44. 扑克牌的顺子 45. 圆圈中最后剩下的数 46. 1+2+3+...+n的和 47. 不用加减乘除做加法 48. 不能被继承的类 49. 字符串转换为整数 50. 树中两个节点的最低公共祖先 51. 找出重复的数 52. 构建乘积数组 53. 正则表达式匹配 54. 表示数值的字符串 55. 字符流中第一个不重复的字符 56. 链表中环的入口节点 57. 删除链表中重复的节点 58. 二叉树的下一个节点 59. 对称的二叉树 60. 按之字形顺序打印二叉树 61. 把二叉树打印成多行 62. 序列化二叉树 63. 二叉搜索树的第K个节点 64. 数据流中的中位数 65. 滑动窗口的最大值 66. 矩阵中的路径 67. 机器人的运动范围
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值