算法训练第一天 | 数组理论基础 704. 二分查找,27. 移除元数组理论基础,704. 二分查找,27. 移除元素

文章主要用于

数组理论基础

数学基础

首先要知道数组在内存中的存储方式:数组是存放在连续内存空间上的相同类型数据的集合

数组可以方便的通过下标索引的方式获取到下标下对应的数据 ,这个数据结构的特征就在于用下标指向数据的等同对应关系。

需要两点注意的是:

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的

另一方面,正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
数组的元素是不能删的,只能覆盖。,没有办法直接将某个元素从数组中单纯拿出来,只能将它所对应的下标和内容都修改来达到删除的目的

二维数组是简单数组操作中相较丰富的一类,特点在于有两个下标,也就是说定位的方式相较于一维数组翻了一倍;连续性对于不同的语言可能不同,在 C++中二维数组的存储地址依旧是连续的。

从语言角度来看待数组,在 Go 中与数组的概念最相近的就是切片,切片对象是一个地址,指向底层的数组,所有Go的数组对象都是常量固定的。

704. 二分查找

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

示例 1: 输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums
中并且下标为 4

示例 2: 输入: nums = [-1,0,3,5,9,12], target = 2 输出: -1 解释: 2 不存在 nums
中因此返回 -1

提示: 你可以假设 nums 中的所有元素是不重复的。 n 将在 [1, 10000]之间。 nums
的每个元素都将在 [-9999, 9999]之间。

题目中的关键字眼是:** n 个有序元素(生序)**
也就是说数组的元素之间是存在着大小关系的,这里面存在着一个简单的逻辑筛选规则,即 target 大于 A时, 所有比A小的数组元素就可以都不用再比较一次了。

二分比较在这个情况下起到的作用就在快速遍历数组内具有代表性大小关系的数字,target 每次与中间值比较就能够直接丢弃一半的比较任务量

依照这个想法,我们实现这个功能就需要有两段:

  • 找到数组的中间值
  • target 与数组中间值比较,大于则丢弃较小的那部分===》 运用双指针数据结构可以灵活操作数组的边界,实现对连续数组元素的增删

实际上到这里对于数组二分的使用就已经完成了,但这道题目在实现上却存在着两处难点:

1. 计算中间值

这看起来是非常简单的小学数学题,计算两数的中间值完全可以使用 (A+B)/2
但这样会出现两个问题:
A+B的除法四舍五入存在着语言上的差别,2.5 在Go 计算时候全都舍去算是低位,这就使得边界条件非常的不稳定,另一方面这样叠加的方式可能会直接溢出,所以在涉及到边界条件或者是具体的数值计算的时候,除法不能够作为最外层的计算,需要尽量替换为可以获得稳定结果的方式: ==位运算==

这里的除二应该表示为 >> 1,结果右移一位

同时为了能够保证计算过程中的大小不会溢出,将上述除法运算变成偏移值的加和,最后结果为:
==Mid = L + ((R-L)>>1) ==

但并不是所有的除法都可以直接表示为位运算的形式。位运算(如左移、右移)通常用于处理整数,而除法操作可以应用于整数和浮点数。

对于整数除法,如果除数是2的幂次,那么除法可以表示为位运算。例如,将一个整数除以2等于将该整数右移一位,将一个整数除以4等于将该整数右移两位,依此类推。然而,对于非2的幂次的除数,整数除法通常不能直接表示为位运算。

对于浮点数除法,位运算并不适用。浮点数的表示和计算与整数完全不同,它们遵循IEEE 754标准,使用尾数、阶码和符号位来表示实数。对于浮点数的除法,需要使用专门的浮点运算单元(FPU)或软件实现的浮点运算库来进行计算。

2. 双指针的边界条件

三种类型以及他们的模版:

2.1 左闭右开 指的是在这一次的循环中,会使用涉及(包含)的数,也就是说最左边的数会取,但是最右边的数不会取

// left 取0, right 本应该是 len(nums)-1,但是在这个情况下直接取len(nums)就是超出数组的边
left,right := 0,len(nums)
    for left < right {
        center := getnum(left,right)
        // 相等的情况就是需要结果,直接返回
        if nums[center] == target {
            return center
        }
        // 左闭右开,所以优先判断的是是否丢弃右边的数
        if nums[center] > target {
            right = center
        }else{
        // 左闭右开,所以左边的数不符合所以需要加一,以达到丢弃这个边界值的作用
            left = center + 1
        }
    }

2.2 左开右闭

2.3 左闭右闭

最后我们可以得到答案是:

func search(nums []int, target int) int {
    left,right := 0,len(nums)
    for left < right {
        center := getnum(left,right)
        // 相等的情况就是需要结果,直接返回
        if nums[center] == target {
            return center
        }
        if nums[center] > target {
            right = center
        }else{
            left = center + 1
        }
    }
    // 所有情况都找过了,没有对应的 center,所以直接输出
    return -1
}

func getnum(left int, right int) int {
    return ((right-left)>>1) + left
}

27. 移除元素

https://leetcode.cn/problems/remove-element/

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明: 为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。 for
(int i = 0; i < len; i++) {
print(nums[i]); }

示例 1: 输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2: 输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示: 0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100

这也是一道双指针的题目,核心在是在于控制两个指针实现对数组元素的遍历
那么既然是双指针那必然就是和分类相关,和二分的思想相关,这道题目需求的是将匹配中的元素从数组当中删除
我们使用分类的思想来看待这个问题,如果说将等于 val 的数化归为一类,不等于的数划归为另一类,然后将等于的那一类的位置全部都放到数组的右边,就像是挑选衣服一样。
那么我们就可以得到这道题目的关键词: 双指针匹配,交换位置

按照这个思路我们来实现代码

func removeElement(nums []int, val int) int {
  leftIndex := 0
  rightIndex := len(nums) - 1
  for leftIndex <= rightIndex {
    if nums[leftIndex] == val {
      nums[rightIndex], nums[leftIndex] = nums[leftIndex], nums[rightIndex]
      rightIndex--;
    } else {
      leftIndex++
    }
  }
  return leftIndex
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值