[关闭]
@qiezhian 2014-06-27T11:26:51.000000Z 字数 9121 阅读 1455

剑指offer——数据结构基础

算法


大多数面试题都是围绕数组、字符串、链表、树、栈、队列等几种最常见的数据结构展开的。因为高级数据结构实现复杂,很难在段时间内完全写出代码,而这些简单的数据结构是基础却可以解决很多复杂的问题,所以面试官更愿意考察这些基础结构。

数组

二维数组查找:在一个二维数组中,每一行按从左至右的递增顺序排序,每一列都按从上到下顺序递增排列。完成一个函数,输入这样一个二维数组和一个整数,判断数组中是否含有该整数。
容易想到二分查找。但是从矩阵(二维数组)的右下角开始取点的话,需要分3种情况来分析查找的过程。这一题的创新思维在于要从矩阵边上取点,则每次只需分2种情况进行查找,也就实现了二分查找。

  1. #include <iostream>
  2. using namespace std;
  3. int FindinMatrix( int* m, int rows, int cols, int element );
  4. int main()
  5. {
  6. int m[]=
  7. {
  8. 1,2,3,4,
  9. 2,3,4,5,
  10. 3,4,5,6,
  11. 4,5,6,7,
  12. };
  13. int element = 8;
  14. if( FindinMatrix( m ,4,4, element ) )
  15. cout<< " search success, element " << element << " is in matrix !"<<endl;
  16. else
  17. cout<< " search failed, element " << element<< " is not in matrix !"<<endl;
  18. return 0;
  19. }
  20. int FindinMatrix( int* matrix, int rows, int cols, int element )
  21. {
  22. int col, row;
  23. row = 0;
  24. col = cols-1;
  25. while( row <= rows-1 && col >= 0 )
  26. {
  27. if( matrix[row*cols+col] == element )
  28. return 1;
  29. else if( matrix[row*cols+col] >= element )
  30. col--;
  31. else
  32. row++;
  33. }
  34. return 0;
  35. }

字符串

字符串替换:实现一个函数,把字符串中的每个空格替换成"%20"。
将一个空格字符替换为"%20",会使得字符串变长,会覆盖后面的字符。因此需要从后往前替换,首先需要计算空格个数,以正确的计算替换后字符串长度。

  1. /*
  2. Filename: ReplaceString.c
  3. Function: replace the space ' ' with '%20' in string
  4. Details:
  5. */
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. void ReplaceString( char *str, int length );
  10. int main()
  11. {
  12. char str[30] = "I'm so happy!";
  13. printf( "original string:%s\n ", str );
  14. ReplaceString( str, 30 );
  15. printf( "replaced string:%s\n", str );
  16. return 0;
  17. }
  18. /* length 是str拥有的实际内存大小,单位字节 */
  19. void ReplaceString( char *str, int length )
  20. {
  21. int original_length = strlen( str );
  22. int new_length;
  23. int i=0;
  24. int j;
  25. int space_num=0;
  26. while( str[i] != '\0' )
  27. if( str[i++]== ' ' )
  28. space_num++;
  29. new_length = 2*space_num + original_length;
  30. if( new_length >= length )
  31. {
  32. printf("string's memory not enough!");
  33. return;
  34. }
  35. i = original_length;
  36. j = new_length;
  37. while( i>=0 && j>=0 )
  38. if( str[i]==' ' )
  39. {
  40. str[j--] = '0';
  41. str[j--] = '2';
  42. str[j--] = '%';
  43. i--;
  44. }
  45. else
  46. {
  47. str[j--] = str[i--];
  48. }
  49. }

链表

