点赞再看,养成习惯,您动动手指对原创作者意义非凡🤝 备战2021秋招面试 微信搜索【CodePan】关注更多新鲜好文和互联网大厂的笔经面经。 作者@几番问道
面试现场,终于到了紧张刺激的手撕代码环节,你忐忑的心情随着考官的一句话归于平静。
你简直不敢相信眼前这个穿着格子衬衫,牛仔裤角有些发白,头发在风中有些凌乱但仍然难掩老程序员气质的面试官竟然问出这么水的问题,你有些诧异的眼神瞥了一下他,他只是推了推镜框,厚厚的镜片埋下的眼睛连正眼都没有看你。你压抑住自己兴奋激动的心情,“老子早写了八百遍了”,但还是装出若有所思的样子,大约过了十分钟,你把代码甩到了他的脸上,不好意思,这是在梦里, 现实中你恭恭敬敬地交出了如下代码,并幻想着面试官向你投来赞许的目光,后生可畏呀:

眼看面试官一愣,门口的保安拿着对讲机已经蠢蠢欲动,另外一只手隐隐约约拿了样东西,像是砖头,但比砖头略大。你迟疑了几秒,“不好意思不好意思,拿错了,应该是这个“,你挠了挠头,抱歉的样子还是一如往常帅气逼人。
void QuickSort_Core(vector<int> &A, int Left, int Right){
if(Left>=Right) {return;}
int pivot = A[0];
int i=Left;
int j=Right-1;
while(1){
while(A[++i] < pivot){} //从左往右扫,碰到大于pivot的值,就记录下来
while(A[--j] > pivot){} //从右往左扫,碰到小于pivot的值,就记录下来
if(i<j){ //如果没有扫完整个数组就停下,说明有值需要交换
swap(A[i],A[j]);
}
else
{
break;
}
}
swap(A[i],A[Right-1]); //把pivot插入指针i,j相遇点
QuickSort_Core(A,Left,i-1);
QuickSort_Core(A,i+1,Right);
}
int main(){
vector<int> A = {};
int N = A.size();
Quick_Sort(A,0,N-1);
cout<<"{";
return 0;
}
你以为自己大功告成,结果面试官追问:
”快排采用的是分治的策略,根据选定的pivot,即主元,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,就可以达到整个数据变成有序序列。“
你的回答熟练的让人心疼,但是面试官好像并没有什么回应。
意料之中,ez。
”是这样的,快速排序是现实应用中最快的排序算法,它的时间复杂度可以达到,这是因为它采用了“分而治之”的思想。同时需要
的空间复杂度,这是他递归调用需要的额外栈空间。“
回答完这道,面试官终于抬起了头开始正眼看你啦,你觉得自己的自信心达到了新的高度。至此,一切还在你的掌控之中。
面试官又扶了扶眼镜,问道:
你开始碰到了之前没碰到的问题了,有点慌,但是应场能力还是可以的,你只是用余光警惕地瞟了一眼门口的保安,然后赶紧回答道:
”我觉得我写的这个代码 特别完美一不小心咋还说出心里话了捏 在处理本身就是有序的数组时可能会重复排序递归的子数组。“
你开始有了一丝慌张,?,你不是很自信的答道。
“emmmmm那个,应该是嵌套递归相关,emmm”,
幸运的是,老练的面试官还是给了你一些帮助,你踉踉跄跄终于写出如下推导: 假设我递归的时候,每次都选取数组第一个值作为pivot,那么如果一个数组如果本身就是有序的,比如1到N,我选取1做pivot,其他数和他比较分为两个子集,再递归,但是这里,化成的子集就是2到N,又重复,(近似等于没有分治)。

