LeetCode笔记三(二分查找)

二分查找时间复杂度o(log n)

一、非递减顺序数组目标值的边界位置

1. 题目描述:

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

2. 分析

左边界:在nums[mid] == target时,另right = mid - 1,继续在[left,mid-1]范围寻找target的左边界,直到[left,mid-1]范围内没有target,此时最后一次nums[mid] == targetmid即为左边界。
左边界:在nums[mid] == target时,另left = mid + 1,继续在[mid+1, right]范围寻找target的右边界,直到[mid+1, right]范围内没有target,此时最后一次nums[mid] == targetmid即为右边界。

3. 题解

class Solution {
public:
    int searchLeftRange(vector<int>& nums, int target, bool ifLeft)
    {
        int left = 0, right = nums.size() - 1;
        int mid = 0, res = -1;
        while(left <= right)
        {
            mid = left + (right - left) / 2;
            if(nums[mid] < target)
                left = mid + 1;
            else if(nums[mid] > target)
                right = mid - 1;
            else
            {
                res = mid;
                if(ifLeft)
                    right = mid - 1;    //继续在[left,mid-1]范围寻找目标值target
                else
                    left = mid + 1;     //继续在[mid+1,right]范围寻找目标值target
            }
        }
        return res;
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        return {searchLeftRange(nums, target, true), searchLeftRange(nums, target, false)};
    }
};

二、寻找旋转排序数组中的最小值

1. 题目描述:

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

2. 分析

主要思路:用二分法查找,始终将目标值(这里是最小值)套住,并不断收缩左边界或右边界,循环条件left < right,当left = right时找到最小值,下标为left或right,又因为mid = left + (right - left) / 2地板除,mid永远不可能等于right

if条件判断

  • 中值>右值:有旋转,如 [4,5,6,7,0,1,2],且mid一定不是最小值,最小值范围[mid+1, right],故收缩左边界left = mid + 1
  • 中值<右值:
    (1)无旋转,如[0,1,2,4,5,6,7],最小值范围[left, mid],收缩有边界
    (2)有旋转,如 [5,6,7,0,1,2,3],但mid有可能是最小值,最小值范围[left, mid],故收缩边界right = mid

3. 题解

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        int mid = 0;
        while(left < right)
        {
            mid = left + (right - left) / 2;
            if(nums[mid] > nums[right])
                left = mid + 1;          //收缩左边界
            else
                right = mid;             //收缩右边界
        }
        return nums[left];              //此时left=right,left和right均是最小值的索引
    }
};

4. 扩展

可能存在重复元素值的数组,非降序排序进行旋转

与无重复元素值题目的区别:可能存在mid == right的情况
if条件判断

  • 中值>右值:有旋转,如 [4,5,6,6,0,1,2],且mid一定不是最小值,最小值范围[mid+1, right],故收缩左边界left = mid + 1

  • 中值<右值:
    (1)无旋转,如[0,1,2,4,5,6,6],最小值范围[left, mid],收缩右边界
    (2)有旋转,如 [5,6,6,0,1,2,3][6,6,0,1,2,3,5],但mid有可能是最小值,最小值范围[left, mid],故收缩右边界right = mid

  • 中值=右值:如[5,6,6,6,6][1,1]右值的左边一定还有最小值(也可能是重复的最小值),最小值范围[left, right-1],故收缩右边界right--

题解:

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

三、搜索旋转排序数组

1. 题目描述:

整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

2. 分析:

旋转后仍部分有序,从mid分成两部分[left, mid)(mid, right]必有一部分是有序的。

先判断nums[mid]nums[right]的关系,若nums[mid] > nums[right],说明[left, mid)部分有序,判断target是否在[nums[left], nums[mid])范围内,再在[left, mid-1][mid+1, right]范围内搜索。

否则,nums[mid] <= nums[right],说明(mid, right]部分有序,判断target是否在(nums[mid], nums[right]]范围内,再在[left, mid-1][mid+1, right]范围内搜索。

3. 题解:

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

4. 扩展:

数组中的值不必互不相同

与数组中的值必相互相同的区别:
可能存在在nums[mid] = nums[right]时,无法判断左或右是否有虚,如[1,0,1,1,1][1,1,1,2,1],无法判断target[left, mid-1][mid+1, right]范围。

因此在nums[mid] = nums[right]时,直接将右边界收缩right--,再继续判断直至nums[mid] != nums[right]

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        int mid = 0;
        while(left <= right)
        {
            mid = left + (right - left) / 2;
            if(nums[mid] == target)
                return true;
            if(nums[mid] > nums[right])
            {
                if(target >= nums[left] && target < nums[mid])
                    right = mid - 1;
                else
                    left = mid + 1;
            }
            else if(nums[mid] < nums[right])
            {
                if(target > nums[mid] && target <= nums[right])
                    left = mid + 1;
                else
                    right = mid - 1;
            }
            else
                right--;
        }
        return false;
    }
};

四、困难题:寻找两个正序数组的中位数

1. 题目描述:

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数
算法的时间复杂度应该为 O(log (m+n)) 。

2. 分析:

题目要求时间复杂度O(log (m+n)),想到二分查找。

中位数分成m+n奇数和偶数讨论,或直接求第(m + n + 1) / 2个数和第(m + n + 2) / 2个数的和求平均。

要找到第K个数,可以先排除前K/2个数,因为没有重复数字,故nums1[i+K/2-1](i为开始寻找的下标,因为是第K/2个数,需要-1)和nums2[j+K/2-1]中较小的那个一定不是第K个数,可以将较小的那个数组开始搜索的下标右移到i+K/2i+K/2从而排除掉K/2个数,继续迭代搜索第K-K/2个数。

迭代终止条件:

(1)直到i超过了nums1的边界,说明此时第K个数在nums2中,要找的数就是nums2[j + K - 1],或j超过了nums2的边界,说明此时第K个数在nums1中,要找的数就是nums1[i + K - 1]
(2)迭代到K=1,找剩下部分排序后第一个数,即为min(nums1[i], nums2[j])

3. 题解

class Solution {
public:
    int findKth(vector<int>& nums1, int start1, vector<int>& nums2, int start2, int K)
    {
        if(start1 >= nums1.size())
            return nums2[start2 + K - 1];
        if(start2 >= nums2.size())
            return nums1[start1 + K - 1];
        if(K == 1)
            return min(nums1[start1], nums2[start2]);
        int value1 = start1 + K/2 - 1 < nums1.size() ? nums1[start1 + K/2 - 1] : INT_MAX;
        int value2 = start2 + K/2 - 1 < nums2.size() ? nums2[start2 + K/2 - 1] : INT_MAX;
        if(value1 < value2)
            return findKth(nums1, start1 + K/2, nums2, start2, K - K/2);
        else
            return findKth(nums1, start1, nums2, start2 + K/2, K - K/2);
    }
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        int K1 = (m + n + 1) / 2;
        int K2 = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, K1) + findKth(nums1, 0, nums2, 0, K2)) / 2.0;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值