算法笔记(一)
一 什么是正确的回答一个算法问题
回答一个算法问题,并不是一下子回答出来一个解决方案,而应该是和面试官探讨的过程,在探讨过程中逐渐选择一个最优方案
对一组数据进行排序
这组数据有什么样的特征?
- 有没有包含大量重复的元素? 如果有,三路快排
- 是否大部分数据距离它的正确位置很近?即是否近乎有序?插入排序最好
- 取值范围是否有限?比如对学生成绩排序,这样计数排序更好
有什么额外要求?
- 是否要求稳定排序? 如果是,快排不好,归并排序是更好的选择
数据的存储状态是怎样的?
- 快排等算法依赖数组的随机存取的特征,如果数据存储在链表中,快排不适用,归并排序更好
- 数据的大小是否可以装载在内存里?如果数据很大,或者内存很小,不足以装载到内存,需要使用外排序算法或者对数据进行转换,比如bitmap算法
即便一个问题一时回答不上来,也应该积极给出一个方向,让面试官知道所做出的努力和思考
二、算法面试优秀不意味着技术面试优秀
项目经历和项目中遇到的实际问题,考察做项目时是仅仅为了达到要求,还是自己进行了深入的思考
你遇到的印象最深的bug是什么?
面向对象
设计模式
网络相关;安全相关;内存相关;并发相关;
系统设计;scalability(系统在大规模使用时可能遇到的问题)
三、技术面试优秀不等于offer
经常会问其他问题,与人的交流合作、个人思考问题的方式、生活习惯、思维模式、价值观、对公司的了解等等
- 遇到的最大挑战是什么?
- 你是怎么解决xxxx的?
- 遇到xxx你会xxx?
- 你有什么要问面试官的?
四、如何准备算法面试?
算法导论
完全没必要那么大,如果喜欢读,必须有选择的读,跳过理论推导,记住结论即可,后面再回过来慢慢看
五、面试中的时间复杂度
时间复杂度
是一个近似,也跟指令条数有关。假如一个算法的时间复杂度是O(n),那么其执行时间是aO(n),前面是一个固定的常数,代表指令条数。
对于时间复杂度的对比,一定要注意是否在相同数据规模下,否则是没有意义的。
数据规模的概念
如果想要在1s之内解决问题:
- O(n2)的算法可以处理大约104级别的数据
- O(n)的算法可以处理大约10^8级别的数据
- O(nlogn)的算法可以处理大约10^7级别的数据
这个数据是在仅进行简单的加法操作的情况下得出的,保险起见,可以再除以10
空间复杂度
多开一个辅助数组: O(n)
多开一个辅助的二维数组:O(n^2)
多开常数空间:O(1)
注意:递归调用是有空间代价的,其跟递归的深度有关,需要在系统栈中存入状态
时间复杂度分析
一般的时间复杂度都要从代码层面去分析,在有循环的代码里,一定要注意循环的上下界,举个例子:
for (i = 0; i < n; i++)
for (j=0; j < 30; j++)
swap(a, b);
这里虽然是两重循环,但里面的循环只循环了30次,外层循环n次,总共30n次,时间复杂度是O(n)
摊还分析
举个例子,构建一个动态数组,在数组元素个数达到数组容量时进行扩容
void push_back(T value) {
if (size == capacity)
resize(2 * capacity)
data[size++] = value;
}
void resize(int newCapacity) {
T* newData = new T[newCapacity];
for (int i =0; i < size; i++)
newData[i] = data[i];
delete *data;
}
这里的代码,增加一个元素,时间复杂度是O(1),但里面增加了一个resize操作,resize的操作时间复杂度是O(n),那么,push_back的事件复杂度是多少呢?
这里应该还是O(1),具体分析来说,每增加一个元素时O(1),当增加到满的时候,此时进行了n次O(1),进行扩容,也是O(n),一共O(2n),一共n次操作,平均下来每次为O(2),还是O(1)
复杂度震荡问题
在动态数组实现中,有这样一种实现,增加元素时,每次元素个数达到数组容量的1/2便进行扩容。同时,在删除元素时,每次元素个数得到1/2时,进行数组容量缩减,这样存在一种极端情况:增加一个元素,进行了扩容,时间复杂度是O(n),扩容之后立马删除元素,又要对数组进行缩减,时间复杂度也是O(n),这样操作便退化了,造成了复杂度震荡。