二分查找
1. 理论基础
- 前提条件:数组有序,且无重复元素(如果包含重复元素的话,那么二分查找法返回的下标元素可能不是唯一的)
- 对区间的定义:即左闭右闭[left, right],或者左闭右开[left, right)
2. 实践
704 二分查找
力扣题目链接
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
- 你可以假设 nums 中的所有元素是不重复的。
- n 将在 [1, 10000]之间。
- nums 的每个元素都将在 [-9999, 9999]之间。
代码如下:
Java版本如下:
class Solution {
public int search(int[] nums, int target) {
if (target < nums[0] || target > nums[nums.length - 1]) {
return - 1;
}
/*
// 第一种,区间左闭右闭
int left = 0, right = nums.length - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] > target)
// 已知nums[middle] > target 且 区间右闭,故如下:
right = middle - 1;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
*/
// 第二种,区间左闭右开
int left = 0, right = nums.length;
while (left < right) {
int middle = (left + right) / 2;
if (nums[middle] > target)
// 已知nums[middle] > target 且 区间右开,故如下:
right = middle;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return -1;
}
}
JavaScript版本如下:
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
/*
//第一种写法:左闭右闭
let left = 0,
right = nums.length - 1;
while (left <= right) {
let middle = Math.floor((left + right) / 2);
if (nums[middle] > target)
right = middle - 1;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return -1;
*/
//第二种写法:左闭右开
let left = 0,
right = nums.length;
while (left < right) {
let middle = Math.floor((left + right) / 2);
if (nums[middle] > target)
right = middle;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return -1;
};
Python3版本如下:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
return middle
return -1
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
35. 搜索插入位置
力扣题目链接
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例3:
输入: nums = [1,3,5,6], target = 7
输出: 4
提示:
- 1 <= nums.length <= 1 0 4 10^4 104
- - 1 0 4 10^4 104 <= nums[i] <= 1 0 4 10^4 104
- nums 为 无重复元素 的 升序 排列数组
- - 1 0 4 10^4 104<= target <= 1 0 4 10^4 104
代码如下:
Java版本如下:
class Solution {
public int searchInsert(int[] nums, int target) {
// 第一种,区间左闭右闭
int left = 0, right = nums.length - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] > target)
// 已知nums[middle] > target 且 区间右闭,故如下:
right = middle - 1;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return right + 1;
//分别处理以下四种情况(针对左闭右闭)
//目标值小于数组所有元素,return right + 1
//目标值被找到,return middle
//目标值大于数组所有元素,return right + 1
//目标值插入数组中的位置[left,right], return right + 1
/*
// 第二种,区间左闭右开
int left = 0, right = nums.length;
while (left < right) {
int middle = (left + right) / 2;
if (nums[middle] > target)
// 已知nums[middle] > target 且 区间右开,故如下:
right = middle;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return right;
*/
}
}
JavaScript版本如下:
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
//第一种写法:左闭右闭
let left = 0,
right = nums.length - 1;
while (left <= right) {
let middle = Math.floor((left + right) / 2);
if (nums[middle] > target)
right = middle - 1;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return right + 1;
//分别处理以下四种情况(针对左闭右闭)
//目标值小于数组所有元素,return right + 1
//目标值被找到,return middle
//目标值大于数组所有元素,return right + 1
//目标值插入数组中的位置[left,right], return right + 1
/*
//第二种写法:左闭右开
let left = 0,
right = nums.length;
while (left < right) {
let middle = Math.floor((left + right) / 2);
if (nums[middle] > target)
right = middle;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return right;
*/
};
Python3代码如下:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
return middle
return right + 1
36. 搜索插入位置
力扣题目链接
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例3:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
- 0 <= nums.length <= 1 0 5 10^5 105
- - 1 0 9 10^9 109 <= nums[i] <= 1 0 9 10^9 109
- nums 是一个非递减数组
- - 1 0 9 10^9 109<= target <= 1 0 9 10^9 109
代码如下:
Java版本如下:
方法一:
public class LeetCode34 {
/**
* 方法一:先判断目标值是否存在,如果存在,左右滑动指针,确定边界值
*/
public int[] searchRange(int[] nums, int target) {
int index = binarySearch(nums, target);
if (index == -1) {
return new int[] {-1, -1};
}
int left = index, right = index;
// 滑动左右指针,找到first and last
while (left - 1 >= 0 && nums[left - 1] == target) {
left--;
}
while (right + 1 < nums.length && nums[right + 1] == target) {
right++;
}
return new int[] {left, right};
}
/**
* @param nums
* @param target
* @Method binarySearch
* @Author Ami
* @Version 1.0
* @Description 二分查找,判断target是否存在
* @Return int
* @Exception
* @Date 2023/5/5 20:44
*/
public int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] > target) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
}
方法二:
/**
* 方法二:寻找target的左右边界,根据题意,则会存在三种情况
* 情况一:target在数组范围的左边或者右边,则返回【-1,-1】
* 情况二:target在数组范围内,但是在数组中不存在,也返回【-1,-1】 如nums = [5,7,7,8,8,10], target = 6
* 情况三:target存在于数组中,
*/
public int[] searchRange(int[] nums, int target) {
int rightBorder = getRightBorder(nums, target);
int leftBorder = getLeftBorder(nums, target);
// 情况一
if (rightBorder == -2 || leftBorder == -2) {
return new int[]{-1, -1};
}
// 情况三
if (rightBorder - leftBorder > 1) {
return new int[]{leftBorder + 1, rightBorder - 1};
}
// 情况二: 如果target不存在于数组nums中,那么其左右边界之差一定为1
return new int[]{-1, -1};
}
/**
* @param nums
* @param target
* @Method getRightBorder
* @Author Ami
* @Version 1.0
* @Description 得到target在数组中的右边界,需要注意的是,如果其值不等于-2,那么这个右边界的值比target在数组中的最右边索引值大1
* @Return int
* @Exception
* @Date 2023/5/6 9:16
*/
public int getRightBorder(int[] nums, int target) {
int left = 0, right = nums.length - 1;
/**
* 初始化右边界为-2,意味着开始不存在
* 示例:nums = [5,7,7,8,8,10], target = 8
*/
int rightBorder = -2;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
/**
* @param nums
* @param target
* @Method getLeftBorder
* @Author Ami
* @Version 1.0
* @Description 得到target在数组中的左边界,需要注意的是,如果其值不等于-2,那么这个左边界的值比target在数组中的最左边索引值小1
* @Return int
* @Exception
* @Date 2023/5/6 9:25
*/
public int getLeftBorder(int[] nums, int target) {
int leftBorder = -2;
int left = 0, right = nums.length - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] < target) {
left = middle + 1;
} else {
right = middle - 1;
leftBorder = right;
}
}
return leftBorder;
}
JavaScript版本如下:
方法一:
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let index = binarySearch(nums, target);
if (index == -1)
return [-1, -1];
let left = right = index;
while (left - 1 >= 0 && nums[left - 1] == target) {
left--;
}
while (right + 1 < nums.length && nums[right + 1] == target) {
right++;
}
return [left, right];
};
function binarySearch(nums, target) {
let left = 0, right = nums.length - 1;
while (left <= right) {
let middle = Math.floor((left + right) / 2);
if (nums[middle] > target)
right = middle - 1;
else if (nums[middle] < target)
left = middle + 1;
else
return middle;
}
return -1;
}
方法二:
let getRightBorder = function(nums, target) {
let rightBorder = -2;
let left = 0, right = nums.lenght - 1;
while (left <= right) {
let middle = Math.floor((left + right) / 2);
if (nums[middle] > target)
right = middle - 1;
else {
left = middle + 1;
rightBorder = left
}
}
return rightBorder;
}
let getLeftBorder = function(nums, target) {
let leftBorder = -2;
let left = 0, right = nums.lenght - 1;
while (left <= right) {
let middle = Math.floor((left + right) / 2);
if (nums[middle] < target)
left = middle + 1;
else {
right = middle - 1;
leftBorder = right;
}
}
return leftBorder;
}
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let leftBorder = getLeftBorder(nums, target),
rightBorder = getRightBorder(nums, target);
if (leftBorder == -2 || rightBorder == -2)
return [-1, -1];
if (rightBorder - leftBorder > 1)
return [leftBorder + 1, rightBorder - 1];
return [-1, -1];
};
Python3代码如下:
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
indexOfTarget = self.getIndexOfTarget(nums, target)
if indexOfTarget == -1:
return [-1, -1]
else:
left = right = indexOfTarget
while left >= 0 and nums[left] == target:
left -= 1
while right < len(nums) and nums[right] == target:
right += 1
return [left + 1, right - 1]
def getIndexOfTarget(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
return middle
return -1
69.x的平方根
力扣题目链接
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例1:
输入:x = 4
输出:2
示例2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
- 0 <= x <= 2 32 − 1 2^{32}-1 232−1
代码如下:
Java版本如下:
public class LeetCode69 {
public int mySqrt(int x) {
int left = 0,
right = x,
ans = -1;
while (left <= right) {
int middle = (left + right) / 2;
if ((long) middle * middle <= x) {
left = middle + 1;
ans = middle;
} else {
right = middle - 1;
}
}
return ans;
}
}
JavaScript版本如下:
/**
* @param {number} x
* @return {number}
*/
let mySqrt = function(x) {
let left = 0,
right = x,
ans = -1;
while (left <= right) {
let middle = Math.floor((left + right) / 2);
if (middle * middle <= x) {
ans = middle;
left = middle + 1;
} else {
right = middle - 1;
}
}
return ans;
};
Python版本代码如下:
class Solution:
def mySqrt(self, x: int) -> int:
left, right, ans = 0, x, -1
while left <= right:
middle = (left + right) // 2
if middle ** 2 <= x:
left = middle + 1
ans = middle
else:
right = middle - 1
return ans
367.有效的完全平方数
力扣题目连接
给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt 。
示例1:
输入:num = 16
输出:true
解释:返回 true ,因为 4 * 4 = 16 且 4 是一个整数。
示例2:
输入:num = 14
输出:false
解释:返回 false ,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。
提示:
- 1 <= num <= 2 31 − 1 2^{31} - 1 231−1
Java代码如下:
public class LeetCode367 {
public boolean isPerfectSquare(int num) {
int left = 1,
right = num;
while (left <= right) {
int middle = (left + right) / 2;
if ((long) middle * middle < num) {
left = middle + 1;
} else if ((long) middle * middle > num) {
right = middle - 1;
} else {
return true;
}
}
return false;
}
}
Python版本代码如下:
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left, right = 0, num
while left <= right:
middle = (left + right) // 2
if middle ** 2 < num:
left = middle + 1
elif middle ** 2 > num:
right = middle - 1
else:
return True
return False