704. 二分查找
思路
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?
大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
下面我用这两种区间的定义分别讲解两种不同的二分写法。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left = 0;
let right = nums.length -1;
while(left <= right) {
const middle = (left + right) >> 1;
if (nums[middle] > target) {
right = middle - 1;
}
if (nums[middle] < target) {
left = middle + 1;
}
if (nums[middle] ===target) {
return middle;
}
}
return -1;
};
35. 搜索插入位置
思路说明:
- 定义两个指针 left 和 right,分别指向数组的开始和结尾。
- 在循环中,计算 mid 值: (left + right) / 2。
- 检查 nums[mid] 是否等于目标值 target。如果是,则返回 mid,表示找到了目标值的索引。
- 如果 nums[mid] 小于目标值 target,说明目标值在 mid 的右边。更新 left 为 mid + 1。
- 如果 nums[mid] 大于目标值 target,说明目标值在 mid 的左边。更新 right 为 mid - 1。
- 循环继续直到找到目标值或者 left > right。
- 如果最后没有找到目标值,返回 left。因为 left 会指向目标值插入的位置。
这种解法的时间复杂度是O(log n),其中 n 是数组的长度。因为每次循环都会将查找区间缩小一半,直到找到目标值或者区间为空。如果目标值存在于数组中,那么时间复杂度是O(log n)。如果目标值不存在于数组中,那么时间复杂度也是O(log n),因为最后会找到插入位置。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const middle = (left + right) >> 1;
if (nums[middle] > target) {
right = middle - 1;
}
if (nums[middle] < target) {
left = middle + 1;
}
if (nums[middle] === target) {
return middle
}
}
return left;
};
34. 在排序数组中查找元素的第一个和最后一个位置
const binarySearch = (nums, target, lower) => {
let left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
var searchRange = function(nums, target) {
let ans = [-1, -1];
const leftIdx = binarySearch(nums, target, true);
const rightIdx = binarySearch(nums, target, false) - 1;
if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] === target && nums[rightIdx] === target) {
ans = [leftIdx, rightIdx];
}
return ans;
}
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solution/zai-pai-xu-shu-zu-zhong-cha-zhao-yuan-su-de-di-3-4/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
69. x 的平方根
/**
* @description: 模拟法 TC:O(n) SC:O(1)
* @author: JunLiangWang
* @param {*} x 给定非负整数 x
* @return {*}
*/
function simulation(x){
/**
* 本方案使用模拟的方式,由于仅需要返回算术平方
* 根整数部分,因此我们只需要从1开始逐步递增找
* 到第一个其平方大于x的数,然后将该数-1即为答
* 案
*/
let root=1;
// 从1开始逐步递增,找到第一个其平方大于x的数
while(root*root<=x)root++;
// 将该数-1即为答案
return root-1;
}
/**
* @description: 二分法 TC:O(logn) SC:O(1)
* @author: JunLiangWang
* @param {*} x 给定非负整数 x
* @return {*}
*/
function binary(x){
/**
* 本方案使用二分法,上述模拟法是通过将根逐步递增1来逼近答案的,
* 因此存在优化空间,我们可以利用二分法不断在分割区间并做选择,
* 使其2倍增或2倍减的方式逼近答案
*/
// 初值化左指针为0
let left=0,
// 初值化右指针为x
right=x,
// 初始化中间值为0
middle=0
// 当左指针超出了右指针,遍历完成
while(left<=right){
// 计算中间值
middle=Math.floor((left+right)/2);
// 如果中间值的平方等于x,则为答案直接返回
if(middle*middle==x)return middle;
// 如果中间值的平方大于x,证明[middle,right]区间
// 的平方都是大于x的,因此舍去该区间,并重置为
// [left,middle-1]
else if(middle*middle>x)right=middle-1
// 如果中间值的平方小于x,证明[left,middle]区间
// 的平方都是小于x的,因此舍去该区间,并重置为
// [middle+1,right]
else left=middle+1
}
// 当遍历完成都未找到平方与x相等的答案,
// 此时返回left-1即可
return left-1;
}
/**
* @description: 牛顿迭代法 TC:O(logn) SC:O(1)
* @author: JunLiangWang
* @param {*} x
* @return {*}
*/
function newtonIteration(x){
/**
* 该方案使用牛顿迭代法,我们不断用(x,f(x))的切线来逼近x^2-a=0的根。
* 根号a其实就是x^2-a=0的一个正实根,这个函数的导数为2x。也就是说
* 函数上任意一点(x,f(x))的切线斜率为2x。那么,x-f(x)/(2x)就是一
* 个比x更接近的近似值。法如f(x)=x^2-a得到x-(x^2-a)/(2x),也就是
* (x+a/x)/2。
* */
if (x == 0) {
return 0;
}
let C = x, x0 = x;
while (true) {
let xi = 0.5 * (x0 + C / x0);
if (Math.abs(x0 - xi) < 1e-7) {
break;
}
x0 = xi;
}
return Math.floor(x0);
}
367. 有效的完全平方数
你可以使用二分查找的方法来解决这个问题。
首先,对于给定的整数num,你可以在范围[1, num]内进行二分查找,找到一个整数mid。然后,判断mid的平方是否等于num。如果是,则num是一个完全平方数,返回true。如果不是,判断mid的平方是否大于num,如果大于,则在范围[1, mid-1]内继续进行二分查找。如果小于,则在范围[mid+1, num]内继续进行二分查找。重复这个过程,直到找到一个完全平方数或者确定不存在完全平方数。
以下是使用JavaScript实现该算法的代码:
function isPerfectSquare(num) {
if (num === 1) {
return true;
}
let left = 1;
let right = num;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (mid ** 2 === num) {
return true;
} else if (mid ** 2 < num) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return false;
}
console.log(isPerfectSquare(16)); // 输出 true
console.log(isPerfectSquare(14)); // 输出 false