首页 > 其他分享 >day1 二分查找(及其进阶)和移除元素的双指针法

day1 二分查找(及其进阶)和移除元素的双指针法

时间:2024-07-17 14:18:12浏览次数:10  
标签:进阶 nums int lo 元素 day1 查找 移除 target

基础概念

算法的单调性:问题的规模随着算法的推进不断缩减 (如704中开始的查找区间是[lo,hi),随着循环的进行,问题规模确实在不断的缩小)
算法的不变性:在初始状态下自然满足,当问题的有效规模缩减为0时,不变性应该随即等于正确性。(如704中开始的查找区间是[lo,hi),最终要么直接命中,要么缩减为0后未命中)

数组

连续内存空间,查找o(1),增删O(n) 由于需要移动后面的元素。

任务

704 二分查找

题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

思路:

由于数组有序,遂采用二分查找而非顺序查找,使用界桩lo和hi表示查找区间,mi的值为lo和hi的中间,用来缩减这个区间的大小。如果查找的值小于arr[mi],则缩减为左半区间,否则缩减为右半区间,直到在搜索过程中命中target,若直到循环结束也没有命中(缩减到空集),说明没有找到,则返回-1。注意这里的界桩表示的是左闭右开,更符合个人的思路,最右侧表示实际元素的后一个元素,与c++迭代器中的end类似,符合 != 语义,表示空集时也更符合直觉 [x,x)。核心就是当前待查找的范围

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        lo = 0
        hi = len(nums)
        while lo<hi:
            mi = lo + (hi - lo)//2
            if target < nums[mi]:
                hi = mi
            elif target > nums[mi]:
                lo = mi + 1
            else:
                return mi
        return -1

如果采取闭区间语义,则实际很类似,有一些循环条件和查找范围中细小的改变,只要保证每处语义符合要求即可。但是个人还是推荐左闭右开的语义去编程

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        lo = 0
        hi = len(nums) - 1 
        while lo<=hi:
            mi = lo + (hi - lo)//2
            if target < nums[mi]:
                hi = mi -1
            elif target > nums[mi]:
                lo = mi + 1
            else:
                return mi
        return -1

语言tips 注意python3中的floor除法与之前版本的区别

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。

思路:

1.暴力解法 使用双层循环,外层循环遍历每一个元素,当遍历到符合移除条件的元素时,用后面的值覆盖前面的值,覆盖完成后,数组的size减1,注意i也减1指向新的元素。时间复杂度为O(n^2)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        size = len(nums)
        i = 0
        while i < size:
            if nums[i] == val:
                for j in range(i+1,size):
                    nums[j-1] = nums[j]
                size-=1
                i-=1 # 这里的语句也可以改为continue,表示继续检查当前位置
            i+=1
        return size

2.双指针法: slow指针表示当前归入最终数组中的元素的末尾哨兵(右开),fast指针用来遍历数组,符合条件的就加入到slow所表示的范围中,时间复杂度优化为o(n)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        size = len(nums)
        slow = 0 
        fast = 0
        while fast != size:
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow+=1    
            fast+=1
        return slow

重要!!! 语言tips python中的for循环和C++ C#等中的不同!在Python中,for循环中的循环变量i在每次迭代开始时都会被重新赋值为下一个值,而不会保留上次循环中的修改。因此遇到需要在循环中修改循环变量的情况需要用while循环处理

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

思路:

找到插入位置,即将数组分为 [0,lo)中的元素均小于target,而[hi,size)中的元素都大于等于e,即算法的不变性是 左边是<target的区间 ,中间是未知待拓展区间,右边是>=target的区间,初始是左右两边的区间均为空,整个区间都是未知区间;最终,随着算法的迭代,区间变为前面描述的 < 和 e<= 。即lo和hi最终相等等于右边区间的开始第一个即不小于target的第一个元素的索引。核心是将区间划分为 < target 和target <= 的两端 [0,lo) [hi,size)

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        lo = 0
        hi = len(nums)
        while(lo<hi):
            mi = lo+ (hi-lo)//2
            if target <= nums[mi]:
                hi = mi
            else: lo = mi+1
        return lo

注意虽然看起来和704的代码很相似,只有边界条件上细微的差别,但实际上的思路是完全不一样的。对于查找失败只用返回-1的情况来说,核心思路是缩小查找的范围,直到最终找到或者为空集。而对于本例来说,核心思路是将原本未知的区间划分成由target分界的两端,最终因为算法的不变性和单调性,一步步迭代,找到分界点。

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

如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

思路

