二分法的时间复杂度是 O(logn) 。其原理是采用一个时间复杂度为O(1)的操作,将问题分为两半之后,只取其中一半。这样就减小了问题的规模。
二分法属于P 问题,即能够在多项式时间复杂度内解决的问题。与P问题相对应的是NP 问题,定义为无法用多项式时间复杂度解决的问题。NP 问题一般是指只能用深度优先搜索解决的问题。
二分法可以用递归或者非递归实现。算法过程中是否采用递归,需要具体问题具体分析。但是如果是链表问题,则一定不能用递归,因为可能造成很深的递归深度。
二分法的实质是去找一条分割线,然后确定抛弃哪边,取哪边。所以只要能够确定抛弃哪边,就能用二分,如果不能确定抛弃哪边,就不能用二分。
非递归版本:
public int findNumber(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while ( start + 1 < end) {
int mid = start + (end - start) / 2;
if (nums[mid] < target) {
start = mid;
}
else {
end = mid;
}
}
if (nums[start] == target) {
return start;
}
if (nums[end] == target) {
return end;
}
return -1;
}
如果没有关于first position of target 或者 last position of target的限制,则不需要管当nums[ mid] == target 时的情况。
还有一个问题是当 mid = target时,应该归于左边还是右边。
如果是找target第一次出现的位置。
if (nums[mid] < target) {
start = mid;
}
else {
end = mid;
}
因为mid的位置已经确定是target了,现在需要找mid前面有没有target, 所以让 end = mid.
同理,如果是找target最后一次出现的位置,则是
if (nums[mid] <= target) {
start = mid;
}
else {
end = mid;
}
二分的另一种实先方式是用递归实现。这里虽然不需要回头,但是需要自己调用自己。代码如下:
public int findNumber(int[] nums, int start, int end, int target){
if (start > end) {
return -1;
}
int mid = start + (end - start) / 2;
if (nums[mid] > target) {
return findNumber(nums, mid + 1, end, target);
}
return findNumber(nums, start, mid - 1, target);
}
如果采用 start < end,则在找 last position of target 或 first position of target时,会出现死循环。只需记住一个反例:
[1,1] target = 1
二分法的实质是去找一条分割线,然后确定抛弃哪边,取哪边。所以只要能够确定抛弃哪边,就能用二分,如果不能确定抛弃哪边,就不能用二分。二分法可以解决这种问题:给出一个数组,可以通过某一个属性将整个数组分成两部分,即为OOOOXXXX。然后可用二分法找数组中第一个X 或者 最后一个 O 的位置。应用的例子有:动态数组,网络重试。
例题:
Find Minimum in Rotated Sorted Array:
这道题从直观上看,可采用遍历法,时间复杂度是O(N)。如果要优化算法的话,就只能变成O(logN)。而O(logN)的算法只有二分法。这道题的难点在于如果寻找前后两部分的不同特点。分界位置可以是首个小于数组最后一个数的数,或者首个小于第一个数的数。分辨的方法是找一个极端情况,即当所有数都是升序的时候,最小的数怎么找。故可得到如下的图:
如果是递减的数列去找 Minimum in Rotated Sorted Array的话,则找首个小于数列中第一个数的数。如下图所示:
如果 rotated array 中有重复的数,那么就无法用 log(n) 来解决!只能去遍历
K分法:
如果分成K份,那么总体的时间复杂度是 (K - 1) * logK(N) 这么做和二分法消耗的时间是一样的,并不会有效果上的提升。
Search in Rotated Sorted Array:
有两种方法:1. 先找到最小值在什么地方,然后判断对哪一部分再用二分法。2. 直接用二分法,不判断最小值。
对于第二种方法,需要先判断一下中轴线的位置,然后利用首,尾节点和中轴线判断取哪边。
三步翻转法:
将一个 rotated array 恢复成原来的情况。时间复杂度是O(N)。反转三次以得到最后的结果。
相关练习:Recover Rotated Sorted Array; Rotated String。
二维数组找数 Search a 2D Matrix II:
这个数组有一个特征,左上角最小, 右下角最大。通过对时间复杂度的分析,时间复杂度最坏是O(N *M),对于特殊情况来说是O(N + M)。所以可知,时间复杂度不可能是log级别的。只能是N + M这种级别的。为了通过O(1)的时间复杂度排除一行或一列,只能考虑从左下或者右上。
快速幂算法:
求一个数的n次幂,只需求出n/2次幂,以此类推,可得到时间复杂度为O(logN)的算法。有两种算法,递归和非递归。
辗转相除:求最大公约数的方法。
循环数组(rotated array) 如何实现循环数组??????
要明白一些内置函数的时间复杂度