Leetcode刷题笔记6

34. 在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

解法一:暴力查找

[1, 2, 3, 3, 3, 4, 5] t = 3
从前往后扫描暴力查找,最坏情况下O(N)

优化
利用数组有序的特性

解法二:朴素二分

[1, 2, 3, 3, 3, 4, 5] t = 3
 L           M        R
在极端情况下时间复杂度会降低,比如如果数组内的元素都一样

优化
[1, 2, 3, 3, 3, 4, 5] t = 3
[           ] [            ]
左边区间小于t,右边区间大于等于t

当发现问题中有二段线的规律时就可以用二分查找

 L             M             R
[-----------------------------] t
               x


查找区间左端点

1. x < t -> left = mid + 1 -> [left,right]

2. x >= t -> right = mid -> [left, right] 

因为 mid有可能是最终结果,所以不能更新到 mid + 1

细节处理:

1. 循环条件
left < right

a. 当 left = right的时候就是最终结果,无需判断
b. 如果判断,就会死循环

第一种情况:[left, right]中有结果
 L                               R
[-----------------------------] t
[            ][                    ]         
           t
因为 left = mid + 1,所以 left是想跳出左边这个区域的
当left和right相遇的位置就是最终结果

第二种情况:全大于t
 L                              R
[-----------------------------] t
只会命中第二个条件,right只会像左移动
直接判断左端点的值是否是t

第三种情况:全小于t
只会命中第一个条件,left只会像右移动
直接判断右端点的值是否是t

2. 求中点的操作
a. left + (right - left) / 2
b. left + (right - left + 1) / 2
当数组的个数是偶数时,用a求中点是靠左,用b是靠右

用b会陷入死循环,因为是右端点

当a。求的是左端点,当命中第一个条件时left会右移,此时相等的话就终止循环
当命中第二个条件时,right会更新到mid,此时两个又相遇了,又终止循环了
所以求中点的操作只能用a才能终止循环

查找区间右端点

[1, 2, 3, 3, 3, 4, 5] t = 3
[                    ][    ]

左边区间全部小于等于t,右边全部大于t

 L             M             R
[-----------------------------] t
                x
1. x <= t,mid此时在左边区域,所以更新left -> left = mid -> [left, right] left和right中继续寻找结果

2. x > t,mid此时在右边区域,更新right -> right = mid - 1 -> [left, right]

处理细节:
1. 循环条件
left < right

2. 求中点的方式
a. left + (right - left) / 2
b. left + (right - left + 1) / 2

b求的是右端点
当最后元素剩2个时
[* *]
 L R

如果使用a,那么mid现在落在L,那么对于第一个条件,left会在这里不动,然后mid又落在这个位置,就会陷入死循环
使用b,mid就在R,当命中第一个条件时,left会移动到right,终止循环
命中第二个条件,right移动到left,也会终止循环

代码:C++

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        // 处理边界情况
        if(nums.size() == 0) return {-1, -1};
        int begin = 0;
        // 1. 二分左端点
        int left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2; // 中点下标
            if(nums[mid] < target) left = mid + 1;
            else right = mid;

        }
        // 结束循环后,left和right相遇
        // 判断是否有结果
        if(nums[left] != target) return {-1, -1};
        else begin = left; // 等于left或right都一样,标记左端点

        // 2. 二分右端点
        // 其实left不需要重置,但是为了保持代码的独立性
        left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2; // 中点下标
            if(nums[mid] <= target) left = mid;
            else right = mid - 1;
        }
        return {begin, right}; // 此时left或者right都存的右端点
    }
};

当下面出现 -1 的时候,上面就 +1
分类讨论的代码,就题论题即可

查找区间左端点的模版

// 查找区间左端点的模版
while (left < right)
{
    int mid = left + (right - left) / 2;
    if (...) left = mid + 1;
    else right = mid;
}

 查找区间右端点的模版

// 查找区间右端点的模版
while (left < right)
{
    int mid = left + (right - left + 1) / 2;
    if (...) left = mid;
    else right = mid - 1;
}

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值