二分查找:力扣34.在排序数组中寻找第一个和最后一个位置(c++)

时间复杂度

假设运气最差时需要查找k次,每次寻找的区间会被缩小到 1 / 2 ; 1 / 4 ; 1 / 8 ; 1 / 16 ; 1 / 2 k 1/2;1/4;1/8;1/16;1/2^k 1/2;1/4;1/8;1/16;1/2k。最终找到时是范围缩小到1了,所以是 n / ( 2 k ) = 1 n/(2^k)=1 n/(2k)=1,所以最终 k = l o g 2 n 简写为 l o g n k=log_2n简写为logn k=log2n简写为logn

所以有 l o g n logn logn这个复杂度要求的可以去考虑二分法

34.在排序数组中寻找第一个和最后一个位置
具体实现

1.首先想好函数和对应的功能,并且考虑一些边界情况

应当将二分查找的逻辑放到另外的函数中去,左边界用二分查找,右边界如**果用线性查找的话会不会更快呢?**不一定,当target值的重复次数不多的时候会比重新二分查找快,但是如果target的数量为n,那么时间复杂度会上升到 o ( n ) o(n) o(n),所以做两次二分查找才能达到题目中的 o ( l o g n ) o(logn) o(logn)的要求。 2 l o g n 的复杂度也是 l o g n 2logn的复杂度也是logn 2logn的复杂度也是logn

边界情况:nums=[],即长度为0直接返回[-1,-1]

原理就是不断的移动区间,让left=right从而锁定一个数

vector<int> searchRange(vector<int>& nums, int target) {
if (nums.size()==0) return vector<int> {-1,-1};  //不要写[-1,-1]因为要求要是一个vector;
int begin_position = binarySearch_begin(nums,target);
if (begin_position==-1) return vector<int> {-1,-1};
int end_position = binarySearch_end(nums,target);
return vector<int> {begin_position,end_position};
}

2.编写开始位置的二分查找

要注意:

  • 退出条件 可以是left=right 写作while(left<right),或者left>right 写作while(left<=right)
  • 二分法的移动规则,移动后区间的开闭要思考清楚
  • 算法是否保证能找到target,如果不能请加入判断
int binarySearch_begin(vector<int>& nums,int target){  // 为什么是&?
    //开始位置的二分查找
    // 1.初始化左右边界
    int left=0,right=nums.size()-1;
    // 2.确定退出二分查找条件:左右指针与mid相等
    while(left<right){ // 确保在退出循环的时候left一定等于right
        int mid=(left+right)/2;   // 或者用位移
        // 注意是nums[mid],而不是mid说明区间需要右移,left移动[mid+1,right]
        if(nums[mid]<target) left=mid+1; 
        // 说明 第一个 target肯定不在mid右边,即target位置<=mid,所以区间变成[left,mid]
        else if(nums[mid]==target)  right=mid;
        // nums[mid]>target,说明target肯定不在mid的右边,变为[left,mid-1]
        else right=mid-1;
    }
    // 条件只能保证left==right,不能保证一定找到了target
    if(nums[left]==target) return left;
    else return -1;
}

3.结束位置的二分查找

要注意:

  • 如果转移的条件是: left=mid 那么mid的条件就是mid=(left+right+1)/2

​ 这个是在实际跑代码的时候才注意到的,例如left=4,mid=5会进入死循环,nums={5,7,7,8,8,10},target=8的情况下。因为left此时等于mid,(right+left)/2也等于mid,所以就一直循环,要加一的话才可以。

所以在刷题时确定开闭区间,要思考如果最后区间只剩下一个数或者两个数,自己的写法是否会陷入死循环,如果某种写法无法跳出死循环,则考虑尝试另一种写法。

4.完整代码

class Solution {
public:
    int binarySearch_begin(vector<int>& nums,int target){  // 为什么是&?
        //开始位置的二分查找
        // 1.初始化左右边界
        int left=0,right=nums.size()-1;
        // 2.确定退出二分查找条件:左右指针与mid相等
        while(left<right){ // 确保在退出循环的时候left一定等于right
            int mid=(left+right)/2;   // 或者用位移
            // 注意是nums[mid],而不是mid说明区间需要右移,left移动[mid+1,right]
            if(nums[mid]<target) left=mid+1; 
            // 说明 第一个 target肯定不在mid右边,即target位置<=mid,所以区间变成[left,mid]
            else if(nums[mid]==target)  right=mid;
            // nums[mid]>target,说明target肯定不在mid的右边,变为[left,mid-1]
            else right=mid-1;
        }
        // 条件只能保证left==right,不能保证一定找到了target
        if(nums[left]==target) return left;
        else return -1;
    }

