代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素

代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素

704.二分查找

代码随想录文档

二分查找的使用前提是有序数组并无重复数字
在写二分查找时,关键是搞明白具体区间的左右开闭

自己首先写一版出来

首先自己想到的就是递归,写的代码如下,一次ac:

class Solution {
 public:
	int search(vector<int> &nums, int target) {
		int num = nums.size();
		return search2(nums, 0, num - 1, target);
	}
 private:
	int search2(vector<int> &nums, int low, int high, int target) {
		if (low > high) return -1;
		int mid = low + (high - low) / 2;
		if (nums[mid] > target) return search2(nums, low, mid-1, target);
		if (nums[mid] == target) return mid;
		if (nums[mid] < target) return search2(nums, mid+1, high, target);
		return false;
	}
};

自己写的时候想的几个点:

  • 首先原始函数入参只有数组和目标,直接递归的话,需要分割新的数组占用空间。所以直接新建函数,使用下标检索递归
  • 注意nums.size()的值比最大数组下标大一,所以搜索数组时high =nums.size()-1
  • 在递归中:
    • 首先写出退出的情况,如果low比high还大,那么说明数组中没有找到目标,直接返回-1。
    • 在求每次的二分中点的时候,想到卡哥说的防止超过int范围,所有使用low+(high-low)/2的方法,
  • 接下来就是二分法比较中点数值时的三种情况(数组升序):关键是在递归寻找两边的数组时,注意开闭区间。在左边的时候需要把右边的中值去掉选择[low,mid-1],在右边是选择去掉左边的中值[mid+1,high]。也是一种左闭右闭
  • 最后按照leetcode的要求,每个循环最后都需要有返回值,所以最后随便加了一行return false

代码随想录解法

好像都没有用递归,我自己写的递归占用空间比较多

使用左闭右闭区间搜索

定义target在左闭右闭这个区间,[left, right]

几个关键点:

  • 首先循环条件定为while (left <= right) ,因为左闭右闭,所以左=右是有意义的
  • if (nums[middle] > target) right = middle - 1 和我写的一个意思,因为左闭右闭,需要去掉比较过的mid值

具体代码如下:

// 版本一
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

  • 时间复杂度O(log n): 二分法循环的区间量级,n,n/2,n/4,…,n/2k(接下来操作元素的剩余个数),其中k就是循环的次数。 n/2^k=1.可得k=log2n(2为底)。所以复杂度为O(log n)
  • 空间复杂度:一直使用一个数组的常数空间所以O(n)
使用左闭右开搜索

定义target在左闭右闭这个区间,[left, right)

几个关键点:

  • 首先循环条件定为while (left < right) ,因为左闭右开,所以比较右边界是没有意义的。
  • if (nums[middle] > target) right = middle 因为当前nums[mid]不等于目标,去左边区间寻找,因为时左闭右开,可以将比较过的mid写进区间中,下一次也不会在对他进行比较

具体代码如下:

// 版本二
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};
  • 时间复杂度O(log n)
  • 空间复杂度O(n)

27.移除元素

想的是直接将数组尾部元素补到需要删除的元素位置,但写了半天没写出来

代码随想录文档
BILIBILI

代码随想录答案-快慢指针

class Solution {
 public:
	int removeElement(vector<int> &nums, int val) {
		int slow = 0;
		for (int fast = 0; fast < nums.size(); fast++) {
			if (nums[fast] != val)
				nums[slow++] = nums[fast];
		}
		return slow;
	}
};

其中快慢指针的定义:

  • 快指针:寻找新数组的元素, 把原数组全部遍历一遍
  • 慢指针:指向更新的新数组的下标位置

代码随想录答案-移动最少元素

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.size() - 1;
        while (leftIndex <= rightIndex) {
            // 找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        return leftIndex;   // leftIndex一定指向了最终数组末尾的下一个元素
    }
};

使用左右两个指针,向中间逼近,我一开始也是这么想的,但是没写出来

使用while,结束条件为左指针超过右指针,说明所有元素都被遍历一遍

但是在每次判断时都要确保左指针没有超过右指针

  1. 首先移动左指针,寻找值为vla的元素,如果不等于val就一直移动
  2. 同理移动右指针,直到遇到值不等于val的元素,方便覆盖左指针找到的val
  3. 如果能走到if判断,并且右指针位置大于左指针,就使用右指针值覆盖左指针
  4. 最后返回的是左指针位置,代表左边数组都是不含有val的,此时就是数组元素个数

第一天结束

你可能感兴趣的:(代码随想录算法训练营,算法,leetcode,数据结构)