究竟是什么样的讲解二分查找算法的博客让我写了三小时???

news2024/12/23 10:12:59

版本说明

当前版本号[20230926]。

版本修改说明
20230926初版

目录

文章目录

  • 版本说明
  • 目录
  • 二分查找
    • 基础版
      • 算法描述
      • 分步演示
        • 情况一:能在有序数组找到待查值
        • 情况二:不能在有序数组找到待查值
      • 翻译成代码
      • 基础版代码(包括测试类)
      • 疑惑解答
      • 基础版改良后代码
    • 进阶版
      • 改动地方
        • 改动一:i 跟 j 的边界位置
        • 改动二:while 的条件
        • 改动三:if 判断中的 j 的边界问题
      • 改动后代码
      • 分步演示
      • 改动地方的解释
        • i 跟 j 的边界位置改动原因
        • while 的条件改动原因
        • if 判断中的 j 的边界问题改动原因
    • 衡量算法好坏
    • 平衡版
    • 算法题实战:力扣704 . 二分查找

二分查找

二分查找算法也称折半查找,是一种非常高效的工作于有序数组的查找算法。后续的课程中还会学习更多的查找算法,但在此之前,不妨用它作为入门。

基础版

需求:在有序数组 A A A 内,查找值 t a r g e t target target

  • 如果找到返回索引
  • 如果找不到返回 − 1 -1 1

算法描述

前提给定一个内含 n n n 个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ ⋯ ≤ A n − 1 A_{0}\leq A_{1}\leq A_{2}\leq \cdots \leq A_{n-1} A0A1A2An1,一个待查值 t a r g e t target target
1设置 i = 0 i=0 i=0 j = n − 1 j=n-1 j=n1
2如果 i > j i \gt j i>j,结束查找,没找到
3设置 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) m m m 为中间索引, f l o o r floor floor 是向下取整( ≤ i + j 2 \leq \frac {i+j}{2} 2i+j 的最小整数)
4如果 t a r g e t < A m target < A_{m} target<Am 设置 j = m − 1 j = m - 1 j=m1,跳到第2步
5如果 A m < t a r g e t A_{m} < target Am<target 设置 i = m + 1 i = m + 1 i=m+1,跳到第2步
6如果 A m = t a r g e t A_{m} = target Am=target,结束查找,找到了

分步演示

情况一:能在有序数组找到待查值

1、给定一个有序数组,并且在其下面标上下标。

image-20230924200302292

2、设置一个i值和一个j值,i从数组第0号元素左边开始检索,j从数组最后一个元素右边开始检索。

image-20230924200522320

3、设定一个可以在数组中能找到的数值:36 作为待查值target。接着开始二分查找。首先找第一次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(0+7)/ 2 = 3.5 ,向下求值后可得m=3。

image-20230924200916782

4、**判断第一次中间索引与待查值的大小。**发现 m 所对应的数为 28,小于待查值 36.就将 i 设置成 m+1 .

image-20230924201345251

5、接着找第二次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(4+7)/ 2 = 5.5 ,向下求值后可得m=5。

image-20230924201832748

6、**判断第二次中间索引与待查值的大小。**发现 m 所对应的数为 38,大于待查值 36.就将 j 设置成 m-1 .

image-20230924201947239

7、接着找第三次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(4+4)/ 2 = 4 .

image-20230924202028023

8、**判断第三次中间索引与待查值的大小。**发现 m 所对应的数为 36,等于待查值 36.到这里我们就通过二分查找找到了待查值。

情况二:不能在有序数组找到待查值

1、给定一个有序数组,并且在其下面标上下标。【与上同】

image-20230924200302292

2、设置一个i值和一个j值,i从左边开始检索,j从右边开始检索。【与上同】

image-20230924200522320

3、设定一个可以在数组中能呗找到的数值:26 作为待查值target。接着开始二分查找。首先找第一次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(0+7)/ 2 = 3.5 ,向下求值后可得m=3。【与上同】

