[Day1]代码随想录

95 阅读4分钟

今日内容

数组理论基础,704. 二分查找,27. 移除元素
代码随想录链接:programmercarl.com/%E6%95%B0%E…

数组理论基础

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

  • 数组的下标都是从0开始
  • 数组内存空间的地址是连续的
    因此增删数组中间的元素后,往往需要改变其他元素的地址
    数组的元素是不能删除的,只能覆盖

二维数组在C++上的存储是连续的,Java上是随机开辟内存空间存放一维数组

704,二分查找

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

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

按照区间的定义,二分查找可以分为左右都闭合左闭合右不闭合两种思路,但都是把用过的点都去掉

1.左右都闭合
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

代码如下

class Solution {
    public int search(int[] nums, int target) {
    if(target < nums[0] || target > nums[nums.length - 1]){// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
        return -1;
    }
    int left = 0;
    int right = nums.length - 1;
    while(right >= left){
        int mid = left + ((right - left) / 2);//防溢出
        if (nums[mid] < target)
            left = mid + 1;
        else if (nums[mid] > target)
            right = mid - 1;
        else
            return mid;
        }
    return -1;
    }
}

两处细节:开头避免多次循环运算和mid的计算方式要防止溢出(代码注解)


2.左闭合右不闭合
这种情况下,只需要对上一种情况进行三处改动(不要死记硬背),代码如下:

class Solution {
    public int search(int[] nums, int target) {
    if(target < nums[0] || target > nums[nums.length - 1]){// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
        return -1;
    }
    int left = 0;
    int right = nums.length;//第一处改动
    while(right > left){//第二处改动
        int mid = left + ((right - left) / 2);//防溢出
        if (nums[mid] < target)
            left = mid + 1;
        else if (nums[mid] > target)
            right = mid;//第三处改动
        else
            return mid;
        }
    return -1;
    }
}
  • 第一处改动:当区间右边是开区间时,right本身指向的元素不会被计算,为了保证所有元素都被计算,right要加一位,指向数组最后一位的后面一位。
  • 第二处改动:因为right是开的(虚的),所以left等于right也就没有元素了。
  • 第三处改动:因为实际上计算到的元素是right前一位元素,把mid赋值给right并不会重复计算mid元素。

小结

二分查找的两种方法都会遵循一些规律:

  • 被计算过的mid将不会再被计算,因此在左右闭合的情况下,left和right都会在mid的基础上+1或-1,而左闭合右不闭合的情况下,left仍旧+1,而right直接取mid(right所指的元素不会在下一轮被计算)
  • 抽象的来说,右开区间的情况下,实际计算的右边界是'right'值的前一个

27,移除元素

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

题目看了半天,解题思路是把数组里不等于val的数换到前面去?然后发现我for循环忘了怎么写了(乐.jpg) 代码如下:

class Solution {
    public int removeElement(int[] nums, int val) {
        int length=0 ;
        for( int i = 0; i < nums.length;i++){
            if(nums[i] != val){
                nums[length] = nums[i];
                length++;
            }
        }
        return length;    
    }
}

补充:这是↑ 快慢指针方法 ↑,length是慢指针,i是快指针


还有一种解法, 代码如下

class Solution {
    public int removeElement(int[] nums, int val) {
        int left=0 ;
        int right = nums.length-1;
        while(right >= 0 && nums[right] == val)right--;//找到第一个非val元素
        while(left <= right){
            if(nums[left] == val){
                nums[left] = nums[right];
                right--;
            }
            left++;
            while(right >= 0 && nums[right] == val) right--;//找到下一个非val元素
        }
        
        return left;    
    }
}

这是↑ 相向双指针法 ↑这个思路就是让指针从后往前找非val元素,然后换到前面的val元素位置上,计数的工作由left来完成。 还有另一写法,让left直接等于right,直到换过来的right不是val,再让left++,有巧妙:

class Solution {
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            if(nums[left] == val){
                nums[left] = nums[right];
                right--;
            }else {
                // 这里兼容了right指针指向的值与val相等的情况
                left++;
            }
        }
        return left;
    }
}

以上便是第一天的内容,第一次刷题,虽然题目好像很基础,奈何脑子不好使,写得磕磕绊绊。如有错误,请您指出。