[微软面试100题]51-60
第五十一题:输出所有和为n的连续正数序列
方法1:从1遍历到n/2,算出所有sum,如果sum刚好等于n则输出,效率极低。
方法2:用一个变量b标志连续序列的开头,e表示结尾。如果序列的sum<n,则e++;如果sum>n,则b++。复杂度O(n)
void constSumSequence(int n){ int b=1,e=1,sum=1,bound=n/2+1; while(e<=bound){ if(sum==n){ for(int k=b;k<=e;++k)cout<<k<<" "; cout<<endl; sum+=(e+1-b); b++; e++; }else if(sum>n){ sum-=b; b++; }else{ e++; sum+=e; } } }
第五十三题:输入字符串,打印字符串所含字符的所有组合
以第一个字符为分组。每组还没到最后两个字符时,往下递归。到最后两个时,先打印一次,调换了再打印一次。
把所有字符一次放到字符串第一位,依次进行上面算法
如a组abc acb, b组bac bca c组cba cab
//将第i个字符与后面的字符串对掉 void swapchar(char *c,int i,int n){ char tmp=c[i]; for(int k=i+1;k<n;++k){ c[k-1]=c[k]; } c[n-1]=tmp; } void helper(char *c,int i,int n){ if(i<n-2){//还没到最后两个字符时,swap char tmp[n+1]; for(int k=0;k<n;++k)tmp[k]=c[k]; tmp[n]='\0'; helper(c,i+1,n); swapchar(tmp,i,n); helper(tmp,i+1,n); }//到最后两个,打印一个,对调后再打印一次 cout<<c<<endl; swapchar(c,i,n); cout<<c<<endl; } //把每一个字符依次放到第一 void final(char *c,int n){ for(int i=0;i<n;++i){ char tmp[n+1]; for(int k=0;k<n;++k)tmp[k]=c[k]; tmp[n]='\0'; char firstChar=c[i]; tmp[i]=c[0]; tmp[0]=firstChar; helper(tmp,1,n); } }
第五十四题:调整数组位置使奇数放在数组前部,偶数后部。复杂度为O(n)
记录一个index,然后从前往后扫描数组,扫到一个奇数就把奇数和index上的数对调,然后index++。
不知道为什么网上这一题其他人的答案都好复杂什么从两边同时扫。。。明显简单问题复杂化了吧?
void helper(int *num,int n){ int i=0; for(int j=0;j<n;++j){ if(num[j]%2!=0){ int tmp=num[i]; num[i]=num[j]; num[j]=tmp; i++; } } }
第五十六题:最长公共子串
定义f(m, n)为Xm和Yn之间最长的子序列的长度
若f(m-1,n-1)已知,那么这时候检查Xm和Yn
1.如果 xm == xn,那么有,说明最后一位应该加入公共子序列,所以其长度 f(m,n) = f(m-1,n-1)+1
2.如果 ym != yn,那么公共子序列是:
X(m-1)与 Y(n)的最长公共子序列
X(m)与 Y(n-1)的最长公共子序列
之间较长的那个
则f(m, n) = max{ f(m-1, n), f(m, n-1) }
PS:需要用连个FOR循环,将表从左上往右下遍历。最后右下就是结果
int max(int a,int b){ if(a>=b)return a; else return b; } int helper(int **f,int i,int j){ if(f[i][j]==-1){ int a,b; if(f[i-1][j]!=-1)a=f[i-1][j]; else a=helper(f,i-1,j); if(f[i][j-1]!=-1)b=f[i][j-1]; else b=helper(f,i,j-1); f[i][j]=max(a,b); return f[i][j]; }else{ return f[i][j]; } } int final(char *c1,char *c2,int n1,int n2){ int* f[n1+1]; for(int i=0;i<=n1;++i){ f[i]=new int[n2+1]; } for(int i=0;i<=n1;++i){ for(int j=0;j<=n2;++j) f[i][j]=-1; } for(int i=0;i<=n1;++i){ f[i][0]=0; } for(int i=0;i<=n2;++i){ f[0][i]=0; } for(int i=1;i<=n1;++i){ for(int j=1;j<=n2;++j){ if(c1[i-1]==c2[j-1]){ f[i][j]=helper(f,i-1,j-1)+1; }else helper(f,i,j); } } return f[n1][n2]; }
第五十七题:用两栈实现队列
一盏用来存放,一盏用来弹出。栈具有翻转顺序的性质,两个栈就刚刚是顺序的队列性质了。
第五十九题:设计不能被继承的类
方法1:把构造函数和析构函数声明为私有。使用静态函数来new和delete来新建。方法2:
class Helper{ friend class noSon;//这里其实应该用模板T代替noson,但codeblock不知为何不支持 private: Helper(){} ~Helper(){} }; class noSon:virtual public Helper{//使用虚继承是希望子类直接调用最底层父类的构造函数。从而继承noson的子类调用Help的私有构造函数 public: //这样noson就不能被继承。如果没有virtual,则子类会正常调用noson的共有构造函数。 noSon(){cout<<"noSon is created!";}//而noson在调用helper的构造函数(因为是友元),从而可以使用。 };
虚继承:普通继承时,子类只可以调用直接父类的构造函数。而虚继承时,子类可以调用所有父类的构造函数。详细看源代码
虚继承时为了解决多继承的“二义性”问题。 例如B继承A,C也继承A,D继承B,C。则D含有2个A的实例。调用了2次A的构造函数。
更严重的是造成“二义性”,就是通过D调用A的函数或者成员时,由于有2个A实例,到底调用哪个?而使用虚继承时则只有一个A实例。
第六十题:O(1)时间删除单链表节点
传统方法是从链表头节点开始遍历,找到需要删除的节点的前一个节点,然后把这个节点与要删的后一个节点相连。
新的方法是,把要删的后一个节点的值复制给要删的节点,然后删除后一个节点。这样就不需要遍历了。如要删最后一个节点则按传统方法,总体的平均复杂度也是O(1)