首页 > 其他分享 ># 如何优雅的写出二分

# 如何优雅的写出二分

时间:2024-05-12 22:10:01浏览次数:19  
标签:二分 right target nums int mid 优雅 写出 left

二分查找

二分法查找单个值

题目:给定一个n个有序的(升序)数组nums和一个目标值target,写一个函数搜索nums中target,如果目标值存在返回下标,否则返回-1;
关键词:有序数组,无重复元素
难点:区间选择及循环不变量

  • 在每次循环中要坚持循环不变量原则(名字不重要,怎么做很重要)
      如果我们在定义数组边界的时候选取的区间为左闭右开[left, right),那么在每一个循环中所有划分的子区间都要保证为[left, right)。同理开始定义区间为[left, right],那么每一次的子区间都要保证为[left, right]。
    以左闭右开为例,写出如下代码:
int left = 0, right = nums.size(); 

左闭右闭:

int left = 0, right = nums.size() - 1;
  • 子区间划分及边界选取
      左闭右开:区间为[left, right), 当left == right时区间是没有意义的,如[1, 1)。这个区间内不包含任何一个整数。所以循环条件为while(left < right) 不包含left等于right的情况。
      当nums[mid] < target时,target值在右区间,此时需要更新左边界,假如将区间更新为[mid, right)的话,很明显mid我们是刚刚检查过的nums[mid] < target,所以更新后的区间不应该包含mid,故区间跟新为[mid+1, right);
      当nums[mid] > target时,此时要要更新右边界, 假如将边界更新为[left, mid),nums[mid]检查过了,所以区间不应该包含mid,而[left, mid)区间恰好不包含mid,更新正确
    由此,不难写出如下代码:
class Solution {
public:
    int search(vector<int>& nums, int target) {
        // 定义区间
        int left = 0, right = nums.size();
        // 根据所选区间确定循环条件
        while(left < right) {
            int mid = ((right - left) >> 1) + left;
            if(nums[mid] == target) return mid;
            // 遵守循环不变量原则更新子区间
            if(nums[mid] < target) {
                // 更新左边界
                left = mid + 1;
            } else {
                // 更新右边界
                right = mid;
            }
        }
        return -1;
    }
};

  左闭右闭:由于区间为[left, right], 当left == right时是有意义的,如[1, 1]。区间包含正整数1。所以循环条件为while(left <= right)。
  当nums[mid] < target时,区间为[left, right],由于检查过nums[mid],所以区间更新为[mid+1, right]; 当nums[mid] > target时,更新右边界为[left, mid-1]
代码如下:

class Solution {
public:
    // 定义区间
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        // 根据区间确定循环条件
        while(left <= right) {
            int mid = ((right - left) >> 1) + left;
            if(nums[mid] == target) return mid;
            // 根据循环不变量原则更新子区间
            if(nums[mid] < target) {
                // 更新左边界
                left = mid + 1;
            } else {
                // 更新右边界
                right = mid - 1;
            }
        }
        return -1;
    }
};

二分法查找区间

题目:给定一个非递减的有序整数数组,和一个target,
请找出给定目标值在数组中的开始位置和结束位置,如果不存在目标值,返回[-1, -1]
  经过上一道题,我们已经学会了怎么查找到target值所在的位置了,但是如果数组中有多个值都等于target,那找到的究竟是哪一个呢,当然可以通过返回当前值的坐标,分别向左向右遍历,来找到左边界和右边界,但试想如下场景,num = [ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 ],target = 5,经过第一次二分找到了最中间的5,但是不论向左遍历还是向右遍历都要n/2次,此时算法的时间复杂度退化为o(n)。
  那么该怎么做呢,首先要明确的是此时我们找到了某一个target,那么假如我们要找最左边的target,我们是否可以再此用二分法查找呢,答案是肯定的,只需修改右边界,继续查找左区间是否有满足的值,循环多次直到找到最左边的target,以左闭右开区间为例,不难写出如下代码:

int searchLeft(vector<int>& nums, int target) {
    // 定义区间
    int left = 0, right = nums.size();
    int res = -1;
    // 根据所选区间确定循环条件
    while(left < right) {
        int mid = ((right - left) >> 1) + left;
        // 遵守循环不变量原则更新子区间
        if(nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] == target) {
            right = mid;
        } else {
            // 保存当前满足要求的值的下标, 因为收缩边界后有可能没有满足要求的值了,所以要提前保存
            res = mid;
            // 收缩右边界查找左区间是否有满足要求的值
            right = mid;
        }
    }
    return res;
}

同理查找右边界的代码为:

int searchRight(vector<int>& nums, int target) {
    // 定义区间
    int left = 0, right = nums.size();
    int res = -1;
    // 根据所选区间确定循环条件
    while(left < right) {
        int mid = ((right - left) >> 1) + left;
        // 遵守循环不变量原则更新子区间
        if(nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] == target) {
            right = mid;
        } else {
            res = mid;
            // 收缩左边界查找左区间是否有满足要求的值
            left = mid + 1;
        }
    }
    return res;
}