image-20230924200916782

4、**判断第一次中间索引与待查值的大小。**发现 m 所对应的数为 28,大于待查值 26.就将 j 设置成 m-1 .

image-20230924202629285

5、接着找第二次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(0+2)/ 2 = 1 .

image-20230924202733897

6、**判断第二次中间索引与待查值的大小。**发现 m 所对应的数为 12,小于待查值 36.就将 i 设置成 m+1 .

image-20230924202841148

7、接着找第三次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(2+2)/ 2 = 2 .

image-20230924202916780

8、**判断第三次中间索引与待查值的大小。**发现 m 所对应的数为 20,小于待查值 36.我们再次将 i 设置成 m+1 .

image-20230924203054479

9、到这里我们能发现 i 已经大于 j ,能够证明我们找不到了,到此结束查找.

P.S.

  • 对于一个算法来讲,都有较为严谨的描述,上面是一个例子
  • 后续讲解时,以简明直白为目标,不会总以上面的方式来描述算法

翻译成代码

以:情况一:能在有序数组找到待查值 进行演示

1、给定一个有序数组。

【这一步可以通过测试代码进行测试、输出,放到后面去详细解释】

image-20230924200302292

2、设置一个i值和一个j值,i从左边开始检索,j从右边开始检索。

image-20230924200522320

这句话可以翻译成以下代码:

int i = 0;
int j = a.length - 1;

3、设定一个可以在数组中能找到的待查值target。接着开始二分查找。首先找中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 。在java中, int m = (i+j) / 2 ; 会自动地向下取整

image-20230924200916782

这句话可以翻译成以下代码:

while(i <= j)   //只有在i <= j中,才可以通过二分查找找到数值
        {
            int m = (i+j) / 2 ;
        }
return -1//当i > j中,就证明无法找到了

4、**判断中间索引与待查值的大小。**发现 m 小于待查值就将 **i 设置成 m+1 .**若发现 m 大于待查值 36.就将 j 设置成 m-1 .如果发现 m 等于待查值就证明找到了直接返回m值即可。

image-20230924201345251

这句话可以翻译成以下代码:

if(a[m] > target) //目标待查值在m的左边
            {
                j = m - 1;
            }
            else if (a[m] < target)//目标待查值在m的右边
            {
                i = m + 1;
            }
            else //目标待查值等于m,证明找到了
            {
                return m;
            }

基础版代码(包括测试类)

本代码仍然可以正常运行,不过如果想有更好更优化的代码可以向下继续观看。

public class 二分查找 {
    public static int binarySearch(int[] a, int target) {
        int i = 0;
        int j = a.length - 1;
        while(i <= j)   //只有在i <= j中,才可以通过二分查找找到数值
        {
            int m = (i+j) / 2 ; //这里有点小问题,但不影响代码运行,具体可以跳到《疑惑解答》去了解
            if(a[m] > target) //目标待查值在m的左边
            {
                j = m - 1;
            }
            else if (a[m] < target)//目标待查值在m的右边
            {
                i = m + 1;
            }
            else //目标待查值等于m,证明找到了
            {
                return m;
            }
        }
        return -1;  //当i > j中,就证明无法找到了
    }
}

同时你也可以自己写一个junit测试类,来测试这段代码是否能正常运行:

package SuanFa.test;

import org.junit.jupiter.api.DisplayName;

import SuanFa.第一章_初始算法.数组.二分查找;

import static org.junit.jupiter.api.Assertions.*;

class 二分查找Test {

    @org.junit.jupiter.api.Test
    @DisplayName("binarySearch 找到!")
    void testbinarySearch() {
        int[] a = {7, 13, 24, 45, 66, 82, 94};
        assertEquals(0,二分查找.binarySearch(a, 7));
    }
}

然后发现测试是正常可运行的,证明我们代码暂时没有出现问题。

image-20230924214257470

疑惑解答

