算法--数组--二分查找

62 阅读4分钟

算法--数组--二分查找

力扣题目--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

示例  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)