代码随想录算法训练营第1天|LeetCode707.二分查找、LeetCode27.移除元素

代码随想录算法训练营第1天|LeetCode707.二分查找、LeetCode27.移除元素

1、数组理论基础

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

获取:下标索引的方式。从0开始。

删除/增添:需要移动其他元素的地址。不能删除,只能覆盖。

vector VS array:vector是容器,底层实现是array

Java中没有指针,且不对程序员暴露元素地址。

2、LeetCode 707.二分查找

题目链接:https://leetcode.cn/problems/binary-search/
文章讲解:https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html
视频讲解:https://www.bilibili.com/video/BV1fA4y1o715

第一想法

(忘记是二分查找了…)存数组–设置pos=-1 – 从头遍历,知道找到,停止遍历并输出

看完之后的想法

damn!醍醐灌顶!!我困扰多年的边界取值问题!!!

笔记

前提条件

  • 有序数组
  • 无重复元素

常见问题

  1. while(left right)是<还是<=
    • 解决:进入while的需要是合法区间
  2. if(…) right=middle还是middle+1
    • 解决:已经判断middle>target,那么接下来的区间一定不包含middle这个值,因此right=middle-1。其他类似

解法

  1. 方法1:左闭右闭[left, right]
    while(left<=right) :区间是合法区间[1, 1]
  2. 方法2 左闭右开[left, right)
    while(left<=right) :区间是非法的[1, 1)
    因此应该为while(left<right)

代码:

int search(vector<int>& nums, int target) {
        // 法1 左闭右闭
        int left = 0;
        int right = nums.size()-1;
        int middle;
        // 区间合法
        while(left <= right){
            middle = (left+right)/2;
            if(target < nums[middle]){
                right = middle - 1; //右闭,不包括middle
            }
            else if(target > nums[middle]){
                left = middle + 1; //左闭,不包括middle
            }
            else
                return middle;
        }
        // 法2 左闭右开
        int left = 0;
        int right = nums.size();
        int middle;
        while(left<right){
            middle = (left+right)/2;
            if(target < nums[middle]){
                right = middle; //右开
            }
            else if(target > nums[middle]){
                left = middle + 1;
            }
            else
                return middle;
        }
        return -1;
    }

除此之外,需要注意mid溢出问题。

  • mid = (l + r) / 2时,如果l + r 大于 INT_MAX(C++内,就是int整型的上限),那么就会产生溢出问题(int类型无法表示该数)。
  • 所以写成 mid = l + (r - l) / 2或者 mid = l + ((r - l) >> 1) 可以避免溢出问题。
  • 对于二进制的正数来说,右移x位相当于除以2的x几次方,所以右移一位等于➗2,用位运算的好处是比直接相除的操作快。

3、LeetCode 27.移除元素

题目链接:27. 移除元素 - 力扣(LeetCode)
文章讲解:代码随想录 (programmercarl.com)
视频讲解:https://www.bilibili.com/video/BV12A4y1Z7LP/

第一想法

双指针:使用两个指针i, j,i从前往后遍历,指向val,j从后往前遍历,指向非val。如果i遇到val,那么nums[i]与nums[j]互换,直到i>j。每一个val就计数+1,最后数组总长度-计数。

注意j从右往左遍历时若遇到val,count需要++。
(这只是我的第一思路,但实际上有问题)
![]=https://stackedit.cn/9ef8a453-2794-4f48-947a-6a3f9137c376

法1 暴力解法

犯错:最外层的while条件中的size应该是新数组的大小,否则会陷入死循环。

    int removeElement(vector<int>& nums, int val) {
        // 法1暴力解法
        int i=0;
        int size = nums.size();
        while(i<size){  //注意这里的新的size
            for(int k=0;k<size;k++){
            cout<<nums[k]<<" "; 
            }
            cout<<endl;
            // 等于val,需要把后面的每一个元素都往前移动
            if(nums[i] == val){
                size--;
                int j = i+1;
                while(j<nums.size()){
                    nums[j-1] = nums[j];
                    j++;
                }
                //移动之后,i指向一个新的值,因此不需要往后移
                continue;
            }
            i++;
        }
        return size;
    }
法2 双指针
  1. 两个指针左右遍历

我的第一思路中的count有问题,如果用count计数,那么会有无法考虑到的案例,导致无法通过。

正确的解决方法是返回left值,最后一个left是一个分界,左边都是非val,右边都是val。

并且,需要考虑数组大小为1的情况。

    int removeElement(vector<int>& nums, int val) {
        // 法3 双指针(双向遍历)
        int left=0;
        int right=nums.size()-1;
        while(left <= right){  // =考虑数组大小为1
	        // 找到==val的
            while(left<nums.size()){
                if(nums[left] == val){
                    break;
                }
                left++;
            }
            //找到!=val的
            while(right>=0){
                if(nums[right] != val){
                    break;
                }
                right--;
            }

            if(left < right){
                // 交换
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                left++;
                right--;
            }
        }
        return left;

    }
  1. 两个指针同侧遍历(快慢法)
    read指针读取新数组的元素,write指针新数组要更新的位置。更新/不更新。最后return write指针。
    int removeElement(vector<int>& nums, int val) {
        // 法2 双指针(快慢法)
        int read=0; //用以找需要放到新数组中去的索引
        int write=0; //新数组中此时记录到了哪个索引
        for(read=0;read<nums.size();read++){
            if(nums[read]!=val){
                nums[write]=nums[read];
                write++;
            }
        }
        return write;
    }
法3 vector的erase函数

复杂度O(n2),原理是删除后逐个往前移动。

首先需要得到val的索引,可以使用find函数。找到索引后使用erase(),注意的是每次find只能找到一个位置,因此需要不断地找直到找不到新的val。

刷算法题时使用库函数的时机:使用的库函数只是实现算法的一部分,并且已经掌握其底层原理以及复杂度时,可使用。

总结:

  • 区间不变量
  • 二分使用前提-有序数组
  • 避免mid溢出,mid=left+( (right - left )>>1)。
  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值