从尾到头打印链表:反序输出是典型的“后进先出”,容易想到递归和栈结构。用栈结构需要多Θ(N)的空间,正序遍历链表时将节点顺序压入栈中,然后从栈中依次弹出节点并打印,即为反序。用递归方法则是在打印本节点之前先打印下一个节点,代码简单清晰,但是对于链表过长时,递归调用时间过长且易因递归调用过多产生栈溢出。下面代码中stack结构定义stack.h在其他文中有完整实现。

  1. /*
  2. Filename: PrintListReversingly.cpp
  3. Function: print list reversingly
  4. Details:
  5. */
  6. #include <iostream>
  7. #include "stack.hpp"
  8. struct ListNode
  9. {
  10. int mElement;
  11. ListNode *pNext;
  12. };
  13. ListNode* BuildList( ListNode *list );
  14. void PrintListNormally( ListNode *list );
  15. void PrintListReversingly( ListNode *list );
  16. void PrintListReversingly2( ListNode *list );
  17. int main( )
  18. {
  19. int i=0;
  20. ListNode *list = NULL;
  21. list = BuildList( list );
  22. if( list==NULL )
  23. return 0;
  24. PrintListNormally( list );
  25. PrintListReversingly( list );
  26. PrintListReversingly2( list );
  27. }
  28. ListNode* BuildList( ListNode *list )
  29. {
  30. if( list==NULL )
  31. list = new ListNode;
  32. int i=0;
  33. ListNode *head, *tmp;
  34. head = list;
  35. for( i=1; i<25; i++)
  36. {
  37. tmp = new ListNode;
  38. if( tmp == NULL )
  39. return NULL;
  40. tmp->mElement = i;
  41. head->pNext = tmp;
  42. head = head->pNext;
  43. }
  44. return list;
  45. }
  46. void PrintListNormally( ListNode *list )
  47. {
  48. cout<<"print list normally"<<endl;
  49. ListNode *p = list;
  50. while( p!= NULL )
  51. {
  52. cout<< p->mElement <<endl;
  53. p = p->pNext;
  54. }
  55. }
  56. void PrintListReversingly( ListNode *list )
  57. {
  58. cout<<"Print List Reversingly Iteratively"<<endl;
  59. if( list->pNext!=NULL )
  60. PrintListReversingly( list->pNext );
  61. cout<< list->mElement <<endl;
  62. }
  63. void PrintListReversingly2( ListNode *list )
  64. {
  65. cout<<"Print List Reversingly using struct stack"<<endl;
  66. MyStack<ListNode*> stack;
  67. ListNode *p = list;
  68. while( p!=NULL )
  69. {
  70. stack.Push( p );
  71. p = p->pNext;
  72. }
  73. while( !stack.IsEmpty() )
  74. cout<< stack.Pop()->mElement <<endl;
  75. }

树的操作涉及大量的指针,因此与树有关的面试题都不太容易,而树中常考的莫过于二叉树,这是重中之重。
重建二叉树:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中不含重复数字。
在二叉树前序遍历中,第一个数字总是树的根节点的值。但在中序遍历中,根节点的值正好处于序列的中间,其左边为左子树的值集合,其右边为右子树的值集合。因此重建二叉树步骤为:先重建根节点,再重建根节点的左儿子,再重建根节点的右儿子。程序为递归实现。

  1. /*
  2. Filename: ReconstructBinTree.cpp
  3. Function: 给出某二叉树的前序及中序遍历结果,重建该二叉树
  4. Details:
  5. */
  6. #include <iostream>
  7. using namespace std;
  8. struct BinTreeNode
  9. {
  10. int m_Element;
  11. BinTreeNode* m_pLeft;
  12. BinTreeNode* m_pRight;
  13. };
  14. void PrintTreePreOrderly( BinTreeNode* root );
  15. void PrintTreeMidOrderly( BinTreeNode* root );
  16. BinTreeNode* Reconstruct( int* pre, int* mid, int length );
  17. BinTreeNode* ReconstructCore( int* pre, int* mid, int pre_start,
  18. int pre_end, int mid_start, int mid_end);
  19. int FindIndex( int *a, int start, int length, int element );
  20. int main( )
  21. {
  22. int Pre[]={1,2,4,7,3,5,6,8};
  23. int Mid[]={4,7,2,1,5,3,8,6};
  24. int size = sizeof( Pre )/sizeof( int );
  25. int i;
  26. cout<<"Tree preorderly print: " << endl;
  27. for( i=0; i<size; i++ )
  28. cout<< Pre[i];
  29. cout<<endl;
  30. cout<<"Tree midorderly print: " << endl;
  31. for( i=0; i<size; i++ )
  32. cout<< Mid[i];
  33. cout<<endl;
  34. BinTreeNode* root = Reconstruct( Pre, Mid, size );
  35. if( root==NULL )
  36. cout<<" Reconstruct failed!"<< endl;
  37. cout<<"Reconstructed tree preorderly print: " << endl;
  38. PrintTreePreOrderly( root );
  39. cout<<" Reconstructed tree midorderly print: " << endl;
  40. PrintTreeMidOrderly( root );
  41. return 0;
  42. }
  43. BinTreeNode* Reconstruct( int* pre, int* mid, int length )
  44. {
  45. return ReconstructCore( pre, mid, 0, length-1, 0, length-1 );
  46. }
  47. BinTreeNode* ReconstructCore( int* pre, int* mid, int pre_start,
  48. int pre_end, int mid_start, int mid_end)
  49. {
  50. if( pre_end - pre_start != mid_end - mid_start
  51. || pre_end < pre_start || mid_end < mid_start )
  52. return NULL;
  53. BinTreeNode* node = NULL ;
  54. node = new BinTreeNode;
  55. node->m_Element = pre[pre_start];
  56. if( pre_end > pre_start )
  57. {
  58. int index = FindIndex( mid , mid_start,
  59. mid_end-mid_start+1, pre[pre_start] );
  60. if( index!= -1 )
  61. {
  62. node->m_pLeft = ReconstructCore( pre,
  63. mid,pre_start+1,pre_start+index,
  64. mid_start, mid_start+index-1 );
  65. node->m_pRight = ReconstructCore( pre,
  66. mid, pre_start+index+1, pre_end,
  67. mid_start+index+1, mid_end );
  68. }
  69. }
  70. return node;
  71. }
  72. int FindIndex( int* a, int start, int length, int element )
  73. {
  74. if( a==NULL || length<=0 )
  75. return -1;
  76. int i=start;
  77. for( ; i<start+length; i++)
  78. if( element == a[i] )
  79. return ( i-start );
  80. return -1;
  81. }
  82. void PrintTreePreOrderly( BinTreeNode *root )
  83. {
  84. if( !root )
  85. return;
  86. cout<< root->m_Element << endl;
  87. PrintTreePreOrderly( root->m_pLeft );
  88. PrintTreePreOrderly( root->m_pRight );
  89. }
  90. void PrintTreeMidOrderly( BinTreeNode *root )
  91. {
  92. if( !root )
  93. return;
  94. PrintTreeMidOrderly( root->m_pLeft );
  95. cout<< root->m_Element << endl;
  96. PrintTreeMidOrderly( root->m_pRight );
  97. }

