B站左神算法课学习笔记(P3):认识复杂度和简单排序

常数时间的操作

一个操作和样本的数据量没有关系,每次都在固定时间量内完成,称为常数操作

eg:+-*/、位运算

        int a = arr[ i ];

区分:不是常数操作,eg:链表 int b = list.get( i );

时间复杂度

一个算法流程中对于常数操作量的指标。常用O表示(读作big O),求解需写出该流程中发生了多少常数操作,eg遍历、比较、交换等,然后总结出常数操作量的表达式。

表达式中只包含去除系数的最高项,记为O(f(N))。

评价算法的好坏:先看时间复杂度的指标,其相等的情况下载根据不同数据样本下的实际运行时间来判断,即“常数项时间”(注:通过测试完成!)。

额外空间复杂度

实现算法过程中额外申请的用于存储的空间

异或运算

aba^b
000
011
101
111

也可理解为无进位相加

异或运算性质:

(1)0^N = N , N^N = 0

(2)交换律,结合律

(3)N个数相^,调整顺序结果仍相同。

实例:使用 ^ 交换两数的值(前提:两数的地址不同)

解释:令 a =甲, b = 乙;

a = a ^ b        ->        a = 甲 ^ 乙, b = 乙

b = a ^ b        ->        a = 甲 ^ 乙, b = 乙 ^ 甲 ^ 乙 = 甲

a = a ^ b        ->        a = 甲 ^ 乙 ^ 甲 = 乙, b = 甲

感觉也可以使用“无进位加法”来解释,但是自己证了一会没证出来(

例题(常见面试题)

给定一个数组,已知其中存入了若干数字,要求在时间复杂度为O(N),空间复杂度为O(1)的条件下求解下面两问:

(1)若其中只有一种数是奇数个,其他为偶数个,求该数字;

(2)若其中有两种数是奇数个,其他为偶数个,求这两个数字;

解:(1)int eor = 0,使用for循环遍历数组并且执行:eor = eor ^ arr[i]。

最后得到的eor即为出现次数为单数的数字。

原因:n ^ n = 0 , 0 ^ n = n ,偶数个的相互抵消了。

(2)本题目的难点在于区分a,b两数

先同上得到eor,再定义eor'用于存储结果。不妨假设两种数为a, b。

因为 a ^ b \neq 0,所以err的二进制表示形式必有某一位值为1,我们可以通过如下代码找到该位:

(此处找到的是右侧起第一位不同的数)

int rightOne = eor & (~eor + 1)

那一位则是a,b至少会出现的一位不同,利用该处不同,我们可以巧妙地分开a和b。

在for循环中,使用

if(( cur & rightOne ) == 0 ){
    a = a ^ cur; //a = eor'
}

所得到的结果a即是a或b中的某一个,另一个数字则可通过 eor ^ a 求出。

关键点:所有的数字可被分为“ rightOne ”这一个数位为0和不为0的两类,又因为偶数不影响异或结果,故我们借助这一个数位巧妙地区分了ab两数。

选择、冒泡、插入排序

选择排序

每次从所有数中,选一个最大/小的与最左/右侧的数进行交换

时间复杂度O\left ( N^{2} \right ),额外空间复杂度O\left ( 1 \right ).

冒泡排序

相邻数间向左/向右析出较小/大的数

时间复杂度O\left ( N^{2} \right ),额外空间复杂度O\left ( 1 \right ).

插入排序

假如给定一串数字736152,要求使用插入排序进行升序排序。

核心思想:0 ~ i 已经有序的情况下,想使得 0 ~ (i+1) 有序。

步骤 n 现在的数字执行的操作得到的数字
17361520 ~ 1想有序,交换73376152
23761520 ~ 2想有序,交换76,发现 3 < 6 ,不需要再交换367152
33671520 ~ 3想有序,依次交换71、61、31,此时前面无数字,故停止136752
41367520 ~ 4想有序,依次交换75、65,发现 3 < 5 ,故停止135672
51356720 ~ 5想有序,依次交换72、62、52、32,发现 1 < 2 ,故停止123567

索引为 0 ~ 5 的数字都已有序,故排序完成。

对于插入排序,时间复杂度为O\left ( N^{2} \right ),但最好情况下只需O\left ( N \right ),额外空间复杂度O(1)。

修正原有概念:时间复杂度指按照最差情况估计的算法表现。

补充:\Theta \left ( * \right )为平均时间复杂度,\Omega \left ( * \right )为最优时间复杂度。

二分法

下面列举几个使用二分法的例子:

(1)在一个有序数组中,找某个数是否存在。

对半分开并比较,时间复杂度O\left ( \log_2N \right )

注:若不写下标如 O\left ( \log N \right ) 则默认为 O\left ( \log_2N \right ).

(2)在一个有序数组中,找 >= 某个数最左侧的位置

假设找到 >= 3 的最左侧位置:(注意:需要二分到底

(3)局部最小值问题

数组arr无序,且任意两个相邻数不相等,求其 任意一个 局部最小值。

tips:局部最小值——对于位置 i 有 arr[ i ] < arr[ i-1 ] 和 arr[ i ] < arr[ i+1 ]。

解:先观察 0 位置和 N - 1 位置,若满足局部最小值,直接返回。

若其都不满足,则进行二分。取中点观察是否满足局部最小值,若满足,则返回,不满足则随机选择一侧继续进行二分即可。

对数器

若需测 a 方法,已有同样实现但是复杂度不好的 b 方法,只需使用随机函数生成足够多的样本,再对所有样本分别使用 a、b 方法,若有任何不一致,则进行调整;若一致,则 a 方法正确。

其中生成样本的操作如下:

以产生数组为例:

int[] arr = new int[(int)((maxSize + 1) * Math.random())];//生成随机长度
for(int i = 0; i < arr.length; i++){
    //相减保证有正有负,可根据实际情况调整
    arr[i] = (int)((maxValue + 1) * Math.random()) - (int)((maxValue + 1) * Math.random());
} 
return arr;

使用对数器,我们可以生成足够多的样本用于测试,充分保证程序的正确性。

当然,也可以让我们在没有OJ的情况下完成对所做练习的正确性进行检验。

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值