常数时间的操作
一个操作和样本的数据量没有关系,每次都在固定时间量内完成,称为常数操作。
eg:+-*/、位运算
int a = arr[ i ];
区分:不是常数操作,eg:链表 int b = list.get( i );
时间复杂度
一个算法流程中对于常数操作量的指标。常用O表示(读作big O),求解需写出该流程中发生了多少常数操作,eg遍历、比较、交换等,然后总结出常数操作量的表达式。
表达式中只包含去除系数的最高项,记为O(f(N))。
评价算法的好坏:先看时间复杂度的指标,其相等的情况下载根据不同数据样本下的实际运行时间来判断,即“常数项时间”(注:通过测试完成!)。
额外空间复杂度
实现算法过程中额外申请的用于存储的空间
异或运算
a | b | a^b |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
也可理解为无进位相加。
异或运算性质:
(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 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两数。
选择、冒泡、插入排序
选择排序
每次从所有数中,选一个最大/小的与最左/右侧的数进行交换
时间复杂度,额外空间复杂度
.
冒泡排序
相邻数间向左/向右析出较小/大的数
时间复杂度,额外空间复杂度
.
插入排序
假如给定一串数字736152,要求使用插入排序进行升序排序。
核心思想:0 ~ i 已经有序的情况下,想使得 0 ~ (i+1) 有序。
步骤 n | 现在的数字 | 执行的操作 | 得到的数字 |
1 | 736152 | 0 ~ 1想有序,交换73 | 376152 |
2 | 376152 | 0 ~ 2想有序,交换76,发现 3 < 6 ,不需要再交换 | 367152 |
3 | 367152 | 0 ~ 3想有序,依次交换71、61、31,此时前面无数字,故停止 | 136752 |
4 | 136752 | 0 ~ 4想有序,依次交换75、65,发现 3 < 5 ,故停止 | 135672 |
5 | 135672 | 0 ~ 5想有序,依次交换72、62、52、32,发现 1 < 2 ,故停止 | 123567 |
索引为 0 ~ 5 的数字都已有序,故排序完成。
对于插入排序,时间复杂度为,但最好情况下只需
,额外空间复杂度O(1)。
修正原有概念:时间复杂度指按照最差情况估计的算法表现。
补充:为平均时间复杂度,
为最优时间复杂度。
二分法
下面列举几个使用二分法的例子:
(1)在一个有序数组中,找某个数是否存在。
对半分开并比较,时间复杂度。
注:若不写下标如 则默认为
.
(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的情况下完成对所做练习的正确性进行检验。