【算法】第一个错误的版本

51 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情

题目

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

解题思路

关键字:第一个错误版本;方法判断;减少调用次数;错误版本在n个版本中

关键信息:二分法;有序遍历

题目比较重要的一点是错误版本是在n当中的。

递归解法

递归解法必然是简洁代码量最少的情况。但由于调用次数没有优化导致运行时间超时。但递归思想在算法解题中尤为重要,练习使用递归还是有好处的。

  1. 递归终结条件:当版本判断上不是错误版本时下一个版本就是错误版本。
  2. 循环段落:执行上一个版本。
  3. 返回结果是版本号。
public int firstBadVersion(int n) {
	if(!isBadVersion(n)) return n + 1;
    int version = firstBadVersion(n - 1);
    return version;
}

递归在有序遍历中并不太适用,主要是在执行效率上并不高效。因此递归算法不作为最优解但是可以参考的算法思路。

二分查找解法

题目要求尽量减少接口调用;因此并不会对整个版本号都去做遍历请求。所以有序遍历一般会用到二分法来处理实现(双指针算法,首尾指针不断分割收敛遍历范围)。

  1. 创建左右指针分别指向首尾版本号。
  2. 循环获取中间版本号通过isBadVersion方法判断是否是错误版本
  3. 如果是错误版本则右边指针收敛到错误版本;若不是错误版本则左边指针收敛到中间版本+1
  4. 循环终止为左右指针重合。
    public int firstBadVersion(int n) {

        int left = 1;
        int right = n;
        int res = n;
        while (left <= right) {
            int mid = left + ((right - left) / 2);
            if (isBadVersion(mid)) {
                res = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return res;
    }

二分法有效减少遍历次数通过不断收敛只需要做O(logn) 次即可求解。

二分查找思路

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

普通遍历

普通遍历的方法其实就很简单,遍历整个数组找到希望的值。时间长度和数组长度有关。

public int search(int[] nums, int target) {
    int index = 0;
    for(int num : nums){
        if(num == target){
            return index;
        }
        index ++;
    }
    return -1;
}

二分查找

上题错误版本就是采用二分查找算法思路实现,全局遍历效率肯定不高但利用二分法就能实现高效查询。基本的二分查找模版代码如下所示:

public int search(int[] nums, int target) {
    int leftIndex = 0,rightIndex = nums.length - 1;
    while(leftIndex <= rightIndex){
        int mid = ( rightIndex - leftIndex ) / 2 + leftIndex;
        int midValue = nums[mid];
        if(midValue == target){
            return mid;
        }else if(target >  midValue){
            leftIndex = mid + 1;
        }else {
            rightIndex = mid - 1;
        }
    }
    return -1;
}

参考