问:那为什么是i<=j 意味着区间内有未比较的元素,而不是 i<j ?
答:因为 i , j 它们指向的元素也会参与比较

问:使用 (i+j) / 2 会不会出现问题呢?

答:第一段代码中,计算m的方式是(i+j) / 2,这是整数除法,会直接舍去小数部分。这种方式在m恰好是两个整数的中间值时可能会导致问题,因为如果i和j都是奇数,那么m就会偏向于左边,导致可能无法找到目标值。

​ 所以我们可以进行一个改良,把计算m的方式改成(i + j) >>> 1,这是无符号右移操作,可以保证无论i和j的值如何,m都会取到它们中间位置的整数。这种方式可以避免上述问题。

基础版改良后代码

public static int binarySearch(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1; // 可以避免m找不到中间值的问题
        if (target < a[m]) {			// 在左边
            j = m - 1;
        } else if (a[m] < target) {		// 在右边
            i = m + 1;
        } else {
            return m;
        }
    }
    return -1;
}

进阶版

进阶版相对于基础版,会进行三个地方的改动:

改动地方

改动一:i 跟 j 的边界位置

不希望j指的区域参与比较运算

int i = 0;
int j = a.length;
改动二:while 的条件
 while(i < j)   
        {
         	……
        }
改动三:if 判断中的 j 的边界问题
if(a[m] > target) 
            {
                j = m;
            }

改动后代码

package SuanFa.第一章_初始算法.数组;

public class 二分查找 {
    public static int binarySearch(int[] a, int target) {
        int i = 0;
        int j = a.length;
        while(i < j)   
        {
            int m = (i+j) >>> 1 ;
            if(a[m] > target)
            {
                j = m;
            }
            else if (a[m] < target)
            {
                i = m + 1;
            }
            else 
            {
                return m;
            }
        }
        return -1;
    }
}

分步演示

1、给定一个有序数组,并且在其下面标上下标。并设置一个i值和一个j值,i从数组第0个元素开始向左检索,j从外界区域开始向右检索。

image-20230925202920142

2、设定一个可以在数组中能找到的数值:26 作为待查值target。接着开始二分查找。首先找第一次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(0+8)>>> 1 = 4

image-20230925202857443

3、**判断第一次中间索引与待查值的大小。**发现 m 所对应的数为 33,大于待查值 26.就将 j 设置成 m .

image-20230925202356575

4、接着找第二次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(0+4)>>> 1= 2.

image-20230925202740920

5、**判断第二次中间索引与待查值的大小。**发现 m 所对应的数为 20,小于待查值 26.就将 i 设置成 m+1 .

image-20230925203238599

6、接着找第三次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(3+4)>>> 1 = 3 .(向下求值)

image-20230925203402918

7、**判断第三次中间索引与待查值的大小。**发现 m 所对应的数为 26,等于待查值 26.到这里我们就通过二分查找找到了待查值。

改动地方的解释

i 跟 j 的边界位置改动原因

改动原因:不希望 j 指的区域参与比较运算,j 只需要作为 a.length ,下标为8 的元素即可

image-20230925202920142

while 的条件改动原因

改动原因:i 的区域是要参与比较运算的,而 j 不需要。如果出现等于号,就有可能出现 i 带着 j 一起进行运算了。

注:当仅修改了 j = a.length 的条件,却没有改动 while 中 i <= j ,在查找数组中不存在的元素的话,就会发生死循环!

示例:

1、给定一个有序数组,并且在其下面标上下标。并设置一个i值和一个j值,i从数组第0个元素开始向左检索,j从外界区域开始向右检索。

image-20230925202920142

2、设定一个可以在数组中找不到的数值:27 作为待查值target。接着开始二分查找。首先找第一次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(0+8)>>> 1 = 4

image-20230925202857443

3、**判断第一次中间索引与待查值的大小。**发现 m 所对应的数为 33,大于待查值 27.就将 j 设置成 m .

