算法--数组--二分查找
给定一个 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
示例 3:
输入: nums = [-1,0,3,5,9,12], target = 13
输出: -1
解释: 13 不存在 nums 中因此返回 -1
提示:
- 你可以假设
nums中的所有元素是不重复的。 n将在[1, 10000]之间。nums的每个元素都将在[-9999, 9999]之间。
1 解答
先写再看噢~
解答
这个讲得很详细 👉代码随想录
二分法很好理解,和中间值比较,小的在左边,大的在右边,获得一个新的区间,在这个新的区间继续比较。
想着容易,一写就乱
- 如果比最小值小,比最大值大
- 如果正好在两端
- 如果数组就两个数,中间值取整后,一直等于最小值,死循环了
- 什么情况开始循环,什么情况终止循环
要这样写吗?👇
if (target < nums[left] || target > nums[right]) {
return -1;
} else if (target === nums[left]) {
return left;
} else if (target === nums[right]) {
return right;
} else if (right - left <= 1) return -1;
实际不用单列出以上情况判断,直接用数学的区间去理解,通过判断区间是否有效,来判断是否终止循环
1.1 [left, right]
JavaScript
/**
* 二分查找, 返回 target 在 有序(升序)数组 nums 中的下标 (不使用数组的一些实例方法)
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function (nums, target) {
let left = 0;
let right = nums.length - 1;
let middle = 0;
while (left <= right) {
// 当 left == right,区间[left, right]依然有效,所以用 <=
middle = parseInt((left + right) / 2);
if (target === nums[middle]) {
return middle; // 数组中找到目标值,直接返回下标
} else if (target < nums[middle]) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
}
}
return -1;
};
c++
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
1.2 [left, right)
JavaScript
/**
* 二分查找, 返回 target 在 有序(升序)数组 nums 中的下标 (不使用数组的一些实例方法)
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function (nums, target) {
let left = 0;
let right = nums.length; // [left, right) 所以 right 要比实际下标多 1
let middle = 0;
while (left < right) {
// 当 left == right,区间[left, right) 无效,所以用 <
middle = parseInt((left + right) / 2);
if (target === nums[middle]) {
return middle; // 数组中找到目标值,直接返回下标
} else if (target < nums[middle]) {
right = middle; // target 在左区间,所以[left, middle)
} else {
left = middle + 1; // target 在右区间,所以[middle , right)
}
}
return -1;
};
c++
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
1.3 时间复杂度
以 [left, right] 为例
时间复杂度主要看 while 循环中的内容,
// 这三行执行次数与数组长度无关,作为常数,不在时间复杂度的考虑范围
let left = 0;
let right = nums.length;
let middle = 0;
// while 中的代码,每次循环执行两行,作为系数,不在时间复杂度的考虑范围
middle = parseInt((left + right) / 2);
if (target === nums[middle]) {
return middle;
} else if (target < nums[middle]) {
right = middle;
} else {
left = middle + 1;
}
因此主要看循环了几次,考虑一般情况,长度为 n 的数组,循环几次,停止循环
[left, right]->[left, mid1]->[left, mid2]->...->[left,left]
区间长度一直截半,什么时候等于 1
2^x = n ,所以 x = log2n = log n (去掉系数)
因此时间复杂度为 o(log n)
1.4 空间复杂度
let left = 0;
let right = nums.length - 1;
let middle = 0;
while (left <= right) {
// 当 left == right,区间[left, right]依然有效,所以用 <=
middle = parseInt((left + right) / 2);
if (target === nums[middle]) {
return middle; // 数组中找到目标值,直接返回下标
} else if (target < nums[middle]) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
}
}
return -1;
需要腾出位置储存变量 left,right,middle , 这三个变量都是整数,需要三个可以存放 整数的空间,储存空间与数组长度无关,因此空间复杂度为 o(1)