ITPub博客

首页 > IT职业 > IT职场 > 面试->代码 剑指offer(一)

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

IT职场 作者:shinez 时间:2021-09-09 09:12:27 0 删除 编辑

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


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 = 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        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]            index2 = indexMid;  
   }  
}  
 
int MinInOrder( int* arr, int index1, int index2 ){  
   int result = arr[index1];  
 
   for (int i = index1 + 1; i        if (arr[i]            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        return result[n];  
 
   long long fibOne = 0;  
   long long fibTwo = 1;  
   long long fibN = 0;  
 
   // 注意这里是 i    for (unsigned int i = 2; 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        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        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    }  
 
   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        while (pBegin            ++pBegin;  
 
       while (pBegin            --pEnd;  
 
       if (pBegin            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 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.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 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        g_inputInvalid = true;  
 
   return g_inputInvalid;  
}  
 
 
bool CheckMoreThanHalf( int* arr, int len, int number ){  
   int times = 0;  
 
   for (int i = 0; i        if (number == arr[i])  
           ++times;  
 
   bool isMoreThanHalf = true;  
   if (times * 2        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        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 == 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        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            *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        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 ('

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/3127/viewspace-2800538/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-08-30

  • 博文量
    0
  • 访问量
    933