image-20230925202356575

4、接着找第二次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(0+4)>>> 1= 2.

image-20230925202740920

5、**判断第二次中间索引与待查值的大小。**发现 m 所对应的数为 20,小于待查值 27.就将 i 设置成 m+1 .

image-20230925203238599

6、接着找第三次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(3+4)>>> 1 = 3 .(向下求值)

image-20230925203402918

7、**判断第三次中间索引与待查值的大小。**发现 m 所对应的数为 26,小于待查值 27.再将 i 设置成 m+1 .

image-20230925204758187

8、接着找第四次的中间索引 m ,使用公式 m = f l o o r ( i + j 2 ) m = floor(\frac {i+j}{2}) m=floor(2i+j) 可知:m=(4+4)>>> 1 = 4 .

image-20230925204938200

9、**判断第四次中间索引与待查值的大小。**发现 m 所对应的数为 33,大于待查值 27.再将 **j 设置成 m .**但由于原本的 j 就在下标为4的位置,此时就开始陷入死循环里了。

if 判断中的 j 的边界问题改动原因

改动三就很简单了,这里就不多赘述了。

衡量算法好坏

时间复杂度

下面的查找算法也能得出与之前二分查找一样的结果,那你能说出它差在哪里吗?

public static int search(int[] a, int k) {
    for (
        int i = 0;
        i < a.length;
        i++
    ) {
        if (a[i] == k) {
            return i;
        }
    }
    return -1;
}

考虑最坏情况下(没找到)例如 [1,2,3,4] 查找 5

  • int i = 0 只执行一次
  • i < a.length 受数组元素个数 n n n 的影响,比较 n + 1 n+1 n+1
  • i++ 受数组元素个数 n n n 的影响,自增 n n n
  • a[i] == k 受元素个数 n n n 的影响,比较 n n n
  • return -1,执行一次

粗略认为每行代码执行时间是 t t t,假设 n = 4 n=4 n=4 那么

  • 总执行时间是 ( 1 + 4 + 1 + 4 + 4 + 1 ) ∗ t = 15 t (1+4+1+4+4+1)*t = 15t (1+4+1+4+4+1)t=15t
  • 可以推导出更一般地公式为, T = ( 3 ∗ n + 3 ) t T = (3*n+3)t T=(3n+3)t

如果套用二分查找算法,还是 [1,2,3,4] 查找 5

public static int binarySearch(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {			// 在左边
            j = m - 1;
        } else if (a[m] < target) {		// 在右边
            i = m + 1;
        } else {
            return m;
        }
    }
    return -1;
}
  • int i = 0, j = a.length - 1 各执行 1 次

  • i <= j 比较 f l o o r ( log ⁡ 2 ( n ) + 1 ) floor(\log_{2}(n)+1) floor(log2(n)+1) 再加 1 次

  • (i + j) >>> 1 计算 f l o o r ( log ⁡ 2 ( n ) + 1 ) floor(\log_{2}(n)+1) floor(log2(n)+1)

  • 接下来 if() else if() else 会执行 3 ∗ f l o o r ( log ⁡ 2 ( n ) + 1 ) 3* floor(\log_{2}(n)+1) 3floor(log2(n)+1) 次,分别为

    • if 比较
    • else if 比较
    • else if 比较成立后的赋值语句
  • return -1,执行一次

结果:

  • 总执行时间为 ( 2 + ( 1 + 3 ) + 3 + 3 ∗ 3 + 1 ) ∗ t = 19 t (2 + (1+3) + 3 + 3 * 3 +1)*t = 19t (2+(1+3)+3+33+1)t=19t
  • 更一般地公式为 ( 4 + 5 ∗ f l o o r ( log ⁡ 2 ( n ) + 1 ) ) ∗ t (4 + 5 * floor(\log_{2}(n)+1))*t (4+5floor(log2(n)+1))t

注意:

左侧未找到和右侧未找到结果不一样,这里不做分析