用两个栈实现队列

队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。
栈的特点是“先进后出”,在计算机领域被广泛应用,而队列是“先进先出”,二者针锋相对,但却互相联系。

  1. /*
  2. Filename: CQueue.hpp
  3. Function: implement simple version of struct "queue" using two "stack"s.
  4. Details: please check IsEmpty() function before call deleteHead() function
  5. */
  6. #include <iostream>
  7. #include "stack.hpp"
  8. using namespace std;
  9. template<typename T>
  10. class CQueue
  11. {
  12. public:
  13. CQueue();
  14. ~CQueue();
  15. void appendTail( const T& node );
  16. T deleteHead( );
  17. bool IsEmpty( );
  18. private:
  19. MyStack<T> stack_in;
  20. MyStack<T> stack_out;
  21. };
  22. template<typename T>
  23. CQueue<T>::CQueue( )
  24. {
  25. }
  26. template<typename T>
  27. CQueue<T>::~CQueue( )
  28. {
  29. }
  30. template<typename T>
  31. bool CQueue<T>::IsEmpty( )
  32. {
  33. return ( stack_in.IsEmpty() && stack_out.IsEmpty() );
  34. }
  35. template<typename T>
  36. void CQueue<T>::appendTail( const T& node )
  37. {
  38. stack_in.Push( node );
  39. }
  40. template<typename T>
  41. T CQueue<T>::deleteHead( )
  42. {
  43. if( stack_out.IsEmpty() )
  44. while( !stack_in.IsEmpty() )
  45. stack_out.Push( stack_in.Pop() );
  46. return stack_out.Pop();
  47. }

