704. 二分查找

180 阅读3分钟

算法-封面次图.png

题目来源:力扣(LeetCode) leetcode-cn.com/problems/bi…

1. 题目

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

解题思路:通过对比目标值 target 和数组的中间元素(二分查找)。

target = 中间元素:直接返回中间元素下标;

target > 中间元素:则继续使用二分查找的方法从右侧查找;

target < 中间元素:则继续使用二分查找的方法从左侧查找。

2. 代码实现:

class Solution {
    public int search(int[] nums, int target) {
        int pivot, left = 0, right = nums.length - 1;
        while (left <= right) {
            pivot = (right + left) / 2;
            if (nums[pivot] == target) {
                return pivot;
            }
            if (nums[pivot] > target)
                right = pivot - 1;
            else
                left = pivot + 1;

        }
        return -1;
    }
}

3. 复杂度分析

  • 时间复杂度:O (logN)
  • 空间复杂度:O (1)

4. 补充知识

4.1 时间复杂度和空间复杂度解释

  • 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。

  • 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。

4.1.1 时间复杂度

时间复杂度的表示方法:大 O 符号表示法,即 T(n)=O(f(n))

这个公式被称为:算法的渐进时间复杂度

f(n)表示每行代码的执行次数之和,而 O 表示正比例关系。来看一个具体的例子:

for (int i = 0; i < n; i++) {
    j = i;
    j++;
}

上面一共 4 行代码,我们假设每一行代码的执行时间是一样的,我们用 一颗粒时间 来表示。

第一行代码的执行耗时:n 个颗粒时间 第二行代码的执行耗时:n 个颗粒时间 第三行代码的执行耗时:n 个颗粒时间 第四行代码的执行耗时:是符号,暂时忽略

总的时间:T(n) = 3n * 颗粒时间。从这个耗时可以看出,总的执行时间是随着 n 的变化而变化,因此,我们可以简化一下这个算法的时间复杂度表示:T(n) = O(n)。

之所以可以简化,是因为大 O 符号表示法并不是用来表示算法真实的执行时间,它是用来表示代码执行时间的增长变化趋势的。

常见的时间复杂度量级有:

  • 常数阶O(1)
  • 对数阶O(logN)
  • 线性阶O(n)
  • 线性对数阶O(nlogN)
  • 平方阶O(n2n^2)
  • 立方阶O(n3n^3)
  • K次方阶O(nkn^k)
  • 指数阶(2n2^n)

上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。

4.1.2 空间复杂度

明白了时间复杂度,那么空间复杂度也是类似的。空间复杂度也不是用来计算程序实际占用的空间的。

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势。

空间复杂度比较常用的有:O(1)、O(n)、O(n²)

来看两个例子:

int i = 1;
int j = 2;
int sum = i + j;

代码里的 i 和 j 所分配的空间不会随着处理数据量而变化,因此它的空间复杂度 S(n) = O(1)。

第二个例子:

int[] m = new int[n];
for (int i = 0; i < n; i++) {
    j = i;
    j++;
}

在第一行代码中,通过 new 关键字创建了一个数组出来,数组 m 占用的大小为 n ,第 2 到第 5 行代码虽然是一个循环,但是没有再分配新的空间,因此,上述代码的空间复杂度是:S(n) = O(n)。