代码随想录算法训练营第1天 | 题目704、题目27

代码随想录算法训练营第1天 | 题目704、题目 27

文章来源:代码随想录
视频来源:视频

题目名称:704.二分查找

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:

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

第一想法:

首先明确二分查找的使用条件,无重复且有序。此时需要找二分查找左右的边界,确定mid的位置

解答思路:

分为左闭右闭以及左闭右开
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
—while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
—if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
方法二
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。
有如下两点:
while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

困难:无

收获:

二分查找的两个方法,突出的是循环不变量的思想

题目名称:27.移除元素

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

示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。

示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

你不需要考虑数组中超出新长度后面的元素。

第一想法:

使用暴力解法,遍历每一位,在找到值后将后面的每一位向前移动一位,使用两层循环

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

解答思路:

这里使用双指针法
指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
-快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
-慢指针:指向更新 新数组下标的位置

class Solution {
    public int removeElement(int[] nums, int val) {
            int j=0;
        for (int i = 0; i < nums.length; i++) {
            // 发现与val不同的元素,将其填入nums[j]
            if (nums[i] != val) {
                nums[j] = nums[i];
                j++;
            }
        }
        return j;
    }
}

困难:

双指针法的快慢指针理解有困难,对边界的选取需要再多学习

收获:

双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

其他题目:35.搜索插入位置 34. 在排序数组中查找元素的第一个和最后一个位置

35题和702差不多,也是使用二分查找,不同之处在于它考察在未寻找到val时的left和right的关系。这也是薄弱点,我对具体的动态变化不太清楚:
int[] nums = {1,3,5,6};
int target = 2;
寻找过程的变化
mid: 1
right: 0
mid: 0
left: 1
34题没想出来,解题是利用两次二分查找。分别查找左右边界,查找左边界为第一次出现的边界,在查寻到第一次的值时赋值,并向左更新right,确保找到的是第一次出现的值。查询右边界则是找最后出现的值,向右更新left,确保是最后值。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = 0;
    int right = nums.length - 1;
    int first = -1;
    int last = -1;
    // 找第一个等于target的位置
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        first = middle;
        right = middle - 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }

    // 最后一个等于target的位置
    left = 0;
    right = nums.length - 1;
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        last = middle;
        left = middle + 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }

    return new int[]{first, last};
    }
}

你可能感兴趣的:(算法)