算法爬坑记——二分法

二分法:

二分法的时间复杂度是 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;
    }

二分的另一种实先方式是用递归实现。这里虽然不需要回头,但是需要自己调用自己。代码如下:

扫描二维码关注公众号,回复: 1779846 查看本文章
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. 直接用二分法,不判断最小值。

 对于第二种方法,需要先判断一下中轴线的位置,然后利用首,尾节点和中轴线判断取哪边。

时间复杂度为 logN 的其他算法:

三步翻转法:

将一个 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) 如何实现循环数组??????

要明白一些内置函数的时间复杂度

猜你喜欢

转载自blog.csdn.net/chichiply/article/details/80575656