[剑指-Offer] 53. I. 在排序数组中查找数字 I 及 II. 0~n-1中缺失的数字(二分法、代码优化、巧妙解法)

文章目录

    • 1. 题目来源
    • 2. 题目说明
    • 3. 题目解析 --- I. 在排序数组中查找数字 I
      • 方法一:遍历+常规解法
      • 方法二:二分法+递归+巧妙解法
    • 4. 题目解析 --- II. 0~n-1中缺失的数字
      • 4.1 方法一:遍历+常规解法
      • 方法二:二分法+巧妙解法

1. 题目来源

链接:I. 在排序数组中查找数字 I
链接:II. 0~n-1中缺失的数字
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

[剑指-Offer] 53. I. 在排序数组中查找数字 I 及 II. 0~n-1中缺失的数字(二分法、代码优化、巧妙解法)_第1张图片
[剑指-Offer] 53. I. 在排序数组中查找数字 I 及 II. 0~n-1中缺失的数字(二分法、代码优化、巧妙解法)_第2张图片

3. 题目解析 — I. 在排序数组中查找数字 I

方法一:遍历+常规解法

顺序遍历即可。时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)。没有用到 排序数组 的特性。

参见代码如下:

// 执行用时 :4 ms, 在所有 C++ 提交中击败了98.65%的用户
// 内存消耗 :15.8 MB,在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if (nums.size() == 0) return 0;
        int tmp = 0;
        for (int i = 0; i <nums.size(); ++i) {
            while (i < nums.size() and nums[i] == target) {
                ++tmp;
                ++i;
            }
        }
        return tmp;;
    }
};

方法二:二分法+递归+巧妙解法

二分法的巧妙应用,现以找连续出现数字的第一个位置为例,主要思路如下:

  • 每次拿中间数字 nums[mid]target 做比较
    • nums[mid] > targettarget 只能出现在前半段,即 right = mid - 1
    • nums[mid] < targettarget 只能出现在前半段,即 left = mid + 1
    • nums[mid] == target,首先需要判断中间这个数字是不是第一个 target,如果中间数字的前一个数字不等于 target 那么中间数字就是第一个 target,如果前一个数字也为 target 那么第一个数字出现的位置就在前半段,所以 right = mid - 1

至此,即可采用二分+递归的思想解决这个问题了,关于最后一个 target 的位置查找与上面的思想基本一致,在此不列出,可自行思考。

时间复杂度 O ( l o g n ) O(logn) O(logn),空间复杂度 O ( 1 ) O(1) O(1)

所以,遇到排序数组或者能转化成排序数组的形式,二分法是降低时间复杂度的利器!

参见代码如下:

// 执行用时 :16 ms, 在所有 C++ 提交中击败了22.43%的用户
// 内存消耗 :23.7 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = getLeft(nums, target, 0, nums.size() - 1);
        int right = getRight(nums, target, 0, nums.size() - 1);
        if (left > -1 and right > -1) return right - left + 1;
        return 0; 
    }

    int getLeft(vector<int> nums, int target, int left, int right) {
        if (left > right) return -1;
        int mid = (left + right) >> 1;
        if (nums[mid] == target) {
            if ((mid > 0 and nums[mid - 1] != target) or mid == 0) return mid;
            else right = mid - 1;
        }
        else if (nums[mid] > target) right = mid - 1;
        else left = mid + 1;

        return getLeft(nums, target, left, right);
    }

    int getRight(vector<int> nums, int target, int left, int right) {
        if (left > right) return -1;
        int mid = (left + right) >> 1;
        if (nums[mid] == target) {
            if ((mid < nums.size() - 1 and nums[mid + 1] != target) or mid == nums.size() - 1) return mid;
            else left = mid + 1;
        }
        else if (nums[mid] < target) left = mid + 1;
        else right = mid - 1;

        return getRight(nums, target, left, right);
    }
};

4. 题目解析 — II. 0~n-1中缺失的数字

4.1 方法一:遍历+常规解法

暴力法,遍历数组,数字在数组中,其下标与当前数组所存放的值相同,若不相同,则返回数组下标即可。若遍历完数组仍为返回,说明数组有序,最后一个数字是缺失数字,则需要返回数组末尾存放的数字再加一即可。时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)

// 执行用时 :24 ms, 在所有 C++ 提交中击败了58.30%的用户
// 内存消耗 :19.5 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        for (int i = 0; i < nums.size(); ++i) {
            if (i != nums[i]) 
                return i;
        }
        return nums.back() + 1;
    }

};

方法二:二分法+巧妙解法

类比在有序数组查找某数是否存在一个性质。

参见代码如下:

// 执行用时 :20 ms, 在所有 C++ 提交中击败了82.48%的用户
// 内存消耗 :19.5 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = (left + right) >> 1;
            if (mid == nums[mid]) left = mid + 1; 
            else right = mid;
        }
        return left;
    }
};

你可能感兴趣的:(#,《剑指-Offer》(第二版))