两个算法比较,可以看到 n n n 在较小的时候,二者花费的次数差不多

但随着 n n n 越来越大,比如说 n = 1000 n=1000 n=1000 时,用二分查找算法(红色)也就是 54 t 54t 54t,而蓝色算法则需要 3003 t 3003t 3003t

计算机科学中,时间复杂度是用来衡量:一个算法的执行,随数据规模增大,而增长的时间成本

  • 不依赖于环境因素
如何表示时间复杂度
  • 假设算法要处理的数据规模是 n n n,代码总的执行行数用函数 f ( n ) f(n) f(n) 来表示,例如:

    • 线性查找算法的函数 f ( n ) = 3 ∗ n + 3 f(n) = 3*n + 3 f(n)=3n+3
    • 二分查找算法的函数 f ( n ) = ( f l o o r ( l o g 2 ( n ) ) + 1 ) ∗ 5 + 4 f(n) = (floor(log_2(n)) + 1) * 5 + 4 f(n)=(floor(log2(n))+1)5+4
  • 为了对 f ( n ) f(n) f(n) 进行化简,应当抓住主要矛盾,找到一个变化趋势与之相近的表示法

O O O 表示法

image-20221108103846566

其中

  • c , c 1 , c 2 c, c_1, c_2 c,c1,c2 都为一个常数
  • f ( n ) f(n) f(n) 是实际执行代码行数与 n 的函数
  • g ( n ) g(n) g(n) 是经过化简,变化趋势与 f ( n ) f(n) f(n) 一致的 n 的函数
渐进上界

渐进上界(asymptotic upper bound):从某个常数 n 0 n_0 n0开始, c ∗ g ( n ) c*g(n) cg(n) 总是位于 f ( n ) f(n) f(n) 上方,那么记作 O ( g ( n ) ) O(g(n)) O(g(n))

  • 代表算法执行的最差情况

例1:

  • f ( n ) = 3 ∗ n + 3 f(n) = 3*n+3 f(n)=3n+3
  • g ( n ) = n g(n) = n g(n)=n
  • c = 4 c=4 c=4,在 n 0 = 3 n_0=3 n0=3 之后, g ( n ) g(n) g(n) 可以作为 f ( n ) f(n) f(n) 的渐进上界,因此表示法写作 O ( n ) O(n) O(n)

例2:

  • f ( n ) = 5 ∗ f l o o r ( l o g 2 ( n ) ) + 9 f(n) = 5*floor(log_2(n)) + 9 f(n)=5floor(log2(n))+9
  • g ( n ) = l o g 2 ( n ) g(n) = log_2(n) g(n)=log2(n)
  • O ( l o g 2 ( n ) ) O(log_2(n)) O(log2(n))

已知 f ( n ) f(n) f(n) 来说,求 g ( n ) g(n) g(n)

  • 表达式中相乘的常量,可以省略,如
    • f ( n ) = 100 ∗ n 2 f(n) = 100*n^2 f(n)=100n2 中的 100 100 100
  • 多项式中数量规模更小(低次项)的表达式,如
    • f ( n ) = n 2 + n f(n)=n^2+n f(n)=n2+n 中的 n n n
    • f ( n ) = n 3 + n 2 f(n) = n^3 + n^2 f(n)=n3+n2 中的 n 2 n^2 n2
  • 不同底数的对数,渐进上界可以用一个对数函数 log ⁡ n \log n logn 表示
    • 例如: l o g 2 ( n ) log_2(n) log2(n) 可以替换为 l o g 10 ( n ) log_{10}(n) log10(n),因为 l o g 2 ( n ) = l o g 10 ( n ) l o g 10 ( 2 ) log_2(n) = \frac{log_{10}(n)}{log_{10}(2)} log2(n)=log10(2)log10(n),相乘的常量 1 l o g 10 ( 2 ) \frac{1}{log_{10}(2)} log10(2)1 可以省略
  • 类似的,对数的常数次幂可省略
    • 如: l o g ( n c ) = c ∗ l o g ( n ) log(n^c) = c * log(n) log(nc)=clog(n)
