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

1、数组理论基础

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

数组的特点:

  • 组下标都是从0开始的。
  • 数组内存空间的地址是连续的

正是由于第二个特点,我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

注:数组的元素是不能删除的,只能覆盖,比如我要删除第二个元素,本质过程是把第三个开始的元素全部都往前覆盖一个位置。

ps:C++中二维数组在地址空间上是连续的,但是其他语言中不一定,例如Java就不是连续的。


2、704. 二分查找

题目链接:704. 二分查找 - 力扣(Leetcode)

文章链接:代码随想录 (programmercarl.com)https://www.programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html

视频解析:

这道题目的两个基本条件是:

1、前提是数组为有序数组;

2、数组中无重复元素

如果题目不满足上述两个基本条件的时候,就要考虑题目是否适合使用二分法了。

二分法书写的关键点在于:将一个数组一分为二时的区域边界。

写法一:target再左闭右开的区间里    [left,right]

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right=nums.size() -1;
        while(left<=right) {
        int middle = left + (right - left)/2;
        if(nums[middle]>target){
            right = middle -1;
        }else if(nums[middle]<target){
            left = middle +1;
        }else{
            return middle;
        }
        }
        return -1;
    }
};

注意点:

1、当认为target在闭区间里的时候,left=right就有意义,所以while的判断条件就是<=

2、找中点下标时,为了避免left+right会内存溢出,使用   

middle = left + (right - left)/2;

3、当middle的大于target,也就是target在左边区间(假设样例是递增的),因为已知了middle!=target,所以此时就要让检测范围的right= middle-1;middle小于target同理。

写法二:target再左闭右开的区间里    [left,right)

class Solution {
public:  //这是[left,right)左闭右开版本的写法
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right=nums.size();
        while(left<right) {
        int middle = left + (right - left)/2;
        if(nums[middle]>target){
            right = middle;
        }else if(nums[middle]<target){
            left = middle +1;           //这里+1是因为下一个循环中left仍是是闭的,
                                        //而已经知道此时的middle不是target,所以就要让left从middle+1开始
        }else{
            return middle;
        }
        }
        return -1;
    }
};

注意点:

1、当认为target在左闭右开区间里的时候,left=right就无意义,所以while的判断条件就是<;

2、当middle的大于target,也就是target在左边区间(假设样例是递增的),因为已知了middle!=target,但因为右边界是开的,所以直接让right= middle就可以了,此时左区间是不包含middle的;而middle小于target的时候,因为左边是闭区间,所以要想让middle不在闭区间,就要让 left = middle +1

总结:两种写法的复杂度都相同,注意边界处理即可

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)


27. 移除元素

题目链接:27. 移除元素 - 力扣(Leetcode)

文章链接:代码随想录 (programmercarl.com)https://www.programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html

数组移除元素的本质是:覆盖元素

写法一:暴力解法(两个for循环)

外层for循环遍历数组,内层for循环找到需要删除的数字位置,把所以后面的数字向前覆盖一位。

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;
    }
};

复杂度:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

注:关于 i--的解释,比如删掉了第二个数字,后面的第三个变成了第二个,第四个变成了第三个...,此时要让i=2退一格,然后进入下一次循环i++,这时候i=2,判断的数字就是原来的第三个数。

写法二:双指针法(重点,非常常用)

核心思路:通过一个快指针和一个满指针,在一个for循环下完成两个for循环的工作。(减少时间复杂度)

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

删除过程:起始两个指针同位置0;进入循环(快指针遍历元素),当快指针指向的元素不是要删除的数字,则将这个数字赋值给此时慢指针指向的位置,然后满指针++,进入下一次循环;如果此时快指针指向的数字需要删除,则直接跳过,不与慢指针产生关系,进入下一次循环。循环结束后,慢指针指向的下标应该是此时实际数组长度+1(例如原数组4个数字(下标0-3),删除两个数字后还剩两个,慢指针最后指向的下是2,虽然指向的是第三个下标吗,但是正好是剩下数组的长度)

ps:虽然叫慢指针,但如果从过程上来看,慢指针在没有删除元素之前都是比快指针走的快一步

写法三:相对双指针法(基于本题中允许改变元素相对位置)

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一定指向了最终数组末尾的下一个元素
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值