代码随想录刷题训练营【第一天】

2,311 阅读4分钟

今日任务:

  • 数组理论基础
  • 二分查找
  • 移除元素

今日学习的文章 && 和视频

二分查找

看到题目的第一眼想法,就是之前做过这类题目,然后看了看视频发现还是有一些需要注意的事项,比如,对数组的操作,是左闭右开,还是左闭合右边闭合,一般做的题目就是左闭右闭,还有的题目是左闭右开,这时候数组的索引应该去怎么样进行设置,开始我还把这两个操作给搞混了,以为是因为可以传入左闭右开的数组,但是群友告诉我,它在方法内进行索引的设置属于逻辑操作,而数组是一个物理结构,我们左闭右开,和左闭右闭的目的都是为了通过二分遍历数组中的所有元素,最终找到符合条件的值,以前不知道 left + ((right - left) >> 2) 这样写的作用,这次知道了它是为了防止相加之后的数值溢出问题,然后还有位运算,它是 >> 1 表示除以 2。 下面是左闭右闭的代码,如果 mid 值大于 target 的话,就将 left 设置为 mid + 1,因为我们不需要对 mid 值在进行判断,他已经和 target 不等于 target 了,如果 mid 值小于 target 的话,就将它设置为 mid - 1 同理!

leftIndex = 0;
rightIndex = nums.length - 1;
while (leftIndex <= rightIndex) {
    int mid = left + ((right - left) >> 1)
    if (nums[mid] == target) {
        return mid;
    }
    if (nums[mid] > target) {
        left = mid + 1;
    } else {
        right = mid - 1;
    }
}

左闭右开和这个代码类似 不过

leftIndex = 0;
rightIndex = nums.length;
while (leftIndex < rightIndex) {
    int mid = left + ((right - left) >> 1)
    if (nums[mid] == target) {
        return mid;
    }
    if (nums[mid] > target) {
        left = mid + 1;//左边这个可以取到,是闭区间,因此不能返回 mid
    } else {
        right = mid;//左边这个是因为它是开区间,取不到,因此就把他返回
    }
}

移除元素

移除元素就是给定一个数组,然后指定一个值,如果存在这个数组中将他们删除掉,最后返回剩余数组的长度 有两种解法,我第一次做的时候没看题目,用了一个二分查找,找到对应的值,然后再创建了一个数组,根据找到的索引将其它的值存入到新的数组中,但是看到题目之后发现这个解法不行,因为传入的数组需要删除的元素,可以是多个重复元素,二分只能找到一个,而且要求空间的复杂度为 O(1) 这意味着我们只能在原数组上进行操作,因此我看了看解法有两种, 一种是暴力求解(这是我先自己写的,然后一直报错,参考代码随想录的代码之后又写的) 刚开始报错是因为,没有设置 nums.length - index 导致它这个外层的 for 循环在执行到最后之后找到我们之前移动到最后的元素,会再次将 index++ 所以需要我们手动设置边界相当于将 size--,移除元素之后将 for 循环的范围缩小,还有一点就是移除元素之后需要将 i--,因为我们在数组中移除元素是将后面的元素覆盖到前面来实现的这样的话 比如 [1, 2, 2, 3] 删除 2,第一个 2 删除之后数组变为 [1, 2, 3] 但是此时在执行 for 循环的话 i 已经为 2 了(如果不修改的话)那么往下就是 3 不是我们要找的,最后返回长度 3,这样就错了,因为漏掉了后面的元素,他移动到前面来了,因此我们在移动元素到前一位之后需要将 i--

public static int forceSolve(int[] nums, int target) {
    int index = 0;
    for (int i = 0; i < nums.length - index; i++) {
        if (nums[i] == target) {
            index++;
            //将之后的元素往前移动一位
            for (int j = i; j < nums.length - 1; j++) {
                nums[j] = nums[j + 1];
            }
            i--;
        }
    }
    return nums.length - index;
}

另一种是快慢指针: 感觉这个有点类似快速排序里面的某段代码,但是我已经忘记快排的代码了 代码倒是挺简单的,但是不知怎么回事我总觉得再遇到这类题目我还是想不到用快慢指针!

public static int pointer(int[] nums, int target) {
    int slowPointer = 0;
    for (int fastPointer = 0; fastPointer < nums.length; fastPointer++) {
        if (nums[fastPointer] != target) {
            nums[slowPointer] = nums[fastPointer];
            slowPointer++;
        }
    }
    return slowPointer;
}

还有一个解法是双指针的方式,我看不懂,现在也不打算看懂!

搜索插入位置

这个我一直没看懂为什么会是这个样子!

 public int searchInsert(int[] nums, int target) {
        int n = nums.length;

        // 定义target在左闭右闭的区间,[low, high]
        int low = 0;
        int high = n - 1;

        while (low <= high) { // 当low==high,区间[low, high]依然有效
            int mid = low + (high - low) / 2; // 防止溢出
            if (nums[mid] > target) {
                high = mid - 1; // target 在左区间,所以[low, mid - 1]
            } else if (nums[mid] < target) {
                low = mid + 1; // target 在右区间,所以[mid + 1, high]
            } else {
                // 1. 目标值等于数组中某一个元素  return mid;
                return mid;
            }
        }
        // 2.目标值在数组所有元素之前 3.目标值插入数组中 4.目标值在数组所有元素之后 return right + 1;
        return high + 1;
    }

然后左闭右开的话 result 不需要加一,我已经打算背代码了!

我的收获

我发现数组的题好难,我啥都不会!