常见大 O O O 表示法

image-20221108114915524

按时间复杂度从低到高

  • 黑色横线 O ( 1 ) O(1) O(1),常量时间,意味着算法时间并不随数据规模而变化
  • 绿色 O ( l o g ( n ) ) O(log(n)) O(log(n)),对数时间
  • 蓝色 O ( n ) O(n) O(n),线性时间,算法时间与数据规模成正比
  • 橙色 O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n)),拟线性时间
  • 红色 O ( n 2 ) O(n^2) O(n2) 平方时间
  • 黑色朝上 O ( 2 n ) O(2^n) O(2n) 指数时间
  • 没画出来的 O ( n ! ) O(n!) O(n!) ☞ 指n的阶乘,是时间复杂度最大的
渐进下界

渐进下界(asymptotic lower bound):从某个常数 n 0 n_0 n0开始, c ∗ g ( n ) c*g(n) cg(n) 总是位于 f ( n ) f(n) f(n) 下方,那么记作 Ω ( g ( n ) ) \Omega(g(n)) Ω(g(n))

渐进紧界

渐进紧界(asymptotic tight bounds):从某个常数 n 0 n_0 n0开始, f ( n ) f(n) f(n) 总是在 c 1 ∗ g ( n ) c_1*g(n) c1g(n) c 2 ∗ g ( n ) c_2*g(n) c2g(n) 之间,那么记作 Θ ( g ( n ) ) \Theta(g(n)) Θ(g(n))

空间复杂度

与时间复杂度类似,一般也使用大 O O O 表示法来衡量:一个算法执行随数据规模增大,而增长的额外空间成本*(额外指的是原始数据所占的空间不用算)

public static int binarySearchBasic(int[] a, int target) {
    int i = 0, j = a.length - 1;    // 设置指针和初值
    while (i <= j) {                // i~j 范围内有东西
        int m = (i + j) >>> 1;
        if(target < a[m]) {         // 目标在左边
            j = m - 1;
        } else if (a[m] < target) { // 目标在右边
            i = m + 1;
        } else {                    // 找到了
            return m;
        }
    }
    return -1;
}

二分查找性能

下面分析二分查找算法的性能

时间复杂度

  • 最坏情况: O ( log ⁡ n ) O(\log n) O(logn)
  • 最好情况:如果待查找元素恰好在数组中央,只需要循环一次 O ( 1 ) O(1) O(1)

空间复杂度

  • 需要常数个指针 i , j , m i,j,m i,j,m,因此额外占用的空间是 O ( 1 ) O(1) O(1)

平衡版

与前面的基础版与进阶版不同的是:只用 if-else 即可

public static int binarySearchBalance(int[] a, int target) {
    int i = 0, j = a.length;
    while (1 < j - i) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m;
        } else {
            i = m;
        }
    }
    return (a[i] == target) ? i : -1;
}

