刷力扣Day1| 704 27| 数组| 二分法 erase函数双指针

Day1 数组 704. 二分查找 | 27. 移除元素

  • 数组
    • 704. 二分查找
    • 27. 移除元素

数组

704. 二分查找

二分查找思路不再赘述, 注意二分查找适用于有序, 无重复元素的数组, 要是有重复元素, 算法查找到的未知不唯一. 主要难点在于边界处理:

  1. while中 left<=right 还是 left?
  2. 重新赋值right时是 right=middle 还是right=middle + 1?

要用数学中区间的观点去处理边界, 常用的是左闭右闭 [left, right], 左闭右开 [left, right).
整个算法要保证这个不变量不变, 也就是到底用左闭右闭还是左闭右开

二分查找就是通过和中间位置的元素比较大小, 重新划定区间, 再和中间位置元素比较大小, 循环往复直到找到这个位置, 如果区间不合法了, 也就是不存在了.

关于区间的问题:

所以对于查找来说, 这个区间一定要是合法的, 也就是进入while循环的条件一定要是合法的区间 ( while内代码的功能就是查找)

  1. 对于左闭右闭: left == right 是合法的, 比如[3,3].
    那么while中的条件就是 while (left<=right)
  2. 对于左闭右开: left == right 是不合法的, 比如[1,1).
    那么while中的条件就是 while (left

关于middle取值的问题
用 if (num[middle] > target)举例子, 需要重新赋值right, 已经判断了middle, 下一次循环不需要带上middle, 要从left到middle-1就可以. 比如数组 [0 1 3 4 5 7 9 12], middle=3 对应元素4,

  1. 对于左闭右闭:
  • right=middle, 下一次循环while (left<=right)中就会[left, middle],
    middle这个位置的元素再次被判断, 所以不可以.
  • right=middle-1就可以实现[left, middle-1]
  1. 对于左闭右开:
  • right=middle-1, 下一次循环while (left中就是[left, middle-1),
    那middle-1这个位置的元素就是始终没有判断, 所以不可以.

  • right=middle就可以实现[left, middle)

在看题解的过程中我发现还需要对target先进性一个判断,要是比最小的小或者比最大的大 直接就返回-1了。if (target < nums[0] || target > nums[nums.length - 1]) { return -1; }
左闭右闭的代码如下:

 public int search(int[] nums, int target) {
 		if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }
         int left = 0;
         int right = nums.length - 1;
         int middle = 0;

         while (left <= right) {
            middle = (left + right)/2;
            
            if (nums[middle] > target) {
                right = middle - 1;
            } else if (nums[middle] < target) {
                left = middle + 1;
            } else {
                return middle;
            }
         }
          return -1;
    }

左闭右开的代码如下:

 public int search(int[] nums, int target) {
 		if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }
         int left = 0;
         int right = nums.length;
         int middle = 0;

         while (left < right) {
            middle = (left + right)/2;
            
            if (nums[middle] > target) {
                right = middle;
            } else if (nums[middle] < target) {
                left = middle + 1;
            } else {
                return middle;
            }
         }
          return -1;
    }

27. 移除元素

数组中删除元素不是直接删除, 要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖.

  • 暴力解法: 两层for循环, 外层遍历整个数组, 若找到val就将它后面的元素向前移一位
  • 快慢指针(重要): 快慢指针的时间复杂度是O(n), 而不是O(1). 这也是erase()函数的实现方式. fast指针是找新数组中合法的元素, 慢指针是合法元素应该在的位置.

暴力解法代码:

public int removeElement(int[] nums, int val) {
        int size = nums.length;

        for (int i=0; i<size; i++) {
            if (nums[i]==val) {
                size--;
                for (int j=i; j<size; j++){
                    nums[j] = nums[j+1]; //元素前移实现覆盖
                }
                i--; //这一行很关键, 重新检查当前位置覆盖的新元素
            }
        }

        return size;
    }

其中 i--; 我在第一次写的时候落下了, 就出现了错误. 比如[0, 1, 2, 2, 3, 2]数组, 删除元素2, 得到的应该是[0, 1, 3]. 但我得到的却是[0, 1, 2, 3]
原因就是没有 i--, 当检测到第一个2时 i=2, 后面的元素前移, 第二个2就到了第一个2的位置上变成了 [0, 1, 2, 3, 2], 此时再下一次循环中i=3, 会去看元素3, 错过了前移的第二个2.
所以再覆盖元素之后需要i--, 再次检测当前位置覆盖之后的元素

双指针解法

  public int removeElement(int[] nums, int val) {
    int fast=0;
    int slow=0;

    for (fast=0; fast<nums.length; fast++) {
        if (nums[fast] != val) {
            nums[slow] = nums[fast];
            slow++;
        }
    }

    return slow;
}

快慢指针是一个将二维循环降到一维很常用的方法
比如[0, 1, 2, 2, 3, 2]数组, 对于0 和1元素, 它们重新赋值了自己
对于第一个2, 慢指针递增的if语句没有进去, 慢指针停下了, 但是快指针继续+1 . slow=3, fast=4
对于第二个2,同理, slow还是3, fast来到了5
遇到了3, slow的位置, 也就是第三个元素赋值为第五个元素5. slow=4, fast=6
遇到最后一个2, 同理slow不动, fast=7 for循环结束, 最后返回数组长度, 也就是slow

你可能感兴趣的:(数据结构,算法,java)