前端-查找算法系列刷题笔记

 

查找算法类题型

算法学习参考

1.二分查找

leetcode参考

二分法搜算步骤:预处理,对集合进行排序;二分查找,使用循坏或者递归再每次比较厚将查找空间分为两半;后处理,在剩余空间中确定可行的候选者;

Question1:   简述二分查找算法与时间复杂度,并实现一个二分查找算法!

如:[2,3,0,9] 在这个数组中查找9,则应该返回下标索引3;

Answer1:  对要查找的数组按从小到大排好序,然后找到中间点;比对中间点和要找的值的大小,如果找的值大于中间值,则在上半部分数组查找,反之;以此循坏直到上下界限相等;没找到就返回-1;

Code1:  Math.floor向下取整的意思  sort()要传入回调函数,指定是从小到大排序还是从大到小排序  时间复杂度O(logn) 空间复杂度O(1)

//二分法查找
function binarySearch(items, item) {
    var low = 0,
        hight = items.length - 1,
        elem, mid;
    while (low <= hight) {
        mid = Math.floor((low + hight) / 2);
        elem = items[mid];
        if (elem < item) {
            low = mid + 1;
        } else if (elem > item) {
            hight = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
};
//测试
var arr = [2, 3, 0, 9];
//使用自带的排序sort 
arr.sort((a,b)=>a-b);
console.log(arr); //[0.2,3,9]
var result = binarySearch(arr, 9);
console.log(result); //3

Question2:   用二分法查找x的平方根??

Answer2:  数组x的平方根不会超过x/2所以只需要在1-x/2之间查找平方根;有的说mid用left+(right-left)/2是因为防止溢出问题;

Code2: 时间复杂度O(logn)n=x/2,但是经过数学化解之后logn 就是 -1+logn ,所以呢时间复杂度就是O(logn)空间复杂度O(1)

var mySqrt = function(x) {
    if (x < 2) return x;
    let left = 1,
        right = Math.floor(x / 2),
        mid;
    while (left <= right) {
        mid = Math.floor(left + (right - left) / 2);
        if (x / mid < mid) {
            right = mid - 1;
        } else if (x / mid > mid) {
            left = mid + 1;
        } else {
            return mid;
        }
    };
    return right;
}

Question3:   用二分法实现猜数字游戏,每轮游戏从1-n随机选择数字,让我们程序去猜它选的哪个数字,搞了半天终于懂了这个题说的啥意思了,是让我写代码去实现猜准这个数字,噗??

可以调用预定义好的guess(num)函数获取猜测结果,猜大了返回-1,猜小了返回1,猜对了返回的是0;

Answer3:  利用二分法的思想从1-n之间去查找选定的数字是哪个,通过返回值来判断是应该向下还是向上半部分查找;

Code3: 时间复杂度O(logn)  空间复杂度O(1)

var guessNumber = function(n) {
    let left = 1,
        right = n,
        mid;
    while (left <= right) {
        mid = Math.floor(left + (right - left) / 2);
        let returnValue = guess(mid);
        if (returnValue == -1) {
            right = mid - 1;
        } else if (returnValue == 1) {
            left = mid + 1;
        } else {
            return mid;
        }
    }
}

Question4:   搜索旋转排序数组,升序排列的数组nums在某个点上进行了旋转,比如[0,1,2,4,5,6,7]旋转后为[4,5,6,7,0,1,2]; 在数组中搜索target,存在返回它的索引,不存在返回-1;

Answer4:  可以观察到旋转之后有一部分有序,有一部分可能无序,所以可以先判断有序无序nums[left]<=nums[midIndex]表示前部分有序,反之后部分有序;然后在有序里面判断是否存在这个值,要是不存在就在无序部分搜索;以此循坏知道left>right

Code4: 平均时间复杂度O(logn),空间复杂度O(1)

var search = function(nums, target) {
    let left = 0,
        right = nums.length - 1,
        midIndex;
    while (left <= right) {
        midIndex = Math.floor(left + (right - left) / 2);
        if (nums[midIndex] == target) {
            return midIndex;
        }
        if (nums[left] <= nums[midIndex]) {
            if (target >= nums[left] && target <= nums[midIndex]) {
                right = midIndex - 1;
            } else {
                left = midIndex + 1;
            }
        } else {
            if (target >= nums[midIndex] && target <= nums[right]) {
                left = midIndex + 1;
            } else {
                right = midIndex - 1;
            }
        }
    }
    return -1;
};

2021.01.12

Question5:   第一个错误版本,有n个版本[1,2,3,....,n],,有一个版本错了则后面的都错,我们需要编程找到导致之后所有版本出错的第一个版本;如:给定n=5,version=4是第一个出错版本;isBadVersion()是预先定义好的;

Answer5:  根据题目意思的话我们可以用二分法的第二个模板来做;先是判断mid的状态,如果mid错误了那就向左查找,错误版本肯定在mid之前或者就是mid;mid是正确的那就像右查找;

Code5: 平均时间复杂度O(logn)  空间复杂度O(1)  

var solution = function(isBadVersion) {
    /**
     * @param {integer} n Total versions
     * @return {integer} The first bad version
     */
    return function(n) {
        if (n == 0) return -1;
        let left = 1,
            right = n,
            mid;
        while (left < right) {
            mid = Math.floor(left + (right - left) / 2);
            if (isBadVersion(mid)) {
                //向左查找
                right = mid;
            } else {
                //向右查找
                left = mid + 1;
            }
        }
        if (isBadVersion(left)) return left;
        return -1;
    };
};

Question6:   题目 峰值元素是指其值大于左右相邻值的元素;给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。假设 nums[-1] = nums[n] = -∞

Answer6:  根据题目的假设,我们可以用中位mid值处于上坡还是下坡去判断,要是处于上坡就往右边查找,处于下坡往左边去查找;

Code6: 平均时间复杂度O(logn)  空间复杂度O(1)  

var findPeakElement = function(nums) {
    if(nums.length==1) return 0;
let left = 0,
        right = nums.length,
        midIndex;
    while (left < right) {
        midIndex = Math.floor(left + (right - left) / 2);
        if (nums[midIndex] > nums[midIndex - 1] && nums[midIndex] > nums[midIndex + 1])
            return midIndex;
        if (nums[midIndex] < nums[midIndex + 1]) {
            //上坡在右边查找
            left = midIndex + 1;
        } else {
            right = midIndex;
        }
    }
    if(left!=nums.length)return left;
    return -1;
};

Question7:   题目 寻找旋转排序数组中得最小值,假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2]

Answer7:  解答参考  用二分法去做,先判断数组有没有旋转,没有旋转当然就是第一个最小了(升序得情况下啦),有旋转的话也是一部分有序一部分无序,我们在无序那部分找最小值,因为旋转是把大的旋转到前面来了,根据解答参考去思考;

Code7: 平均时间复杂度O(logn)  空间复杂度O(1)  

/**
 * @param {number[]} nums
 * @return {number}
 */
var findMin = function(nums) {
    if(nums.length==1)return nums[0];
    let left=0,right=nums.length-1,midIndex;
    if(nums[left]<nums[right])return nums[left];
    while(left<right){
        midIndex=Math.floor(left+(right-left)/2);
        if(nums[midIndex-1]>nums[midIndex])return nums[midIndex];
        if(nums[midIndex]>nums[midIndex+1])return nums[midIndex+1];
        if(nums[left]<nums[midIndex]){
            left=midIndex+1;
        }else{
            right=midIndex;
        }
    }
    return -1;
};

2021.01.13

Question8:   在排序数组中查找元素的第一个和最后一个;给出目标值,查找目标值在数组中的开始位置和结束位置;题目

Answer8:   总共是分为两步:使用第三个模板求解 解答参考 

1.先用二分法查找target开始位置;
在判断查找区间向左还是向右之前,先判断中间位置是否符合要求,找target开始位置的时候,如果mid位置==target而且mid前一个位置又不等于target那mid自然就是它的起始位置了;然后再常规判断,mid位置>=target,那起始位置肯定在左边,所以呢向左查找,反之;

2.再用二分法查找target结束位置;
跟第一步的思想一致,先判断中间mid位置是不是结束位置,然后再常规判断向左还是向右查找;如果mid位置<=target那结束位置肯定在右边,向右查找;

Code8: 平均时间复杂度O(logn)  空间复杂度O(1)  

var searchRange = function(nums, target) {
    if (nums.length == 0) return [-1, -1];
    var startIndex = startPosition(nums, target);
    var endIndex = endPosition(nums, target);
    return [startIndex, endIndex];
}

var startPosition = function(nums, target) {
    if (nums[0] == target) return 0;
    var left = 0,
        right = nums.length - 1,
        mid;
    while (left + 1 < right) {
        mid = Math.floor(left + (right - left) / 2);
        if (nums[mid] == target && nums[mid - 1] != target) return mid;
        if (nums[mid] >= target) {
            //向左查找
            right = mid;
        } else {
            left = mid;
        }
    }
    if (nums[left] == target) return left;
    if (nums[right] == target) return right;
    return -1;
};
var endPosition = function(nums, target) {
    if (nums[nums.length - 1] == target) return nums.length - 1;
    var left = 0,
        right = nums.length - 1,
        mid;
    while (left + 1 < right) {
        mid = Math.floor(left + (right - left) / 2);
        if (nums[mid] == target && nums[mid + 1] != target) return mid;
        if (nums[mid] <= target) {
            //向右查找
            left = mid;
        } else {
            right = mid;
        }
    }
    if (nums[left] == target) return left;
    if (nums[right] == target) return right;
    return -1;
}

2021.01.14

Question9:   给定一个排好序的数组arr,两个整数k和x,从数组中找到最靠近x的k个数;返回的结果必须要是按升序排列的。题目

Answer9:   主要采用二分法的思想:综合前面的案例几个模板灵活运用,有自己的思想;不一定按照模板来;一定要自己分析;参考解答  自己题解

  • 确定二分查找的上下限,left=0,right=arr.length-k;
  • 首先是对x小于数组第一个元素或者大于最后一个元素的判断,这两种情况都比较好写,直接取前个或者后k个,因为数组本身就是有顺序的;
  • 这里只找开始位置,所以,mid位置<=mid+k位置的值时向左查找right=mid;mid位置>mid+k位置的值的时候向右查找,left=mid+1;

Code9:  平均时间复杂度O(log(n+k))  n应该是arr.length-k 那个while循坏次数  k是for循坏次数   O(1)

var findClosestElements = function(arr, k, x) {
    var start, j = 0,
        arrs = [];
    if (arr[0] >= x) {
        start = 0;
    } else if (arr[arr.length - 1] <= x) {
        start = arr.length - k
    } else {
        start = startIndex(arr, k, x);
    }
    console.log(start);
    for (var i = start; i < start + k; i++) {
        arrs[j++] = arr[i];
    }
    return arrs;
}
var startIndex = function(arr, k, x) {
    var left = 0,
        right = arr.length - k,
        mid, endIndex = arr.length - k;
    while (left < right) {
        mid = Math.floor(left + (right - left) / 2);
        endIndex = (mid + k) > arr.length - 1 ? arr.length - 1 : mid + k;
        if (Math.abs(arr[mid] - x) <= Math.abs(arr[endIndex] - x)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left;
}

Question10:  就是Qusestion6

Answer10:


2021.01.17

Question11:  实现pow(x,n)???  题目 

Answer11:  有两种方法,第一种是分治的思想,解答参考1 解答参考2

分治的思想,就是x可以分解如下:

第二种思想就是二分的思想:n用二进制表示,然后呢用这个二进制表示的n次方去实现x的n次方;

Code11: 分治思想的平均时间复杂度为O(logn)  空间复杂度为O(logn);  二分法思想平均时间复杂度为O(logn),空间复杂度为O(1);

//分治思想
var myPow = function(x, n) {
    //分治方法-递归方法
    if (n == 0) return 1;
    if (n == 1) return x;
    return n > 0 ? quickMul(x, n) : 1 / quickMul(x, -n);
}
var quickMul = function(x, n) {
    if (n == 0) return 1;
    var y = quickMul(x, n);
    if (n % 2 == 1) {
        y = y * y * x;
    } else {
        y = y * y;
    }
    return y;
}
//二分的思想
var myPow = function(x, n) {
        if (n == 0) return 1;
        if (n == 1) return x;
        return n > 0 ? binary(x, n) : 1 / binary(x, -n);
    }
var binary = function(x, n) {
    var result = 1,
        x_contribute = x;
    while (n > 0) {
        if (n % 2 != 0) {
            result = result * x_contribute;
        }
        x_contribute = x_contribute * x_contribute;
        n = Math.floor(n / 2);
    }
    return result;
}

Question12:  给定一个正整数num,编写一个函数,如果num是一个完全平方数返回true,否则返回false;???题目

Answer12: 平方根不会超过num/2,所以呢在1-num/2之间使用二分法查找;解答

Code12:平均时间复杂度O(logn)  n=num/2,空间复杂度O(1)

var isPerfectSquare = function(num) {
    // console.log(mySqrt(num));
    var mysqrt = mySqrt(num);
    if (mysqrt == -1) {
        return false;
    } else {
        return true;
    }
}
var mySqrt = function(num) {
    if (num < 0) return -1;
    if (num == 0) return 0;
    var left = 1,
        right = Math.floor(num / 2),
        mid;
    while (left < right) {
        mid = Math.floor(left + (right - left) / 2);
        if (mid * mid == num) return mid;
        if (num / mid < mid) {
            //向左侧查找
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    if (left * left == num) return left;
    return -1;
}

Question13:  寻找比目标字母大的最小字母???简单 题目

Answer13:  用二分法去做,属于简单类型的二分思想;

我用的第二种模板,left<right的思路,如果midIndex处的字母<=target的字母就向右侧去查找,反之向左侧查找,为了找到比target大的最小的字母,所以需要查找到left=right为止,在第一次找到大于target之后就需要向左收缩右边界直到left=right;  解答

Code13:平均时间复杂度O(logn)  n为letters长度  空间复杂度O(1);

var nextGreatestLetter = function(letters, target) {
    var left = 0,
        right = letters.length - 1,
        midIndex;
    while (left < right) {
        midIndex = Math.floor(left + (right - left) / 2);
        if (letters[midIndex] <= target) {
            left = midIndex + 1;
        } else {
            right = midIndex;
        }
    }
    if (letters[left] > target) return letters[left];
    return letters[0];
}

2. 顺序查找

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值