整理之后的完整代码:思路来自leetcode喜刷刷

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        return {searchBound(nums, target, "left"), searchBound(nums, target, "right")};
    }
    int searchBound(vector<int>& nums, int target, const string& bound) {
        int left = 0, right = nums.size();
        // 将res初始化为-1,若查找过程中没有出现等于target的值,则res没有修改过返回-1
        int res = -1;
        while(left < right) {
            int mid = ((right - left) >> 1) + left;
            if(nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid;
            } else {
                res = mid;
                // 找左边界,所以更新有边界,查找mid左边是否有等于target的值
                if(bound == "left") right = mid;
                // 找右边界,所以更新左边界,查找mid右边是否有等于target的值
                if(bound == "right") left = mid + 1;
            }
        }
        return res;
    }
};

标签:二分,right,target,nums,int,mid,优雅,写出,left
From: https://www.cnblogs.com/cscpp/p/18188262

相关文章

  • 二分图(例题)
    https://www.cnblogs.com/kuangbiaopilihu/p/18184536$\quad$这里不再介绍二分图的基础知识,只是一些例题的解释。$\quad$当然,这道题可以用二分+并查集来解决。但这是二分图专辑,所以介绍一下二分图做法。$\quad$首先如果两个罪犯之间有仇恨,那么当他们不在同一......
  • 洛谷 P2824 [HEOI2016/TJOI2016] 排序(二分,线段树)
    传送门解题思路据说是经典思路:把多次排序转化成二分+01序列。首先二分所求位置的数字是啥,将大于mid的数字变成1,将小于等于mid的数字变成0。这样在排序的时候就相当于统计区间里的1的个数(区间和),然后区间全部变成0或者1。也就是区间修改,区间求和,线段树可以实现。AC代码#inclu......
  • 匈牙利算法模板(二分图)
    booldfs(intnow){for(inti=h[now];i;i=nxt[i]){intt=to[i];//这里不用考虑会有回到父结点的边的问题//因为每次都是从左部找邻接点if(!vis[t]){vis[t]=true;//如果邻接点t是非匹配点,则找到一条增广路,匹配......
  • 加练日记2-二分,双指针,排序
    二分模板 #include<bits/stdc++.h>usingnamespacestd;usingll=longlong;constllMOD=998244353;lln,m;constllN=2e5+9;lla[N];llv[N];intf(llmid){ llans=0,pre=-1e9; for(inti=1;i<=n;i++){ if(a[i]-pre>=mid)ans++,pre=a[i......
  • 蓝桥杯-递增三元组(三种解法,二分, 双指针, 前缀和)
    给定三个整数数组A=[A1,A2,…AN],B=[B1,B2,…BN],C=[C1,C2,…CN],请你统计有多少个三元组(i,j,k)满足:1≤i,j,k≤NAi<Bj<Ck输入格式第一行包含一个整数N。第二行包含N个整数A1,A2,…AN。第三行包含N个整数B1,B2,…BN。第四行包含N个整数C1,C2,…CN。输出格......
  • 华为云发布CodeArts IDE for Python,极致优雅云原生开发体验
    近日,华为云正式发布CodeArtsIDEforPython,这是一款内置华为自主创新的Python语言服务,提供智能编程、灵活调试能力的可扩展桌面开发工具,为华为云开发者提供卓越Python编码体验。Python作为一种编程语言,广泛用于Web应用程序、软件开发、数据科学和机器学习(ML)。Python以其优......
  • 二分图
    1.二分图的概念二分图,又称二部图,英文名叫Bipartitegraph。如果一张无向图的\(N\(N≥2)\)个节点,可以分成A、B两个非空集合,其中\(A∩B=Φ\),并且在同一集合内的点之间都没有边相连,那么称这张无向图为一张二分图。A、B分别称为二分图的左部和右部。如下图所示。2.二......
  • 更优雅的使用Gson解析Json
     Gson背靠Google这棵大树,拥有广泛的社区支持和相对丰富的文档资源,同时因其简单直观的API,一直以来基本稳坐Android开发序列化的头把交椅(直到Google宣布kotlin成为Android开发的首选语言)。本文对Gson的使用及主要流程做下分析。Gson的基本使用Gson依赖 kotlin复制代码d......
  • Python 如何优雅的操作 PyMySQL
    一、PyMysql在使用Python操作MySQL数据过的过程中,基本的增删改查操作如何更加高效优雅的执行。这里将以PyMySQL为例,介绍一下如何使用Python操作数据库。Python对MySQL数据库进行操作,基本思路是先连接数据库Connection对象,建立游标Cursor对象,然后执行SQL语句对数据库进行操作......
  • 如何写出整洁的代码
    原文cnblogs.com/liuboren/p/17017421.html0.前言本篇文章是<<代码整洁之道>>的学习总结,通过这篇文章你将了解到整洁的代码对项目、公司和你的重要性,以及如何书写整洁的代码.通过命名、类、函数、测试这四个章节,使我们的代码变得整洁.1.为什么要保持代码整洁?不整洁的代......