查找算法类题型
1.二分查找
二分法搜算步骤:预处理,对集合进行排序;二分查找,使用循坏或者递归再每次比较厚将查找空间分为两半;后处理,在剩余空间中确定可行的候选者;
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];
}