专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

前端面试-算法大全 前端面试常见算法题

ins518 2024-09-29 18:29:57 技术文章 14 ℃ 0 评论

前端面试遇到的一些基本算法问题~

1 全排列

```

Bash
function permute(input) {
    var permArr = [],
        usedChars = [];
    function main(input) {
        for (let i = 0; i < input.length; i++) {
            let ch = input.splice(i, 1)[0];
            usedChars.push(ch);
            if (input.length == 0) {
                permArr.push(usedChars.slice());
            }
            main(input);
            input.splice(i, 0, ch);
            usedChars.pop();
        }
        return permArr
    }
    return main(input);
};
复制代码

```

2 二分法

```

Bash
function BinarySearch (arr, target) {
    let from = 0
    let to = arr.length - 1
    let mid = Math.floor((from + to)/2)
    while (from <= to) {
        mid = Math.floor((from + to)/2)
        if (arr[mid] > target) {
            to = mid - 1
        } else if (arr[mid] < target) {
            from = mid + 1
        } else {
            return mid
        }
    }

    return -1
}
复制代码

```

4 大整数加法

```

function sumStrings(a,b){
    var res='', c=0;
    a = a.split('');
    b = b.split('');
    while (a.length || b.length || c){
        c += ~~a.pop() + ~~b.pop();
        res = c % 10 + res;
        c = c>9;//c大于9返回1,小于等于为0
    }
    return res.replace(/^0+/,'');
}
复制代码

```

3 排序

3.1 冒泡排序

冒泡排序的基本思想是,对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端, 最终达到完全有序。

```

function bubbleSort(arr) {
    if (!Array.isArray(arr) || arr.length <= 1) return;
    let lastIndex = arr.length - 1;
    while (lastIndex > 0) { // 当最后一个交换的元素为第一个时,说明后面全部排序完毕
        let flag = true, k = lastIndex;
        for (let j = 0; j < k; j++) {
            if (arr[j] > arr[j + 1]) {
                flag = false;
              	lastIndex = j; // 设置最后一次交换元素的位置
                [arr[j], arr[j+1]] = [arr[j+1], arr[j]];
            }
        }
      	if (flag) break;
    }
}
复制代码

```

冒泡排序有两种优化方式。

一种是外层循环的优化,我们可以记录当前循环中是否发生了交换,如果没有发生交换,则说明该序列已经为有序序列了。 因此我们不需要再执行之后的外层循环,此时可以直接结束。

一种是内层循环的优化,我们可以记录当前循环中最后一次元素交换的位置,该位置以后的序列都是已排好的序列,因此下 一轮循环中无需再去比较。

优化后的冒泡排序,当排序序列为已排序序列时,为最好的时间复杂度为 O(n)。

冒泡排序的平均时间复杂度为 O(n2) ,最坏时间复杂度为 O(n2) ,空间复杂度为 O(1) ,是稳定排序。

3.2 选择排序

选择排序的基本思想为每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止。

```

function SelectionSort (arr) {
    const length = arr.length
    for (let i = 0; i < length; i++ ) {
        let minIndex= i
        for (let j = i + 1; j < length; j++) {
            minIndex = arr[minIndex] <= arr[j] ? minIndex : j
        }
        if (minIndex !== i) {
            const temp = arr[i]
            arr[i] = arr[minIndex]
            arr[minIndex] = temp
        }
    }
    return arr
}
复制代码

```

在算法实现时,每一趟确定最小元素的时候会通过不断地比较交换来使得首位置为当前最小,交换是个比较耗时的操作。 其实我们很容易发现,在还未完全确定当前最小元素之前,这些交换都是无意义的。我们可以通过设置一个变量 min,每一次比较 仅存储较小元素的数组下标,当轮循环结束之后,那这个变量存储的就是当前最小元素的下标,此时再执行交换操作即可。 选择排序不管初始序列是否有序,时间复杂度都为 O(n2)。

选择排序的平均时间复杂度为 O(n2) ,最坏时间复杂度为 O(n2) ,空间复杂度为 O(1) ,不是稳定排序。

3.3 插入排序

直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。

插入排序核心--扑克牌思想: 就想着自己在打扑克牌,接起来一张,放哪里无所谓,再接起来一张,比第一张小,放左边, 继续接,可能是中间数,就插在中间....依次

```

function InsertionSort (arr) {
    const length = arr.length
    for (let i = 1; i < length; i++) {
        const temp = arr[i]
        let j
        for (j = i - 1; j >= 0 && temp < arr[j]; j--) {
            arr[j+1] = arr[j]
        }
        arr[j+1] = temp
    }
    return arr
}
复制代码

```

当排序序列为已排序序列时,为最好的时间复杂度 O(n)。

插入排序的平均时间复杂度为 O(n2) ,最坏时间复杂度为 O(n2) ,空间复杂度为 O(1) ,是稳定排序。

3.4 希尔排序

希尔排序的基本思想是把数组按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的元素越来越多,当增量减至1时,整个数组恰被分成一组,算法便终止。

```

function ShellSort (arr) {
    const length = arr.length
    let gap = Math.floor(length)
    while (gap) {
        for (let i = gap; i < length; i++) {
            const temp = arr[i]
            let j
            for (j = i - gap; j >= 0 && temp < arr[j]; j = j - gap) {
                arr[j + gap] = arr[j]
            }
            arr[j + gap] = temp
        }
        gap = Math.floor(gap / 2)
    }
    return arr
}
复制代码

```