好,面试官看你踉踉跄跄答出来了,于是决定还能再 虐虐你 多交流一下,于是问道,
看你面露难色,面试官又说道,
你有了思路,就开始写起了代码,经过一段时间的苦思冥想,你得到如下的pivot选取办法,
int Median3(vector<int> &A, int Left, int Right){
int center = (Left+Right)/2;
if(A[Left] > A[center]){ //两两比较,将三个数排序
swap(A[Left],A[center]);
}
if(A[center] > A[Right]){
swap(A[Right],A[center]);
}
if(A[Left] > A[center]){
swap(A[Left],A[center]);
}
swap(A[center],A[Right-1]); //取出中间那个并和最右边倒数第二个调换位置,这样只需要考虑Left+1到Right-2之间的排序
return A[Right-1]; //返回值当作pivot
}
你甚至耍了点小聪明,把数组的前一个和后两个都一起排序好了。但是,到了现在,快排问法的多种多样已经让你始料未及。
你认为如果比较的话会有很多多余的操作,所以你回答应该跳过。
到这里,你已经明显感觉到压力。
这时候,你觉得面试官的格子衫和牛仔裤是那么迷人,连隐隐可见的头油都显得光彩夺目,发出关爱智障的光。
你收起了你的痴汉脸,赶紧端正了下,答道:
“快速排序的问题可能就在于它是使用递归的,有的时候效率比较低”
“因为它是要占用大量系统的堆栈,有很多push和pop的操作,如果数据规模不大的话,是不需要递归的,可以直接用简单排序,比如插入排序就可以了。”
说罢,你擦了擦汗,看着快排已经问了将近一个小时,你似乎对快排有了新的理解。
???说好的最后一个问题呢???

为了读者的一个赞 呸, 为了这个offer,拼了。于是,你又花了些时间,以上面的代码为基础,得到了完整版快速排序,这里的threshold就是你设立的阈值,数组低于这个值时,就可以用插入排序了。
#include <iostream>
#include <vector>
using namespace std;
void swap(int &A,int &B){
int temp = B;
B = A;
A = temp;
}
int Median3(vector<int> &A, int Left, int Right){
int center = (Left+Right)/2;
if(A[Left] > A[center]){ //两两比较,将三个数排序
swap(A[Left],A[center]);
}
if(A[center] > A[Right]){
swap(A[Right],A[center]);
}
if(A[Left] > A[center]){
swap(A[Left],A[center]);
}
swap(A[center],A[Right-1]); //取出中间那个并和最右边倒数第二个调换位置,这样只需要考虑Left+1到Right-2之间的排序
return A[Right-1];
}
void QuickSort_Core(vector<int> &A, int Left, int Right){
if(Right-Left>=threshold){ //满足就快排
if(Left>=Right) {return;}
int pivot = Median3(A, Left, Right); //此时数组的最左边和最右边的值已经确定,不需要再考虑
int i=Left;
int j=Right-1;
while(1){
while(A[++i] < pivot){} //从左往右扫,碰到大于pivot的值,就记录下来
while(A[--j] > pivot){} //从右往左扫,碰到小于pivot的值,就记录下来
if(i<j){ //如果没有扫完整个数组就停下,说明有值需要交换
swap(A[i],A[j]);
}
else
{
break;
}
}
swap(A[i],A[Right-1]); //把pivot插入指针i,j相遇点
QuickSort_Core(A,Left,i-1);
QuickSort_Core(A,i+1,Right);
}
void Quick_Sort(vector<int> &A, int N){
QuickSort_Core(A, 0, N-1);
}
else{ //不满足就插入排序
Insert_Sort(A[remainingpart],Right-Left+1);
}
}
int main(){
vector<int> A = {};
int N = A.size();
Quick_Sort(A,N);
cout<<"{";
for(auto e:A){
cout<<e<<",";
}
cout<<"}";
return 0;
}
看着写满C++代码的白纸,你长舒了一口气,起身离开。
经过门口时,保安向你会心一笑,默默放下了手中比砖头还厚的《Java核心技术》,喃喃说道:Java才是世界上最好的编程语言。
回到学校,同学问你面试如何,
“没啥,面了个快排。” “那么简单?这波血赚。”
| 创作不易,你的鼓励是我创作的动力,如果你有收获,点个赞吧👍 |
|---|