包含min函数的栈

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素min函数。在该栈中,调用min、push、pop等函数的时间复杂度均为Θ(1)
分析: 光用一个成员变量来记录最小元素是不够的,当最小元素弹出时,下一个最小元素是多少?要找出其最小元素就不可能在Θ(1)时间办到。只能用空间换取时间效率。添加一个辅助栈,每当push一个元素,辅助栈均push此时最小元素,每pop一个元素,辅助栈也pop一个元素即可。

  1. /*
  2. Filename: StackWithMin.hpp
  3. Function: implement a version of "stack" with min() function in O(1) time
  4. Details:
  5. */
  6. #include "stack.hpp"
  7. template<typename T>
  8. class StackWithMin
  9. {
  10. public:
  11. StackWithMin();
  12. ~StackWithMin();
  13. void Push( const T& node );
  14. T Pop( );
  15. bool IsEmpty();
  16. T Min();
  17. private:
  18. MyStack<T> stack_data;
  19. MyStack<T> stack_min;
  20. };
  21. template<typename T>
  22. StackWithMin<T>::StackWithMin()
  23. {
  24. }
  25. template<typename T>
  26. StackWithMin<T>::~StackWithMin()
  27. {
  28. }
  29. template<typename T>
  30. void StackWithMin<T>::Push( const T& node )
  31. {
  32. if( stack_min.IsEmpty() )
  33. stack_min.Push( node );
  34. else
  35. {
  36. if( node > stack_min.Top() )
  37. stack_min.Push( stack_min.Top() );
  38. else
  39. stack_min.Push( node );
  40. }
  41. stack_data.Push( node );
  42. }
  43. template<typename T>
  44. T StackWithMin<T>::Pop( )
  45. {
  46. stack_min.Pop();
  47. return stack_data.Pop();
  48. }
  49. template<typename T>
  50. T StackWithMin<T>::Min()
  51. {
  52. return stack_min.Top();
  53. }
  54. template<typename T>
  55. bool StackWithMin<T>::IsEmpty()
  56. {
  57. return ( stack_data.IsEmpty() );
  58. }

栈的压入、弹出序列分析

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1、2、3、4、5是某栈的压栈序列,序列4、5、3、2、1是一个合法的弹出序列,但是序列4、3、5、1、2就不可能是该栈的弹出序列。
分析: 比较直观的思路是用栈来模拟压入弹出过程。假设压入序列为I,弹出序列为O,i和j分别是I和O的索引。则按一下三步走:
1.如果I[i]==O[j],则说明此元素入栈后直接又出栈,直接略过,i++;j++;
2.否则将O[j]和栈顶元素比较,相等说明此元素此时需弹出,则j++且栈弹出一个元素;不相等则栈压入I[i++];
3.最后判断栈是否为空,不为空则说明该弹出序列不合法。

  1. #include <iostream>
  2. #include "stack.hpp"
  3. using namespace std;
  4. bool IsPopOrder( int* push_order, int* pop_order, int length );
  5. int main( )
  6. {
  7. int push_order[]={ 2,4,6,8,10,9,7,5,3,1 };
  8. int pop_order[]={ 6,4,2,9,10,8,3,7,1,5 };
  9. int size = sizeof( push_order )/sizeof( int );
  10. if( IsPopOrder( push_order, pop_order, size ) )
  11. cout<< "pop order is right!"<<endl;
  12. else
  13. cout<< "pop order is wrong!"<<endl;
  14. return 0;
  15. }
  16. bool IsPopOrder( int* push_order, int* pop_order, int length )
  17. {
  18. if( push_order==NULL || pop_order==NULL || length<=0 )
  19. return false;
  20. MyStack<int> mystack;
  21. int i=0, j=0;
  22. while( i<length && j<length )
  23. {
  24. if( push_order[i] == pop_order[j] )
  25. {
  26. i++; j++;
  27. }
  28. else
  29. {
  30. if( !mystack.IsEmpty() && pop_order[j] == mystack.Top() )
  31. {
  32. j++;
  33. mystack.Pop();
  34. }
  35. else
  36. {
  37. mystack.Push( push_order[i++] );
  38. }
  39. }
  40. }
  41. while( !mystack.IsEmpty() && mystack.Top() == pop_order[j] )
  42. {
  43. mystack.Pop(); j++;
  44. }
  45. return ( mystack.IsEmpty() );
  46. }

队列

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

题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到又的顺序打印。
分析:这是树的一种遍历方法,称为层序遍历。本题看似考查树的知识,实际上用的却是优先队列结构。方法如下:从队列头部取出最早进入队列的结点(根结点),当每一次打印一个结点的时候,如果该结点有子结点,则把子结点放入队列的末尾,并弹出打印过的结点,再取下一个结点进行打印。重复前面的动作,直至队列中所有的结点都被打印出来为止。

  1. template<typename T>
  2. void PrintTreeHierarchically( BinTreeNode<T>* root )
  3. {
  4. if( root==NULL )
  5. return;
  6. CQueue<BinTreeNode<T>*> queue;
  7. BinTreeNode<T>* pTree = root;
  8. queue.Push( pTree );
  9. while( !queue.IsEmpty() )
  10. {
  11. pTree = queue.Pop();
  12. cout<< pTree->m_nElement <<endl;
  13. if( pTree->m_pLeft != NULL )
  14. queue.Push( pTree->m_pLeft );
  15. if( pTree->m_pRight != NULL )
  16. queue.Push( pTree->m_pRight );
  17. }
  18. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注