1343 字
7 分钟
面试题 算法
衡量不同算法之间的优劣主要是通过时间和空间两个维度去考量:
时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述
时间复杂度
一个算法花费的时间与算法中语句的「执行次数成正比」,执行次数越多,花费的时间就越多
空间复杂度
空间复杂度主要指执行算法所需内存的大小,用于对程序运行过程中所需要的临时存储空间的度量
冒泡排序
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
选择排序
在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好
唯一的好处是不占用额外的内存存储空间
function selectionSort(arr) {
var len = arr.length;
for (var i = 0; i < len; i++) {
var minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
var temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
return arr;
}
插入排序
它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
function insertionSort(arr) {
var len = arr.length;
for (var i = 1; i < len; i++) {
var j = i - 1;
var temp = arr[i];
while (j >= 0 && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
return arr;
}
快速排序
快速排序是对冒泡排序算法的一种改进,从数列中挑出一个元素,称为”基准”(pivot)
重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
归并排序
分而治之,二叉树
sort 排序(Arrays.sort 并不是单一的排序,而是插入排序,快速排序,归并排序三种排序的组合)
const numberArray = [40, 1, 5, 200];
function compareNumbers(a, b) {
return a - b;
}
numberArray.sort(compareNumbers);
const items = [
{ name: "Edward", value: 21 },
{ name: "Sharpe", value: 37 },
{ name: "And", value: 45 },
{ name: "The", value: -12 },
{ name: "Magnetic", value: 13 },
{ name: "Zeros", value: 37 },
];
items.sort((a, b) => a.value - b.value);
// 使用 map 改善排序
// 需要被排序的数组
const data = ["delta", "alpha", "charlie", "bravo"];
// 用于存放位置和排序值的对象数组
const mapped = data.map((v, i) => {
return { i, value: someSlowOperation(v) };
});
// 按照多个值排序数组
mapped.sort((a, b) => {
if (a.value > b.value) {
return 1;
}
if (a.value < b.value) {
return -1;
}
return 0;
});
const result = mapped.map((v) => data[v.i]);
贪心算法
其期待每一个阶段都是局部最优的选择,从而达到全局最优,但是结果并不一定是最优的
回溯算法
回溯算法会先从一个可能的工作开始解决问题,如果不行,就回溯并选择另一个动作,知道将问题解决
栈
栈(stack)又名堆栈,它是一种运算受限的线性表,限定仅在表尾进行插入和删除操作的线性表
class Stack {
constructor() {
this.items = [];
}
/**
* 添加一个(或几个)新元素到栈顶
* @param {*} element 新元素
*/
push(element) {
this.items.push(element);
}
/**
* 移除栈顶的元素,同时返回被移除的元素
*/
pop() {
return this.items.pop();
}
/**
* 返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)
*/
peek() {
return this.items[this.items.length - 1];
}
/**
* 如果栈里没有任何元素就返回true,否则返回false
*/
isEmpty() {
return this.items.length === 0;
}
/**
* 移除栈里的所有元素
*/
clear() {
this.items = [];
}
/**
* 返回栈里的元素个数。这个方法和数组的length属性很类似
*/
size() {
return this.items.length;
}
}
队列
跟栈十分相似,队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作
class Queue {
constructor() {
this.items = [];
}
enqueue(element) {
this.items.push(element);
}
dequeue() {
if (this.isEmpty()) {
return "队列为空";
}
return this.items.shift();
}
front() {
if (this.isEmpty()) {
return "队列为空";
}
return this.items[0];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
clear() {
this.items = [];
}
}