    int binarySearch_end(vector<int>& nums,int target){  // 为什么是&?
        //结束位置的二分查找
        // 1.初始化左右边界
        int left=0,right=nums.size()-1;
        // 2.确定退出二分查找条件:左右指针与mid相等
        while(left<right){ // 确保在退出循环的时候left一定等于right
            int mid=(left+right+1)/2;   // !!如果left=mid则mid=(l+r+1)
            // 注意是nums[mid],而不是mid说明区间需要右移,left移动[mid+1,right]
            if(nums[mid]<target) left=mid+1; 
            // 说明 最后 target肯定不在mid右边,即target位置>=mid,所以区间变成[mid,right]
            else if(nums[mid]==target)  left=mid;
            // nums[mid]>target,说明target肯定不在mid的右边,变为[left,mid-1]
            else right=mid-1;
        }
        // 条件只能保证left==right,不能保证一定找到了target
        return right;
        // 不需要,因为找开始位置的时候验证过了,else return -1;
    }


    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.size()==0) return vector<int> {-1,-1};  //不要写[-1,-1]因为要求要是一个vector;
        int begin_position = binarySearch_begin(nums,target);
        if (begin_position==-1) return vector<int> {-1,-1};
        int end_position = binarySearch_end(nums,target);
        return vector<int> {begin_position,end_position};
    }
};

也可以写成:

int lower_bound(vector<int> &nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
mid = (l + r) / 2;
    if (nums[mid] >= target) {
        r = mid;
        } else {
        l = mid + 1;
        } }
    return l;
}
// 辅函数
int upper_bound(vector<int> &nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
    mid = (l + r) / 2;
    if (nums[mid] > target) {
    	r = mid;
    } else {
    	l = mid + 1;
	}	
}

### C++ STL 常用于力扣刷题的数据结构与函数示例 #### 数据结构部分 C++ 的 Standard Template Library (STL) 提供了许多高效且易于使用的数据结构,这些工具非常适合解决 LeetCode 上的各种算法问题。 1. **向量 `std::vector`** 向量是一个动态数组,能够自动调整大小并提供随机访问功能。它是许多题目中最常用的基础容器之一。 ```cpp std::vector<int> vec; vec.push_back(10); // 添加元素到末尾 int value = vec.front(); // 访问第一个元素 ``` 2. **哈希表 `std::unordered_map`** 无序映射提供了平均时间复杂度为 O(1) 的键值存储检索能力,在处理频率统计等问题时非常有用。 ```cpp std::unordered_map<int, int> freqMap; freqMap[5]++; // 插入或更新键对应的计数值 if(freqMap.find(key)!=freqMap.end()) { /* 查找 */ } ``` 3. **集合 `std::set` / 多重集 `std::multiset`** 这些基于平衡二叉树实现的有序集合允许高效的去重以及范围查询操作。 ```cpp std::set<int> uniqueSet{7,8}; auto it = uniqueSet.insert(9).first; // 插入新元素并获取迭代器位置 bool exists = uniqueSet.count(7)>0 ? true : false ;// 判断是否存在某个特定值 ``` 4. **堆(优先队列)`std::priority_queue`** 使用大顶堆或者小顶堆来维护一组具有最高/最低优先级的对象列表,适用于求解最大最小K个数之类的问题。 ```cpp std::priority_queue<int,std::vector<int>,std::greater<>> minHeap;// 小根堆定义方式 while(!minHeap.empty()){ cout<<minHeap.top()<<" "; // 输出当前堆顶元素 minHeap.pop(); } ``` 5. **双端队列 `std::deque`** 双端队列为两端都支持常数级别插入删除效率的理想选择,尤其适合滑动窗口类场景应用。 ```cpp deque<int> dq={1,2,3}; dq.emplace_front(-1); dq.emplace_back(4); ``` 6. **字符串流 `std::stringstream`** 字符串流可以帮助我们轻松完成格式化输入输出转换工作。 ```cpp string s="hello world"; stringstream ss(s); string word; while(ss>>word){ // 对每个单词做进一步处理... } ``` #### 排序与搜索函数 除了上述提到的标准模板库(STL),还有一些通用的功能性方法也值得了解: 1. **快速排序 `std::sort()` 或者自定义比较逻辑版本 `std::stable_sort()`** 能够方便快捷地对序列进行升序降序排列甚至更复杂的定制需求满足。 ```cpp vector<pair<int,int>> items={{3,'a'},{1,'b'}}; sort(items.begin(),items.end(), [](const pair<int,char>& a,const pair<int,char>& b)->bool{return a.first<b.first;} ); ``` 2. **二分查找 `std::binary_search`, `std::lower_bound`, `std::upper_bound`** 当目标区间已经处于有序状态时候,则可以通过这几个关联密切的方法加速定位过程。 3. **累积计算相关 `std::accumulate`** 方便地累加一系列数值总合或者其他形式的结果汇总运算。 ```cpp double avg= accumulate(v.begin(),v.end(),0)/double(v.size()); ``` 通过以上列举出来的几种核心组成部分及其简单实例演示可以看出,C++语言本身自带的强大工具箱确实极大地方便了开发者们构建解决方案流程. ```cpp #include <bits/stdc++.h> using namespace std; int main(){ // Example usage of some structures and algorithms discussed above. } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值