思想:

  1. 左闭右开的区间, i i i 指向的可能是目标,而 j j j 指向的不是目标
  2. 不奢望循环内通过 m m m 找出目标, 缩小区间直至剩 1 个, 剩下的这个可能就是要找的(通过 i i i
    • j − i > 1 j - i > 1 ji>1 的含义是,在范围内待比较的元素个数 > 1
  3. 改变 i i i 边界时,它指向的可能是目标,因此不能 m + 1 m+1 m+1
  4. 循环内的平均比较次数减少了
  5. 时间复杂度 Θ ( l o g ( n ) ) \Theta(log(n)) Θ(log(n))

算法题实战:力扣704 . 二分查找

具体的解题过程可以跳转到我的另一篇博客进行观看:代码随想录—力扣算法题:704二分查找.Java版(示例代码与导图详解)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1043730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

regsvr32 initpki.dll找不到指定模块要怎么解决?教你快速修复initpki.dll文件

当你尝试在 Windows 操作系统中注册 DLL 文件时&#xff0c;可能会遇到错误消息&#xff1a;“regsvr32 initpki.dll找不到指定模块”。它通常是由于一个或多个 DLL 文件缺失或损坏所导致的。这是一个常见的错误&#xff0c;并且可以遇到在 Windows 7、Windows 8 和 Windows 10…

百度实习一面(知识图谱部门)

百度面经&#xff08;知识图谱部&#xff09;一面 1.自我介绍 介绍完了&#xff0c;打开共享&#xff0c;对着简历一点一点问 2.ffmpeg在项目中是怎么使用的 回答了ffmpeg在项目中使用的命令&#xff0c;用来干了什么 3.为什么使用toml配置&#xff0c;了解过yml配置吗&am…

【Vue3 源码解析】computed

export function computed<T>(getter: ComputedGetter<T>,debugOptions?: DebuggerOptions ): ComputedRef<T> export function computed<T>(options: WritableComputedOptions<T>,debugOptions?: DebuggerOptions ): WritableComputedRef<…

SPA项目之主页面--动态树右侧内容管理

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于VueElementUI的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.左侧动态树 1.定义组件 ①样式…

【神印王座】悲啸洞穴之物揭晓,圣采儿差点被骗,幸好龙皓晨聪明

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析神印王座。 神印王座动漫现阶段已经出到龙皓晨等人接取新任务深入魔族地界的阶段&#xff0c;而龙皓晨等人接取的任务想必现在大家都知道了&#xff0c;那就是探索魔族地界中的悲啸洞穴。但是大家知道悲啸洞穴里面藏着什么…

智能的障碍:符号化

基于事实与价值叠加的算计与基于事实的计算有着明显的区别。 基于事实的计算是指根据已有的客观事实和数据进行计算和推理。在这种计算中&#xff0c;只考虑和利用与事实相关的信息和数据&#xff0c;目的是得出合理的、基于客观事实的结论。例如&#xff0c;使用数学公式和逻辑…

python实现全局变量共享,一个全局变量在多个文件中使用

因为业务需求要将抓到的数据进行累加统计&#xff0c;而且还要间隔三秒钟将这个数据推送到服务端&#xff0c;所以就要实现一个全局变量来记录这个数据&#xff0c;而且推送服务要每隔三秒钟就推送一次到服务端。之前使用了一个全局文件common.py&#xff0c;里面存储这个变量t…

【CV学习笔记】tensorrtx-yolov5 逐行代码解析

1、前言 TensorRTx(下文简称为trtx)是一个十分流行的利用API来搭建网络结构实现trt加速的开源库&#xff0c;作者提到为什么不用ONNX parser的方式来进行trt加速&#xff0c;而用最底层的API来搭建trt加速的方式有如下原因: Flexible 很容易修改模型的任意一层&#xff0c;删…

渗透测试——信息收集思路

文章目录 信息收集域名与 IPOSINTCDNCDN的作用如何检测是否存在CDN CDN 绕过多地Ping邮件服务器子域名真实IP寻找国外地址请求查找老域名查找关联域名信息泄露/配置文件网站漏洞DNS记录&#xff0c;证书域名历史 搜索引擎语法WHOIS端口对外开放情况Nmap 网站的三种部署模式网站…

chrome extensions mv3通过content scripts注入/获取原网站的window数据

开发插件的都知道插件的content scripts和top window只共享Dom不共享window和其他数据&#xff0c;如果想拿挂载在window的数据还有点难度&#xff0c;下面会通过事件的方式传递cs和top window之间的数据写一个例子 代码 manifest.json 这里只搞了2个js&#xff0c;content.…

DataX - 在有总bps限速条件下,单个channel的bps值不能为空,也不能为非正数

更新服务器上的datax版本后&#xff0c;发现执行以前的任务全都失败&#xff0c;查看日志都有报 com.alibaba.datax.common.exception.DataXException: Code:[Framework-03], Description:[DataX引擎配置错误&#xff0c;该问题通常是由于DataX安装错误引起&#xff0c;请联系…

ssl证书 阿里的域名,腾讯云的证书

目录 1.腾讯云申请ssl免费证书 2.去阿里云进行解析 3.回到腾讯云 4.nginx的配置 说明&#xff1a;阿里云的免费证书用完了&#xff08;每年可以申请20个&#xff09;&#xff0c;还有个项目要用证书&#xff0c;第三方的证书免费的都是90天的。看了下腾讯云业可以申请免费的…

史上最全的公司各种体系流程图,直接拿走!

大家好&#xff0c;我是老原。 优秀企业和卓越企业的区别在哪里&#xff1f; 两个字&#xff1a;流程。 流程的水平高低在一定程度上也体现了项目经理做项目的能力&#xff0c;一个企业能否持续成功的过程能力。 拥有稳定高效的流程管理体系&#xff0c;项目经理的管理水平…

ABB机器人如何在示教器上查看输入输出以及强制输出DO信号

ABB机器人如何在示教器上查看输入输出以及强制输出DO信号 如下图所示,点击左上角的菜单—选择“输入输出“, 如下图所示,进入输入输出画面后,点击右下角的视图,选择“数字输出“, 如下图所示,此时可以看到所有的DO信号及其当前值, 如下图所示,这里以 Local_IO_0_DO1 为…

AI大模型服务上线,助力企业AI大模型应用落地

在数字时代的浪潮中&#xff0c;人工智能(AI)技术的发展和应用已经深入到我们生活的方方面面。其中&#xff0c;企业AI大模型作为AI技术的重要形式之一&#xff0c;正在成为推动企业创新、提高效率和优化决策的关键力量。为顺应AI大模型的新趋势需求&#xff0c;近日&#xff0…

游戏技术亮点|Aavegotchi 与 GameSwift 建立合作伙伴关系

构建一个优秀的游戏只是成功发布的一部分&#xff0c;让数百万玩家体验这款游戏才是真正的乐趣所在。 这也是为什么我们很高兴宣布与 GameSwift 建立了新的合作伙伴关系&#xff0c;GameSwift 是一款先进的模块化游戏区块链&#xff0c;采用 zkEVM 技术构建&#xff0c;是全球…

【通意千问】大模型GitHub开源工程学习笔记(1)

9月25日&#xff0c;阿里云开源通义千问140亿参数模型Qwen-14B及其对话模型Qwen-14B-Chat,免费可商用。 立马就到了GitHub去fork。 GitHub&#xff1a; GitHub - QwenLM/Qwen: The official repo of Qwen (通义千问) chat & pretrained large language model proposed b…

解决谷歌Redux DevTools调试React+Typescript项目数据对不上/连接不上问题

上文 ReactTypescript项目环境中搭建并使用redux环境 我们创建了一个redux项目的环境 但是我们用谷歌浏览器插件调试 会发现 要不 匹配的数据有问题 看不到数据 要不 就压根连接不到 而且 我们点击加减号 去改变值 调试工具也没有任何反应 我们终端输入 npm install --save-d…

VSCode安装离线插件

1. 打开 VSCode 插件市场网址 Extensions for the Visual Studio family of product&#xff0c;输入你想要的插件名称&#xff0c;比如这里我想要安装的是 Markdown All in One 插件 2. 点击进入插件主页&#xff0c;点击右侧的 Download Extension 链接&#xff0c;得到下载…

Hugging News #0925: 一览近期的新功能发布

每一周&#xff0c;我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新&#xff0c;包括我们的产品和平台更新、社区活动、学习资源和内容更新、开源库和模型更新等&#xff0c;我们将其称之为「Hugging News」。本期 Hugging News 有哪些有趣的消息&#xff0…