代码随想录算法跟练 | Day1 | 数组基础Part1

个人博客主页:http://myblog.nxx.nx.cn
代码GitHub地址:https://github.com/nx-xn2002/Data_Structure.git

Day1

数组理论基础

定义:数组是存放在连续内存空间上的相同类型数据的集合

重点:

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

二分查找

704. 二分查找

LeetCode题目链接:
https://leetcode.cn/problems/binary-search/

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

思路:
题目中强调了输入的数组是有序且元素都不重复,因此结合题目需求,我们可以优先考虑使用二分法解决,而二分法又根据有效区间划分,常见有左闭右闭左闭右开两种形式,以下是实现代码:

左闭右闭
/**
 * 左闭右闭
 *
 */
public int search(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        //避免溢出
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return -1;
}
  • 时间复杂度:O(logN)
  • 空间复杂度:O(1)
左闭右开
/**
 * 左闭右开
 *
 */
public int search(int[] nums, int target) {
    int left = 0, right = nums.length;
    while (left < right) {
        //避免溢出
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] > target) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return -1;
}
  • 时间复杂度:O(logN)
  • 空间复杂度:O(1)
重点
  1. mid 中间点的计算:

int 类型占用 4 字节,32 比特,即 int 类型的数据范围是:[-2^31 ~ 2^31-1] ,因此当数组数据量较大时,此时 left + right 的结果可能会超出 int 类型的范围,发生溢出问题。因此如果使用以下写法会出现问题:

int mid = (left + right) / 2;

正确写法:

int mid = left + (right - left) / 2;
  1. 二分查找的前置条件:

二分法基于分治思想,而通过具体的代码实现,我们也可以注意到,要想实现二分算法,前置条件就是相关序列必须是有序的

  1. 区间和边界处理:

我们可以看到,二分法可以选择左闭右闭左闭右开两种写法,而这两种写法中,核心的区别就在于对各种边界条件的处理上。我认为最简单的理解记忆方式就是根据不同区间下,leftright 的有效值范围来理解。
比如,对于 while 循环的终止条件,在左闭右闭的情况下,left 取值为 right 的时候,值依然是在有效区间内,而左闭右开left 取值为 right 的时候,left 的值无效,因此分别是使用 <=< 作为终止条件,而循环内的 if 语句也是同理。

移除元素

LeetCode题目链接:
https://leetcode.cn/problems/remove-element/

题目描述:
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要
  • 返回 k

思路:
我们可以很轻松地想到暴力解法,即遍历数组的同时进行判断,如果出现需要移除的元素,则通过循环将该元素后的所有元素前移一位即可,同时维护一个变量记录数组长度就能满足所有需求。这样做能够通过力扣,但是算法的时间复杂度是 O(N^2) ,还有更简便的思路,就是双指针(快慢指针)法
在双指针法中,我们只需要维护快慢两个指针的位置。通过快指针去对数组进行遍历操作,慢指针来标记当前最高有效位的下一位,然后把遍历到的有效元素存入到慢指针指向的位置即可,这种算法因为避免了数组元素批量移动时的遍历操作,时间复杂度是 O(N)

暴力法
/**
 * 暴力解法
 */
public int removeElement1(int[] nums, int val) {
    int len = nums.length;
    for (int i = 0; i < len; i++) {
        if (nums[i] == val) {
            for (int j = i + 1; j < len; j++) {
                nums[j - 1] = nums[j];
            }
            i--;
            len--;
        }
    }
    return len;
}
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
双指针(快慢指针)法
/**
 * 双指针(快慢指针)
 */
public int removeElement2(int[] nums, int val) {
    int slow = 0, fast = 0;
    while (fast < nums.length) {
        if (nums[fast] != val) {
            nums[slow++] = nums[fast];
        }
        fast++;
    }
    return slow;
}
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
重点

这部分知识点相对来说比较简单,通常情况下,暴力解法也能满足需求,但是当出现重复遍历的情况时,其实可以考虑一些常见的优化方式,如这里的双指针法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值