js算法题目收集

331 阅读8分钟

一天一道算法题,防止老年痴呆,嘎嘎嘎嘎

一、 给定数组中查找几个数字和等于指定数字

一位大佬的博文:www.cnblogs.com/stemon/p/46…
解题思路:
1.暴力算法,多个for循环,很高的时间复杂度
2.利用Hash表(定义一位对象保存值,空间换时间),直接定位元素,很少的时间复杂度

两数之和
function fn(arr, n){
    let map = {}
    for(let i=0;i<arr.length;i++){
        if(arr[i]>=n){
            continue
        }
        if(map[n-arr[i]]){
            return [map[n-arr[i]]-1, i]
        }else{
            // 有可能保存第一位0,0会对应boolean为false,所有先加1
            map[arr[i]] = i+1
        }
    }
    return false
}

这里有出现了有重复数据,需要返回所有匹配值的问题。。。。

多数之和

思路:

使用递归.

代码实现

function fn(array, number, sum, temp = []) {
    if(temp.length === number){
        if(temp.reduce((a,b) => a + b) === sum){
            return temp
        }else{
            return false
        }
    }

    for(let i=0;i<arr.length;i++){
        let cur = arr.shift();
        temp.push(cur);
        const getReturn = fn(arr, number, sum, temp);
        if(getReturn){
            return getReturn
        }
        temp.pop();
        arr.push(cur);
    }
    return false
}

这个时间复杂度是 O(nnumber)

二、以时间复杂度O(n)从数组中找到所有满足一下条件的元素

1. 该元素比放在它右边的所有元素都小
2. 该元素比放在它左边的所有元素都大

2-1.实现方式一

思路:

1. 从左往右遍历,取出所有比前一位大的值存入数组maxArr
2. 从右往左遍历,取出所有比第一位小的值存入数组minArr
3. 两个数组交集就是最后需要的结果,既比前面大,又比后面小

代码实现

function findPeak(arr) {
    if (arr.length <= 1) return arr;

    const max = [arr[0]];
    const min = [arr[arr.length - 1]];
    // 找到比左边都大的
    // 注意左右两边第一个数据不满足条件
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] > max[max.length - 1]) {
            max.push(arr[i]);
        }
    }
    // 找到比右边都小的
    for (let i = arr.length-2; i >= 0; i--) {
        if (arr[i] < min[0]) {
            min.unshift(arr[i]);
        }
    }
    // 取交集
    const res = max.filter((i) => min.includes(i));
    if (res.length < 1) return -1;
    return res;
}
console.log(findPeak([1, 6, 2, 3, 4, 8, 9, 12]));

2-2.实现方式二

思路:

1. 从左往右遍历数组,如果当前值比数组最后一个数大,将当前值push进数组
2. 如果当前值比数组最后一个值小,清空数组,继续遍历
3. 用一个数组存放,用一个max值来比较大小

实现代码

let arr = [3,1,2,5,8,6,7,9,12]

function fn(arr){
    let stack = []
    let max = arr[0]

    for(let i=1;i<arr.length;i++){
        if(arr[i]>max){
            max = arr[i]
            stack.push(arr[i])
        }else{
            stack = []
        }
    }
    return stack
}

console.log(fn(arr))

三、求一个数组的子数组,子数组中任意两个数能被整除(大除以小)

3-1. 实现方式一

思路

1. c%b==0, b%a==0, 那么 c%a==0
2. 遍历数组,当前值如果能整除前面的某个数,保存当前值对应整除数组长度(上一个能被整除的数保存的长度+1),那么就要求数组先排序,后面的如果能整除前面的,就一定是前面保存值+1
3. 用dep记录相应值对应的最大长度。最后遍历,查出最长对应的最后一位数字。
4. 用path记录上一个被整除数的位置。
5. 最后获取到最大值,然后根据他的序号,在path中找到所有前面的值

实现代码

function getBiggestChildrenArray(arr) {
    if (arr.length === 0 || !arr) return;
    // 因为c%b==0, b%a==0, 那么 c%a==0
    // 所以给数组拍个序,那么当前值是否能被整除就只需查找之前的
    arr = arr.sort((num1, num2) => {
        if (num1 < num2) return -1;
    });
    // 深度,用来记录每位数前面有几个能被当前数整除
    let dep = new Array(arr.length);
    // 当前值最近一个能整除的序号,通过这个就能获取到最后能两两整除的所有数
    let path = new Array(arr.length); 
    let ret = [];
    for (let i = 0; i < arr.length; i++) {
        dep[i] = 1;
        path[i] = -1;
    }
    let ml = 1;
    let end = 0;
    for (let i = 1; i < arr.length; i++) {
        for (let j = 0; j < i; j++) {
            if (arr[i] % arr[j] == 0 && dep[i] < dep[j] + 1) {
                dep[i] = dep[j] + 1; // 叠加子树长度
                // 能被整除,获取到能被当前整除的上一位数的序号
                // 这个循环其实只为了获取最后一个
                path[i] = j;
            }
        }
        // 取最大深度
        if (dep[i] > ml) {
            ml = dep[i];
            end = i;
        }
    }
    // 循环获取arr里面存在的值
    for (let i = end; i != -1; i = path[i]) {
        ret.push(arr[i]);
    }
    return ret.reverse();
}

