[读]剑指offer
研二的开始找工作了,首先祝愿他们都能够找到自己满意的工作。看着他们的身影,自问明年自己这个时候是否可以从容面对呢?心虚不已,赶紧从老严那儿讨来一本《剑指offer》。在此顺便将自己做题所想,发现的一些小技巧记录于此,就当是学习笔记。先上一些,没做完的后面有时间继续补上。
2013.09.04于行政北楼
一.数组
题目1:二维数组的查找
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成这样一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
例如下面的二维数组就是每行、每列都递增排序。如果在这个数组中查找数字7,则返回true;如果查找数字5,由于数组不含有该数字,则返回false;
1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15
思路:这题的关键点是用好“每一行从左到要递增,每一列从上到下递增”这个条件,解题时从数组右上角9出发,若它等于查找的数,那么返回结束;若它比查找数小,那么向左移动到8,继续查找;若它比查找数大,那么向下移动到12。比如说我们要寻找7,一次经过的数是9-8-2-4-7,返回。
1 #include<iostream> 2 using namespace std; 3 4 const int row=4; 5 const int column=4; 6 7 bool Find(int array[][4],int search_number) 8 { 9 bool result=false; 10 int i=0; 11 int j=column-1; 12 int temp;//=array[i][j]; 13 14 while(i<=row&&j>=0) 15 { 16 temp=array[i][j]; 17 if(search_number==temp) 18 { 19 result=true; 20 break; 21 } 22 else if(search_number>temp) 23 i++; 24 else 25 j--; 26 } 27 return result; 28 } 29 30 int main() 31 { 32 int array[][4]={ {1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15} }; 33 int search_number; 34 for(search_number=1;search_number<16;search_number++) 35 { 36 if(Find(array,search_number)) 37 cout<<"查找"<<search_number<<":Yes"<<endl; 38 else 39 cout<<"查找"<<search_number<<":No"<<endl; 40 } 41 return 0; 42 }
1 #include<iostream> 2 using namespace std; 3 4 const int row=4; 5 const int column=4; 6 7 bool Find(int array[][4],int search_number) 8 { 9 bool result=false; 10 int i=0; 11 int j=column-1; 12 int temp;//=array[i][j]; 13 14 while(i<=row&&j>=0) 15 { 16 temp=array[i][j]; 17 if(search_number==temp) 18 { 19 result=true; 20 break; 21 } 22 else if(search_number>temp) 23 i++; 24 else 25 j--; 26 } 27 return result; 28 } 29 30 int main() 31 { 32 int array[][4]={ {1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15} }; 33 int search_number; 34 for(search_number=1;search_number<16;search_number++) 35 { 36 if(Find(array,search_number)) 37 cout<<"查找"<<search_number<<":Yes"<<endl; 38 else 39 cout<<"查找"<<search_number<<":No"<<endl; 40 } 41 return 0; 42 }
题目2:数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4.
思路:这一题很容易想到的方法是从头至尾遍历一遍数组,count++,非常简单,时间复杂度O(n);但是既然数组是排序的,那么就有优化的空间,我们自然想到“二分搜索法”,先找到第一个3,然手是最后一个3,最后求出3出现了几次,这样的时间复杂度就为O(logn)。
1 #include<iostream> 2 using namespace std; 3 4 /*二分搜索 5 int BSearch(int a[],int left,int right,int search_number) 6 { 7 if(left<=right) 8 { 9 int middle=(left+right)/2; 10 if(a[middle]<search_number) BSearch(a,middle+1,right,search_number); 11 else if(a[middle]>search_number) BSearch(a,left,middle-1,search_number); 12 else return middle; 13 } 14 return -1;//搜索失败 15 } 16 */ 17 18 int BSearch_first(int a[],int left,int right,int search_number) 19 { 20 if(left<=right) 21 { 22 int middle=(left+right)/2; 23 if(a[middle]<search_number) return BSearch_first(a,middle+1,right,search_number);//return 一定要写,否则一定返回-1; 24 //本人纠结了好久才发现这个错误,泪。。 25 else if(a[middle]==search_number) 26 { 27 if(a[middle-1]==search_number) return BSearch_first(a,left,middle-1,search_number); 28 else return middle; 29 } 30 else return BSearch_first(a,left,middle-1,search_number); 31 } 32 return -1; 33 } 34 35 int BSearch_last(int a[],int left,int right,int search_number) 36 { 37 if(left<=right) 38 { 39 int middle=(left+right)/2; 40 if(a[middle]>search_number) return BSearch_last(a,left,middle-1,search_number); 41 else if(a[middle]==search_number) 42 { 43 if(a[middle+1]==search_number) return BSearch_last(a,middle+1,right,search_number); 44 else return middle; 45 } 46 else return BSearch_last(a,middle+1,right,search_number); 47 } 48 return -1; 49 } 50 51 /***************test 52 a[]={1,2,3,3,3,3,4,5} 53 a[]={3,3,3,3,4,5}; 54 a[]={1,2,3,3,3,3}; 55 a[]={1,3,3,3,3,4}; 56 a[]={3,3,3,3},3; 57 a[]={3,3,3,3},2; 58 ********************/ 59 int main() 60 { 61 int a[8]={3,3,3,3}; 62 int first=BSearch_first(a,0,3,4);//first:第一个3的位置 63 int last=BSearch_last(a,0,3,4);//last:最后一个3的位置 64 int count=0; 65 if(first!=-1&&last!=-1) 66 count=last-first+1; 67 cout<<first<<" "<<last<<" "<<count<<endl; 68 return 0; 69 }
1 #include<iostream> 2 using namespace std; 3 4 /*二分搜索 5 int BSearch(int a[],int left,int right,int search_number) 6 { 7 if(left<=right) 8 { 9 int middle=(left+right)/2; 10 if(a[middle]<search_number) BSearch(a,middle+1,right,search_number); 11 else if(a[middle]>search_number) BSearch(a,left,middle-1,search_number); 12 else return middle; 13 } 14 return -1;//搜索失败 15 } 16 */ 17 18 int BSearch_first(int a[],int left,int right,int search_number) 19 { 20 if(left<=right) 21 { 22 int middle=(left+right)/2; 23 if(a[middle]<search_number) return BSearch_first(a,middle+1,right,search_number);//return 一定要写,否则一定返回-1; 24 //本人纠结了好久才发现这个错误,泪。。 25 else if(a[middle]==search_number) 26 { 27 if(a[middle-1]==search_number) return BSearch_first(a,left,middle-1,search_number); 28 else return middle; 29 } 30 else return BSearch_first(a,left,middle-1,search_number); 31 } 32 return -1; 33 } 34 35 int BSearch_last(int a[],int left,int right,int search_number) 36 { 37 if(left<=right) 38 { 39 int middle=(left+right)/2; 40 if(a[middle]>search_number) return BSearch_last(a,left,middle-1,search_number); 41 else if(a[middle]==search_number) 42 { 43 if(a[middle+1]==search_number) return BSearch_last(a,middle+1,right,search_number); 44 else return middle; 45 } 46 else return BSearch_last(a,middle+1,right,search_number); 47 } 48 return -1; 49 } 50 51 /***************test 52 a[]={1,2,3,3,3,3,4,5} 53 a[]={3,3,3,3,4,5}; 54 a[]={1,2,3,3,3,3}; 55 a[]={1,3,3,3,3,4}; 56 a[]={3,3,3,3},3; 57 a[]={3,3,3,3},2; 58 ********************/ 59 int main() 60 { 61 int a[8]={3,3,3,3}; 62 int first=BSearch_first(a,0,3,4);//first:第一个3的位置 63 int last=BSearch_last(a,0,3,4);//last:最后一个3的位置 64 int count=0; 65 if(first!=-1&&last!=-1) 66 count=last-first+1; 67 cout<<first<<" "<<last<<" "<<count<<endl; 68 return 0; 69 }
题目3(*):数组中只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。如{2,4,3,6,3,2,5,5,},输出为4和6。请写出程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路:初见这一题应该很容易联想到一个数组里面只有一个数字不同的时候应该怎么找出来。http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1057,方法就是异或运算。如还是对所有数异或,那么结果肯定是两个只出现一次的数字异或的结果(成对出现的异或抵消了),怎么通过这个结果找到两个只出现一次的数字呢?《剑指offer》给出了思路:如果能把原数组分成两个子数组,使得每个子数组包含一个只出现的数字,这样我们就能分别找出两个只出现一次的数字了。怎么分呢?读者可以自己先行思考。
1 #include<iostream> 2 using namespace std; 3 4 #define length 9 5 6 void find(int a[]) 7 { 8 int i; 9 int t=0;//t记录两个单独数字异或值 10 for(i=0;i<length;i++) 11 t^=a[i]; 12 int index=0;//index记录t最低位为1的位置 13 int x=t; 14 while((x&1)==0) 15 { 16 x=x>>1; 17 ++index; 18 } 19 int m=0;//记录index位为1的数字 20 int n=0;//记录index位为0的数字 21 for(i=0;i<length;i++) 22 { 23 int data=a[i]>>index; 24 if(data&1)//若为1,则与m最后值为1组 25 { 26 m^=a[i]; 27 } 28 else//若为0,则与n最后值为1组 29 { 30 n^=a[i]; 31 } 32 } 33 cout<<m<<" "<<n<<endl; 34 } 35 36 int main() 37 { 38 int a[length]={2,4,3,6,3,2,5,5}; 39 find(a); 40 return 0; 41 }
题目4:查找和排序
把一个数组最开始的若干元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
1 1 //二分搜索,复杂度O(logn) 2 2 #include<iostream> 3 3 using namespace std; 4 4 5 5 #define length 5 6 6 7 7 int minnum(int x,int y) 8 8 { 9 9 return x<y?x:y; 10 10 } 11 11 12 12 void minofarray(int a[],int n) 13 13 { 14 14 int i,j; 15 15 i=0; 16 16 j=n-1; 17 17 int min=(i+j)/2; 18 18 19 19 while((j-i)>1) 20 20 { 21 21 if(a[min]<a[j]) 22 22 { 23 23 j=min; 24 24 min=(i+min)/2; 25 25 } 26 26 else 27 27 { 28 28 i=min; 29 29 min=(min+j)/2; 30 30 } 31 31 } 32 32 cout<<minnum(a[i],a[j])<<endl; 33 33 } 34 34 35 35 int main() 36 36 { 37 37 int a[length]={3,4,5,1,2}; 38 38 minofarray(a,length); 39 39 return 0; 40 40 }
题目5:调整数组顺序使奇数位于偶数前面
应该很快联想到快速排序的思路,于是:
1 void ReOrder(int a[],int length) 2 { 3 int i=-1; 4 int j=length; 5 do{ 6 do i++;while(a[i]&1); 7 do j--;while(!(a[j]&1)); 8 if(i<j) swap(a[i],a[j]); 9 }while(i<j); 10 for(i=0;i<length;i++) 11 cout<<a[i]<<" "; 12 cout<<endl; 13 }
但是,《剑指offer》指出:若把数组中的数按正负数分为两部分,再写一遍代码?若把数组中按能不能被3整除分为两部分,再写一遍代码?
所以,我们不是要解决一个问题,而是解决一类问题的通用方法。这就是面试官在考察我们对扩展性的理解,寄希望我们能够给出一个模式,在这个模式下能够很方便地把已有的解决方案扩展到同类型的问题上去。
这里,我们只要用一个判断函数来表示while语句的条件就可以了。
题目6:顺时针打印数组
没什么好说的,有兴趣的可以看着两题:
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1094
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1058
题目7:数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,一次输出2。
方法1:遍历数组,count数组用来记录每个数字出现的次数,遍历count数组,若count[i]>length/2,输入对应数字。时间复杂度都为O(n2),空间复杂度O(n),这是最易想到的方法。
方法2:方法1复杂度为O(n2),原因是又遍历了一遍count数组,因为不知道新的数字是否在count数组中出现过。既然这样,先排序,无需再遍历,中间的数即是,时间复杂度为O(nlogn)。
“最直观的算法通常不是面试官满意的算法”。
方法3:第k大数
方法4:遍历数组的时候保存两个值,一个是数组中的数字,一个是次数。当遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则数字加1,若不同则减1.若次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后把次数设为1时对应的数字。即不同数两两相消,剩下的数为出现次数最多的。
题目8:最小的k个数
输入n个整数,找出其中最小的k个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
方法1:排序,前k个数。时间复杂度O(nlogn)。
方法2:因为题目要求最小k个,并不要求排好顺序,所以取k个数,记录最大的数max,继续遍历,若大于max,则替换,时间复杂度O(n)。
上述方法2,若不改变原始数组,需要额外O(k)的辅助空间。
看了书之后,发现自己方法2出现了错误,因为对于替换后的新数组,需要重新求得其最小值,这里可以用最小堆或者红黑树求得,那么复杂度应该是O(nlogk)。
题目9:连续子数组的最大和
输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。
典型的动态规划题,但是时间复杂度是O(n2),显然不符合题目要求,这里code顺便复习一下:
因为只有负数会减小结果,记录每次遇到负数时的sum,若为负数则抛弃,sum置零,继续相加,若下一次的sum大于max则替换,只需遍历一次数组,时间复杂度为O(n)。
题目10:把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这3个数字能排成的最小数字321323。
题目11:数组中的逆序对
在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
方法1:从第一个数开始,遍历后面每个数跟当前数比较,如果符合条件就加1,时间复杂度O(n2),显然不行。
方法2:归并排序
二.字符串
题目1:替换空格
请事先一个函数,把字符串中的每个空格替换成“20%"。例如输入”we are happy.“,则输出”we%20are%20happy.“。
思路:我们可以先遍历一次字符串,这样就能统计出字符串中空格的总数,并可以由此计算出替换之后的字符串的总长度。没替换一个空格,长度增加2。从字符串的后面开始复制和替换。首先准备两个指针,p1和p2。p1指向原始字符串的末尾,p2指向替换后的字符串的末尾。解析来向前移动p1,逐个复制到p2指向的位置,知道碰到空格位置。碰到空格后,p1向前移动1格,在p2之前插入字符串“%20”,同时p2向前移动3格。
上面算法的时间复杂度为O(n),下面是代码:
1 #include<iostream> 2 using namespace std; 3 4 const int length=100; 5 6 void ReplaceBlank(char str[]) 7 { 8 int oldlength=strlen(str); 9 int numofblank=0; 10 int i,j; 11 12 for(i=0;i<oldlength;i++) 13 { 14 if(str[i]==' ') 15 numofblank++; 16 } 17 int newlength=oldlength+numofblank*2; 18 j=newlength; 19 for(i=oldlength;i>=0;i--) 20 { 21 if(str[i]!=' ') 22 { 23 str[j]=str[i]; 24 j--; 25 } 26 else 27 { 28 str[j--]='0'; 29 str[j--]='2'; 30 str[j--]='%'; 31 } 32 } 33 } 34 35 int main() 36 { 37 char str[length]; 38 strcpy(str," we are happy. "); 39 ReplaceBlank(str); 40 cout<<str<<endl; 41 return 0; 42 }
1 #include<iostream> 2 using namespace std; 3 4 const int length=100; 5 6 void ReplaceBlank(char str[]) 7 { 8 int oldlength=strlen(str); 9 int numofblank=0; 10 int i,j; 11 12 for(i=0;i<oldlength;i++) 13 { 14 if(str[i]==' ') 15 numofblank++; 16 } 17 int newlength=oldlength+numofblank*2; 18 j=newlength; 19 for(i=oldlength;i>=0;i--) 20 { 21 if(str[i]!=' ') 22 { 23 str[j]=str[i]; 24 j--; 25 } 26 else 27 { 28 str[j--]='0'; 29 str[j--]='2'; 30 str[j--]='%'; 31 } 32 } 33 } 34 35 int main() 36 { 37 char str[length]; 38 strcpy(str," we are happy. "); 39 ReplaceBlank(str); 40 cout<<str<<endl; 41 return 0; 42 }
附:有两个排序数组A1和A2,内存在A1的末尾有足够的空余空间容纳A2。请实现一个函数,把A2的所有数字插入到A1中并且所有的数字是排序的。
思考:如果从前往后复制每个数字需要移动数字多次,那么我们可以从后往前复制,这样就能该减少移动的次数,从而提高效率。
1 #include<iostream> 2 using namespace std; 3 4 #define maxsize 100 5 6 void merge(int array1[],int array2[]) 7 { 8 int length1=6; 9 int length2=7; 10 int length=length1+length2; 11 int i=length1-1; 12 int j=length2-1; 13 int k=length-1; 14 while(i>-1&&j>-1) 15 { 16 if(array1[i]>array2[j]) 17 { 18 array1[k]=array1[i]; 19 i--; 20 k--; 21 } 22 else 23 { 24 array1[k]=array2[j]; 25 j--; 26 k--; 27 } 28 } 29 if(j>-1) 30 { 31 for(k=0;k<=j;k++) 32 array1[k]=array2[k]; 33 } 34 for(i=0;i<length;i++) 35 cout<<array1[i]<<" "; 36 cout<<endl; 37 } 38 39 int main() 40 { 41 //测试用例 42 int array1[maxsize]={2,4,5,6,8,10}; 43 int array2[7]={1,3,5,7,9,11,13}; 44 merge(array1,array2); 45 return 0; 46 }
1 #include<iostream> 2 using namespace std; 3 4 #define maxsize 100 5 6 void merge(int array1[],int array2[]) 7 { 8 int length1=6; 9 int length2=7; 10 int length=length1+length2; 11 int i=length1-1; 12 int j=length2-1; 13 int k=length-1; 14 while(i>-1&&j>-1) 15 { 16 if(array1[i]>array2[j]) 17 { 18 array1[k]=array1[i]; 19 i--; 20 k--; 21 } 22 else 23 { 24 array1[k]=array2[j]; 25 j--; 26 k--; 27 } 28 } 29 if(j>-1) 30 { 31 for(k=0;k<=j;k++) 32 array1[k]=array2[k]; 33 } 34 for(i=0;i<length;i++) 35 cout<<array1[i]<<" "; 36 cout<<endl; 37 } 38 39 int main() 40 { 41 //测试用例 42 int array1[maxsize]={2,4,5,6,8,10}; 43 int array2[7]={1,3,5,7,9,11,13}; 44 merge(array1,array2); 45 return 0; 46 }
上述方法时间复杂度为O(m+n),如果你觉得还有更好的方法,或者此程序还有不足之处,欢迎跟我联系交流!
题目2:翻转单词顺序
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为了简单起见,标点符号和普通字母一样处理。例如输入字符串“I am a student.",则输出"student. a am I"。
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 5 void swap(char &a,char &b) 6 { 7 char c; 8 c=a; 9 a=b; 10 b=c; 11 } 12 13 void reverse(string &str) 14 { 15 int length=str.length(); 16 int i,j; 17 for(i=0;i<length/2;i++)//翻转整个句子 18 swap(str[i],str[length-i-1]); 19 int temp=-1; 20 for(i=0;i<=length;i++)//翻转每个单词 21 { 22 if(str[i]==' '||str[i]=='\0') 23 { 24 cout<<"1111"<<endl; 25 for(j=temp+1;j<(i+temp+1)/2;j++) 26 swap(str[j],str[i-j+temp]); 27 temp=i; 28 } 29 } 30 } 31 32 int main() 33 { 34 string str="I am a student."; 35 reverse(str); 36 cout<<str<<endl; 37 return 0; 38 }
附:字符串的左旋转操作时吧字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如输入字符串”abcdefg“和数字2,该函数将返回左旋转2位得到的结果”cdefgab“。
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 5 void swap(char &a,char &b) 6 { 7 char c; 8 c=a; 9 a=b; 10 b=c; 11 } 12 13 void reverse(string &str,int m) 14 { 15 int length=str.length(); 16 int n=length-m-1; 17 int i; 18 for(i=0;i<length/2;i++) 19 swap(str[i],str[length-i-1]); 20 for(i=0;i<n/2;i++) 21 swap(str[i],str[n-i]); 22 for(i=n+1;i<(length+n+1)/2;i++) 23 swap(str[i],str[length-i+n]); 24 } 25 26 int main() 27 { 28 string str="abcdefg"; 29 int m=2; 30 reverse(str,2); 31 cout<<str<<endl; 32 return 0; 33 }
题目3:字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出有字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
考查全排列,可以直接用STL中的next_permutation函数,下面是另外一题。
http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1103
题目4:第一个只出现一次的字符
在字符串中找出第一个只出现一次的字符。如输入“abaccdeff”,则输出‘b'。
遍历两次,第一次count数组记录每个字符出现的次数,第二次遍历输出第一个count等于1的字符。这里最好用hashtable。
三.链表
题目1:从尾到头打印链表
输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
链表的定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
1 #include <iostream> 2 using namespace std; 3 4 struct ListNode 5 { 6 int m_nKey; 7 ListNode* m_pNext; 8 }; 9 10 void PrintListReverse(ListNode* pHead) 11 { 12 if(pHead!=NULL) 13 { 14 if(pHead->m_pNext!=NULL) 15 { 16 PrintListReverse(pHead->m_pNext); 17 } 18 cout<<pHead->m_nKey<<" "; 19 } 20 } 21 22 ListNode* CreateListNode(int key) 23 { 24 ListNode* pNode=new ListNode(); 25 pNode->m_nKey=key; 26 pNode->m_pNext=NULL; 27 return pNode; 28 } 29 30 void ConnectListNodes(ListNode *pNode1,ListNode *pNode2) 31 { 32 pNode1->m_pNext=pNode2; 33 } 34 35 void DestroyList(ListNode *pHead) 36 { 37 pHead=NULL; 38 } 39 40 void Test(ListNode *pHead) 41 { 42 PrintListReverse(pHead); 43 } 44 45 void Test1() 46 { 47 cout<<"Test1 begins:"<<endl; 48 49 ListNode* pNode1 = CreateListNode(1); 50 ListNode* pNode2 = CreateListNode(2); 51 ListNode* pNode3 = CreateListNode(3); 52 ListNode* pNode4 = CreateListNode(4); 53 ListNode* pNode5 = CreateListNode(5); 54 55 ConnectListNodes(pNode1, pNode2); 56 ConnectListNodes(pNode2, pNode3); 57 ConnectListNodes(pNode3, pNode4); 58 ConnectListNodes(pNode4, pNode5); 59 60 Test(pNode1); 61 cout<<endl; 62 63 DestroyList(pNode1); 64 } 65 66 void Test2() 67 { 68 cout<<"Test2 begins:"<<endl; 69 ListNode* pNode1 = CreateListNode(1); 70 Test(pNode1); 71 cout<<endl; 72 DestroyList(pNode1); 73 } 74 75 void Test3() 76 { 77 cout<<"Test3 begins:"<<endl; 78 Test(NULL); 79 } 80 81 int main() 82 { 83 Test1(); 84 Test2(); 85 Test3(); 86 return 0; 87 }
1 #include <iostream> 2 using namespace std; 3 4 struct ListNode 5 { 6 int m_nKey; 7 ListNode* m_pNext; 8 }; 9 10 void PrintListReverse(ListNode* pHead) 11 { 12 if(pHead!=NULL) 13 { 14 if(pHead->m_pNext!=NULL) 15 { 16 PrintListReverse(pHead->m_pNext); 17 } 18 cout<<pHead->m_nKey<<" "; 19 } 20 } 21 22 ListNode* CreateListNode(int key) 23 { 24 ListNode* pNode=new ListNode(); 25 pNode->m_nKey=key; 26 pNode->m_pNext=NULL; 27 return pNode; 28 } 29 30 void ConnectListNodes(ListNode *pNode1,ListNode *pNode2) 31 { 32 pNode1->m_pNext=pNode2; 33 } 34 35 void DestroyList(ListNode *pHead) 36 { 37 pHead=NULL; 38 } 39 40 void Test(ListNode *pHead) 41 { 42 PrintListReverse(pHead); 43 } 44 45 void Test1() 46 { 47 cout<<"Test1 begins:"<<endl; 48 49 ListNode* pNode1 = CreateListNode(1); 50 ListNode* pNode2 = CreateListNode(2); 51 ListNode* pNode3 = CreateListNode(3); 52 ListNode* pNode4 = CreateListNode(4); 53 ListNode* pNode5 = CreateListNode(5); 54 55 ConnectListNodes(pNode1, pNode2); 56 ConnectListNodes(pNode2, pNode3); 57 ConnectListNodes(pNode3, pNode4); 58 ConnectListNodes(pNode4, pNode5); 59 60 Test(pNode1); 61 cout<<endl; 62 63 DestroyList(pNode1); 64 } 65 66 void Test2() 67 { 68 cout<<"Test2 begins:"<<endl; 69 ListNode* pNode1 = CreateListNode(1); 70 Test(pNode1); 71 cout<<endl; 72 DestroyList(pNode1); 73 } 74 75 void Test3() 76 { 77 cout<<"Test3 begins:"<<endl; 78 Test(NULL); 79 } 80 81 int main() 82 { 83 Test1(); 84 Test2(); 85 Test3(); 86 return 0; 87 }
题目2:在O(1)时间删除链表接点
给定单项链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
思路:若是从头结点开始遍历寻找该删除结点p,那么时间复杂度肯定是O(n),那么自然想到从删除结点p入手,因为结点p有一个指向下一个结点的指针next,我们将p的值赋给下一个指针,删除结点p即可。(注意考虑代码完整性:链表只有一个结点,删除尾结点,删除普通结点)
题目3:链表中倒数第k个结点
我的思路还是两个指针i,j指向头结点,i先向后遍历,当i指向第k个结点时,j开始移动,i指向尾结点的时候,那么j指向的结点就是倒数第k个结点。
但是存在3个问题(鲁棒性):
(1)输入的pListNode为空指针。试图访问空指针的内存,程序崩溃。
(2)输入的以pListNode为头结点的结点数少于k。循环k-1次,仍然会指向空指针。
(3)输入参数为0。由于k是一个无符号整数,那么在for循环中k-1得到的将不是-1,而是4294967295(0xFFFFFFFF),同样也会造成程序崩溃。
题目4:反转链表
思路很简单,原链表的3个结点p->q->r,q->next=q(不能写反了);p=q;q=r;
题目5:合并两个排序的链表
1 ListNode *Merge(ListNode *pHead1,ListNode *pHead2) 2 { 3 if(pHead1==NULL) return pHead2; 4 if(pHead2==NULL) return pHead1; 5 ListNode *pHead=NULL; 6 if(pHead1->value<pHead2->value) 7 { 8 pHead=pHead1; 9 pHead->next=Merge(pHead1->next,pHead2); 10 } 11 else 12 { 13 pHead=pHead2; 14 pHead->next=Merge(pHead1,pHead2->next); 15 } 16 return pHead; 17 }
题目6:复杂链表的复制
请实现函数ComplexListNode *Clone(ComplexListNode *pHead),复制一个复杂链表。在复杂 链表中,每个节点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任意结点或者NULL。
题目7:两个链表的第一个公共结点
输入两个链表,找出他们的第一个公共结点。
给两个指针i,j分别从两个表头开始遍历,找到相同的结点遍历结束。其实我错了亮点:第一,key相同的不一定是同一个结点;第二,两个链表不一定一样长,一起遍历,因为可能会错开相同的结点,不一定能够找到。比如:
上图中红2不是公共结点,红5才是。因为公共结点后的key都是相同的,所以正确的做法是:先遍历两个链表,得到表长length1,length2,这里假设length1>length2。重新遍历,指针i先走(length1-length2)步,然后i,j一起前进,遇到相同key,记录结点pNode,继续遍历,若后续结点不同,则pNode置空,继续遍历,直到遍历结束,返回pNode。
四.树
二叉树结点的定义如下:
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode *m_pLeft;
BinaryTreeNode *m_pRight;
};
题目1:重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含有重复的数字。
题目2:数的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。
题目3:二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
题目4:从上往下打印二叉树
题目5:二叉搜索树的后续遍历序列
题目6:二叉树中和为某一值的路径
题目7:二叉搜索树与双向链表
题目8:二叉树的深度
五.栈和队列
题目1:用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。
template<typename T> class CQueue
{
public:
CQueue(void);
~CQueue(void);
void appendTail(const T& node);
T deleteHead();
private:
stack<T> stack1;
stack<T> stack2;
};
1 #include <iostream> 2 #include <stack> 3 using namespace std; 4 5 template <class T> 6 class CQueue 7 { 8 public: 9 CQueue(void){}; 10 ~CQueue(void){}; 11 12 void appendTail(const T& node); 13 T deleteHead(); 14 15 private: 16 stack<T> stack1; 17 stack<T> stack2; 18 }; 19 20 template <class T> 21 void CQueue<T>::appendTail(const T& node) 22 { 23 stack1.push(node); 24 } 25 26 template <class T> 27 T CQueue<T>::deleteHead() 28 { 29 while(!stack1.empty()) 30 { 31 T temp=stack1.top(); 32 stack1.pop(); 33 stack2.push(temp); 34 } 35 if(stack2.empty()) 36 throw new exception("queue is empty"); 37 T head=stack2.top(); 38 stack2.pop(); 39 return head; 40 } 41 42 int main() 43 { 44 CQueue<int> queue; 45 queue.appendTail(1); 46 queue.appendTail(2); 47 queue.appendTail(3); 48 cout<<queue.deleteHead()<<endl; 49 cout<<queue.deleteHead()<<endl; 50 cout<<queue.deleteHead()<<endl; 51 return 0; 52 }
1 #include <iostream> 2 #include <stack> 3 using namespace std; 4 5 template <class T> 6 class CQueue 7 { 8 public: 9 CQueue(void){}; 10 ~CQueue(void){}; 11 12 void appendTail(const T& node); 13 T deleteHead(); 14 15 private: 16 stack<T> stack1; 17 stack<T> stack2; 18 }; 19 20 template <class T> 21 void CQueue<T>::appendTail(const T& node) 22 { 23 stack1.push(node); 24 } 25 26 template <class T> 27 T CQueue<T>::deleteHead() 28 { 29 while(!stack1.empty()) 30 { 31 T temp=stack1.top(); 32 stack1.pop(); 33 stack2.push(temp); 34 } 35 if(stack2.empty()) 36 throw new exception("queue is empty"); 37 T head=stack2.top(); 38 stack2.pop(); 39 return head; 40 } 41 42 int main() 43 { 44 CQueue<int> queue; 45 queue.appendTail(1); 46 queue.appendTail(2); 47 queue.appendTail(3); 48 cout<<queue.deleteHead()<<endl; 49 cout<<queue.deleteHead()<<endl; 50 cout<<queue.deleteHead()<<endl; 51 return 0; 52 }
题目2:包含min函数的栈
题目3:栈的压入、弹出序列
六.其他
题目1:从1到n整数中1出现的次数
输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些证书中包含1的数字有1,10,11,和12,1一共出现了5次。
题目2:丑数
题目3:和为s的两个数字VS和为s的连续正数序列
题目4:n个骰子的点数
题目5:扑克牌的顺子
题目6:圆桌最后剩下的数字
题目7:求1+2+...+n
题目8:不能被继承的类