希尔排序是利用了插入排序对于已排序序列排序效果最好的特点,在一开始序列为无序序列时,将序列分为多个小的分组进行 基数排序,由于排序基数小,每次基数排序的效果较好,然后在逐步增大增量,将分组的大小增大,由于每一次都是基于上一 次排序后的结果,所以每一次都可以看做是一个基本排序的序列,所以能够最大化插入排序的优点。

简单来说就是,由于开始时每组只有很少整数,所以排序很快。之后每组含有的整数越来越多,但是由于这些数也越来越有序, 所以排序速度也很快。

希尔排序的时间复杂度根据选择的增量序列不同而不同,但总的来说时间复杂度是小于 O(n^2) 的。

插入排序是一个稳定排序,但是在希尔排序中,由于相同的元素可能在不同的分组中,所以可能会造成相同元素位置的变化, 所以希尔排序是一个不稳定的排序。

希尔排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(n^s) ,空间复杂度为 O(1) ,不是稳定排序。

3.5 归并排序

归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略。递归的将数组两两分开直到只包含一个元素,然后 将数组排序合并,最终合并为排序好的数组。

```

function MergeSort (arr, low, high) {
    const length = arr.length
    if (low === high) {
        return arr[low]
    }
    const mid = Math.floor((low + high)/2)
    MergeSort(arr, low, mid)
    MergeSort(arr, mid + 1, high)
    merge(arr, low, high)
    return arr

}

function merge (arr, low, high) {
    const mid = Math.floor((low + high)/2)
    let left = low
    let right = mid + 1
    const result = []
    while (left <= mid && right <= high) {
        if (arr[left] <= arr[right]) {
            result.push(arr[left++])
        } else {
            result.push(arr[right++])
        }
    }
    while (left <= mid) {
        result.push(arr[left++])
    }
    while (right <= high) {
        result.push(arr[right++])
    }

    arr.splice(low, high-low+1, ...result)
}

const test = [2, 34, 452,3,5, 785, 32, 345, 567, 322,5]

console.log(MergeSort(test, 0, test.length - 1))
复制代码

```

归并排序将整个排序序列看成一个二叉树进行分解,首先将树分解到每一个子节点,树的每一层都是一个归并排序的过程,每 一层归并的时间复杂度为 O(n),因为整个树的高度为 lgn,所以归并排序的时间复杂度不管在什么情况下都为O(nlogn)。

归并排序的空间复杂度取决于递归的深度和用于归并时的临时数组,所以递归的深度为 logn,临时数组的大小为 n,所以归 并排序的空间复杂度为 O(n)。

归并排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(nlogn) ,空间复杂度为 O(n) ,是稳定排序。

3.6 快速排序

快速排序的基本思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据 都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

```

function quickSort(array){
    let pivot = array[array.length - 1];
    let left = array.filter((v,i)=> v <=pivot && i != array.length - 1)
    let right = array.filter(v=> v > pivot)
    return [...quickSort(left),pivot,...quickSort(right)]
}
复制代码

```

  • Js中sort()对长度小于10的数组采用插入排序,对大于10的采用快速排序
  • 优化方法1:结合快排和插入排序,当排到集合长度小于某个数值时,采用插入排序等稳定的排序,优化效果;
  • 优化方法2:随机取基准值,因为每次取第一个或是最后一个,如果数组本身有序就会带来很多交换,因此随机取基准可以避免这个问题,由于Js的随机某种程度上是伪随机,可以采用随机三次来接近随机;
  • 优化方法三:取第一个、最后一个和中间值的中位数,来防止数组本身的顺序干扰;
  • 优化方法四:如果代码中有两次递归,可以将前一次递归改为循环,优化效率;
  • 优化方法五:将相同的数值聚集在一起,避免重复交换。
  • 3.7 堆排序

    堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行 交换,此时末尾就为最大值。然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行, 便能得到一个有序序列了。

    ```

    function HeapSort (arr) {
        const length = arr.length
    
        // 调整初始堆,调整完其实也确定了最大值
        // 但此时最大值是在 arr[0] 中
        for (let i = Math.floor(length/2) - 1; i >= 0; i--) {
            adjustHeap(arr, i, length)
        }
    
        // 把 arr[0](最大值)换到后面
        for (let i = length - 1; i >=0; i--) {
            const temp = arr[0]
            arr[0] = arr[i]
            arr[i] = temp
            adjustHeap(arr, 0, i)
        }
    
        return arr
    }
    
    // size 是还需要调整的堆的大小
    // 随着一个个最大值的确定,size 会越来越小
    function adjustHeap (arr, position, size) {
        const left = position * 2 + 1
        const right = left + 1
        let maxIndex = position
        if (left < size && arr[left] > arr[maxIndex]) {
            maxIndex = left
        }
        if (right < size && arr[right] > arr[maxIndex]) {
            maxIndex = right
        }
        if (maxIndex !== position) {
            const temp = arr[position]
            arr[position] = arr[maxIndex]
            arr[maxIndex] = temp
            adjustHeap(arr, maxIndex, size)
        }
        return arr
    }
    复制代码

    ```

    建立堆的时间复杂度为 O(n),排序循环的次数为 n-1,每次调整堆的时间复杂度为 O(logn),因此堆排序的时间复杂度在 不管什么情况下都是 O(nlogn)。

    堆排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(nlogn) ,空间复杂度为 O(1) ,不是稳定排序。

    有想了解更多的小伙伴可以去[链接](https://www.jianshu.com/p/86bb74768d12)看一下,应该对你们能够有所帮助。

    本文暂时没有评论,来添加一个吧(●'◡'●)

    欢迎 发表评论:

    最近发表
    标签列表