思路与上面的35相同,35是将区间分为左边都< 右边都>=target,因此如果数组中存在target,则右边第一个就是=target的第一个值。另外,我们将区间分为左边都<= 右边都>target,因此,如果存在target,左边的最后一个就是数组中最后一个等于target的数。此外,需要处理一些特殊的边界情况,判断实际是否存在等。这里用了两个while循环去找到最前面和最后面的target的索引。

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if not nums: return [-1,-1]
        lo = 0
        hi = len(nums)
        while(lo<hi):
            mi = lo + (hi - lo)//2
            if target <= nums[mi]:
                hi = mi
            else:
                lo = mi + 1
        minLargeIndex = lo
        if minLargeIndex >= len(nums): return [-1,-1]
        if nums[minLargeIndex] != target:  return [-1,-1]
        lo = 0
        hi = len(nums)
        while(lo<hi):
            mi = lo + (hi - lo)//2
            if target < nums[mi]:
                hi = mi
            else:
                lo = mi + 1
        maxLessIndex = lo - 1
        return [minLargeIndex,maxLessIndex]

标签:进阶,nums,int,lo,元素,day1,查找,移除,target
From: https://www.cnblogs.com/haohaoscnblogs/p/18307266

相关文章

  • Python进阶(1)--面向对象
    文章目录面向对象类的定义类的构造实例化一个类三大特点封装私有属性继承和多态继承继承的作用多态总结总结面向对象Python是一种广泛使用的解释型、高级编程、通用型编程语言,它以其简洁、易读以及面向对象的特性而闻名。面向对象编程(Object-OrientedProgramm......
  • 实训day1
    JDK安装环境变量配置JAVA_HOMEC:\ProgramFiles\Java\jdk-21Path添加%JAVA_HOME%\bin几个插件代码样式主题相关:AtomMaterialIconsOneDarkTheme建项目->建包->建子包->建类,写代码包package区分、管理类,项目分割成不同的模块pan.baidu.comtieba.baidu.comwww.baidu......
  • idea打开/导入maven项目 + 移除
    ——————如何导入方法1————右侧maven——》点击加号   找到要但如项目的pom.xml文件——》ok 方法2————file——》projectstructure……modeles——》加号 importmodule  找到要导入项目的pom.xml文件——》ok 右下角apply——》ok ......
  • 2024信友队蓝润暑期集训提高1班②Day1
    知识总结原理:每一步都采取局部最优解,取到最终的最优解。常见时间复杂度$O(n)$或$O(nlog(n))$后者一般带排序。用法:通过数据规模和题目信息联想贪心算法常见时间复杂度猜测结论验证合理性​-归纳法​-反证法(相邻交换法):如果交换方案中相邻的两个元素/任意......
  • JS进阶总结
    JS进阶作用域作用域规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问;分为局部作用域和全局作用域局部作用域局部作用域分为函数作用域和块作用域1)函数作用域:函数内部声明的变量,在函数外部无法被访问函数执行完毕后,函数内部的变量实际被清空<sc......
  • ComfyUI进阶:Comfyroll插件 (一)
    ComfyUI进阶:Comfyroll插件(一)前言:学习ComfyUI是一场持久战,而ComfyrollStudio是一款功能强大的自定义节点集合,专为ComfyUI用户打造,旨在提供更加丰富和专业的图像生成与编辑工具。借助这些节点,用户可以在静态图像的精细调整和动态动画的复杂构建方面进行深入探索。ComfyrollS......
  • 深度学习全景进阶:Python深度学习
    近年来,伴随着以卷积神经网络(CNN)为代表的深度学习的快速发展,人工智能迈入了第三次发展浪潮,AI技术在各个领域中的应用越来越广泛。注意力机制、Transformer模型(BERT、GPT-1/2/3/3.5/4、DETR、ViT、SwinTransformer等)、生成式模型(变分自编码器VAE、生成式对抗网络GAN、扩散模型Di......
  • android学习day1
    1.android系统框架android大致可分为四层架构:linux内核层,系统运行库层,应用框架层和应用层1.1linux内核层为android设备的各种硬件提供底层驱动,如显示驱动,音频驱动,wifi驱动,电源管理等。1.2系统运行库层通过一些c/c++库为android系统提供了主要的特性支持,如SQLite库提供数......
  • Day11(二叉树) | 二叉树的递归遍历 二叉树的迭代遍历 二叉树的统一迭代法 二
    二叉树的递归遍历终于来到了递归!!!递归是进入动态规划的第一步,有部分的递归完全可以写成动态规划!这里可以移步到左程云的视频观看.递归的步骤:确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返......
  • Day10(栈与队列) | 150. 逆波兰表达式求值 239. 滑动窗口最大值 347.前 K 个高频元
    150.逆波兰表达式求值给你一个字符串数组tokens,表示一个根据逆波兰表示法表示的算术表达式。请你计算该表达式。返回一个表示表达式值的整数。注意:有效的算符为'+'、'-'、'*'和'/'。每个操作数(运算对象)都可以是一个整数或者另一个表达式。两个整数之间的除法总是......