研二的开始找工作了,首先祝愿他们都能够找到自己满意的工作。看着他们的身影,自问明年自己这个时候是否可以从容面对呢?心虚不已,赶紧从老严那儿讨来一本《剑指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]=='