console.log(getBiggestChildrenArray([1, 2, 3, 6, 7, 9, 12, 15]));

如果你想要获取所有最大的,在获取最大那里整一个数组,存放所有最大值,然后遍历循环获取就好了。当前有如果有多个最长一样也只取一个。

3-2.实现方式二

实现思路

1.用一个二维数组group存放所有排列组合,默认排序数组第一个 [[min]]
2.遍历数组,里面遍历group,从后向前,如果当前数是group当前排列最后一个的倍数,group新加一个排列组合:匹配的加上当前数,然后如果新的长度大于maxLen,更新maxLen和索引maxIndex
3.返回maxIndex对应的组合就可以了

实现代码

function fn(arr){
    let sortArr = arr.sort((a,b) => a - b);
    // 用来存放所有组合
    let group = [[sortArr[0]]];
    let maxLen = 1;
    let maxIndex = 0;

    for(let i=1;i<sortArr.length;i++){
        for(let j=group.length-1;j>=0;j--){
            let lastItem = group[j][group[j].length-1];
            if(arr[i]%lastItem === 0){
                group.push([...group[j], sortArr[i]]);
                let curLen = group[group.length-1].length;
                if(curLen>maxLen){
                    maxLen = curLen;
                    maxIndex = group.length;
                }
            }
        }
    }
    return group[maxIndex]
}

console.log(fn([2, 1, 3, 6, 7, 9, 12, 15]))

四、二分查找

在下另一篇关于二分查找详细说明的记录

1. 例子1: 二分查找

力扣地址

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。升序,且没有要求返回所有,那么就可以使用二分查找,时间复杂度O(logn);普通的循环需要O(n);

思路
  1. 如果目标小于最小,大于最大,那么肯定不在数组中
  2. 数组可能是一个可能是多个,begin <= end时都继续处理,大于时候自动停止
  3. 目标等于中间值,返回中间值;
  4. 目标小于中间值,右值为中间减1;否则左值都为中间值加1(等于就不用判断了,最后都会处理成左中右都相等)
代码实现
var search = function(nums, target) {
    let begin = 0;
    let end = nums.length - 1;
    if(target<nums[0] || target>nums[end]){
        return -1
    }
    let middle;
    while(begin <= end){
        middle = Math.floor((end+begin)/2);
        if(nums[middle] === target){
            return middle
        }else if(target < nums[middle]){
            end = middle - 1
        }else{
            begin = middle + 1
        }
    }
    return -1
};

console.log(search([-1, 0, 3],2))

2. 例子二:第一个错误版本

力扣地址

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

这里注意一下,就是如果某个版本正确,那么它前面的版本都正确。如果版本有对错交叉就不能使用二分查找了(我刚开始就是考虑了这个,当前如果正确当然可以将前面都标识为正确)。

思路
  1. 由于当前版本就是错误,肯定在查找出错误版本。
  2. 当中间值是正确,那么从中间+1往后找
  3. 当中间值是错误,那么从前面往中间找
代码实现
let versionStatus = [true, false, false, true, true, false, false, false]

let solution = function(){
    let leftIndex = 0;
    let rightIndex = 7;
    let middle;
    while (leftIndex < rightIndex) {
        middle = Math.floor((leftIndex + rightIndex) / 2);
        if(versionStatus[middle]){
            leftIndex = middle + 1
        }else{
            rightIndex = middle
        }
    }
    return leftIndex
}

console.log(solution())

3. 例子3:搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。

思路
  1. 等于中间值直接返回
  2. 小于中间值左边找,rightIndex = middle - 1
  3. 大于中间值右边找,leftIndex = middle + 1
  4. 存在单个的数组,条件设置小等于
代码实现
var searchInsert = function(nums, target) {
    let leftIndex = 0;
    let rightIndex = nums.length - 1;
    let middle;
    while (leftIndex <= rightIndex) {
        middle = Math.floor((leftIndex + rightIndex) / 2);
        if(nums[middle] === target){
            return middle
        }else if(target < nums[middle]){
            rightIndex = middle - 1
        }else {
            leftIndex = middle + 1
        }
    }
    return leftIndex
};

console.log(searchInsert([4], 5))
console.log(searchInsert([4], 4))
console.log(searchInsert([4], 3))

kmp查找

直接上代码,整体思路就是当前找不到就从当前上一位已经匹配过的最长前缀后继续找。

    function getNext(arr) {
    let i = 1, j = 0, next = [j];
    while (i < arr.length) {
        if (arr[i] === arr[j]) {
            // 0-j和i-j到i这段相等,后续如果i+1不匹配,那么从j位置开始进行匹配
            next[i++] = ++j;
        } else {
            if (j !== 0) {
                j = next[j - 1];
            } else {
                next[i++] = 0;
            }
        }
    }
    return next;
}

function kmpSearch(str, pattern) {
    let next = getNext(pattern);
    let i = 0, j = 0, result = [];
    while (i < str.length) {
        if (str[i] === pattern[j]) {
            i++;
            j++;
        } else {
            if (j !== 0) {
                j = next[j - 1];
            } else {
                i++;
            }
        }
        if (j === pattern.length) {
            result.push(i - j);
        }
    }
    return result.length ? result : false;
}