二刷代码随想录训练营Day 1|数组理论基础,704. 二分查找,27. 移除元素

1.数组理论基础

文章链接:代码随想录

 note:数组的下标是从0开始的,而且内存中连续存储。

二维数组在不同的语言中的存储方式是不同的。在C++中二维数组的地址分布也是连续的,但是在Java里不是。

2.二分查找

代码:(左闭右闭区间)

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(target > nums[middle]){
                left = middle + 1;
            }else if(target < nums[middle]){
                right = middle - 1;
            }else{
                return middle;
            }
        }
        return -1;
    }
};

代码:(左闭右开) 

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

note:

        二分查找的前提是数组已经排过序了!!!!!!

        之前在一刷的时候,一直在这个判断条件后,应该更新哪边的边界值进行犹豫。这次二刷的时候,想清楚了:只要关注target值的位置在哪里就好了,我们的目标一直是缩短target所在的区间大小。因此,如果target>nums[middle],即target值在区间的右半部分,我们就更新left值;反之就去更新right值。 

        关于这个循环不变量(其实就是区间的开闭要时刻保持一致),我们主要是有两种情况——左闭右闭和左闭右开。它们的区别主要是在更新边界以及循环判断条件不同。

        这里主要抓住什么时候区间是个有效区间就好了。[3,3]是一个有效区间,但是[3,3)不是一个有效区间。

        以及,在缩小区间范围的时候,要把之前判断不是target的范围全部剔除,来保证我们的效率。因此,如果是左闭右闭的话,如果还需要缩小区间,那就把边界值也剔除掉。如果是左闭右开的话,我们的有边界其实没有取到有效值,所以下一次的区间,右边界可以直接被赋值为middle。因为它取不到这个以及被我们应该剔除的数字。

相关题目练习

代码随想录
题目:35. 搜索插入位置 - 力扣(LeetCode)

 代码:(左闭右闭)

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

 note:

        这里如果查找不到返回升序应该插入的位置,其实就是我们遍历完数组的left值。

 代码随想录 (programmercarl.com)

题目:
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = getLeftBorder(nums,target);
        int rightBorder = getRightBorder(nums,target);
        // 情况1 目标值不在数组范围内,左右边界没有在循环中被重新赋值
        if(leftBorder == -2 || rightBorder == -2){
            return{-1,-1};
        // 情况2 目标值在数组范围内,且在数组中,因为求出的边界值是不包含目标值的,需要稍加调整
        }else if(rightBorder - leftBorder > 1) return {leftBorder + 1,rightBorder - 1};
        // 情况3 目标值在数组范围内,但不在数组中,这样的左右边界大小会颠倒
        return {-1,-1};
    }
private:
    int getRightBorder(vector<int>& nums,int target){
        int left = 0;
        int right = nums.size() - 1;
        int rightBorder = -2;
        while(left <= right){
            int middle = left + (right - left) / 2;
            if(target < nums[middle]){
                right = middle - 1;
            }else{
                left = middle + 1;
                rightBorder = left;
            }
        }
        return rightBorder;
    }
    int getLeftBorder (vector<int>& nums,int target){
        int left = 0;
        int right = nums.size() - 1;
        int leftBorder = -2;
        while(left <= right){
            int middle = left + (right - left) / 2;
            if(target <= nums[middle]){
                right = middle - 1;
                leftBorder = right;
            }else{
                left = middle + 1;
            }
        }
        return leftBorder;
    }
};

 note:

        左右边界的求法:求右边界,那就把更新left和找到目标值的条件判断合并在一起即(target >= nums[middle]),在更新left的时候,把left赋值给rightBorder。为什么是赋值left呢?因为我们在用二分法的时候,有可能我们找到了目标值,但是定位的是目标值范围的中间数值,把left赋值给rightBorder可以利用上我们的循环的判断条件,left会一直更新,直到它和right相等,这样就可以从左逐渐逼近rightBorder。求左边界同理。

        还有难点就是,要分析好目标值与所给数组的三种关系情况:

        1.目标值不在数组大小范围内,这样我们的左右边界在整个过程中都不会被重新赋值,保持着初始值。要返回{-1,-1}

        2.目标值在数组大小范围内,同时也在数组元素中。我们在这之后,还需要对左右边界进行调整,因为当我们不满足循环条件时,才会跳出循环,那么这就导致右边界其实是真实边界的有边界向右偏移了1位。左边界同理,向左偏移了1位。

        3.目标值在数组范围内,但不在数组元素中,那这样就会导致左右边界的大小值不合法,也就是rightBorder-leftBorder <= 1。这样也是要返回{-1,-1}

题目:69. x 的平方根 - 力扣(LeetCode)

class Solution {
public:
    int mySqrt(int x) {
        int left = 0;
        int right = x;
        int ans = -1;
        while(left <= right){
            int mid = left + (right - left) / 2;
            if(x >= (long long)mid*mid){
                ans = mid;
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        return ans;
    }
};

note: 

         这道题主要是难以分析和下手,其实这道题的目标值是x,我们的目标是让我们的mid*mid逐渐逼近x,left是0,right是x,求的是整个过程不断更新的left的最终值。

 题目:367. 有效的完全平方数 - 力扣(LeetCode)

class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = 0;
        int right = num;
        while(left <= right){
            int mid = left + (right - left) / 2;
            long square = (long) mid * mid;
            if(num < square){
                right = mid - 1;
            }else if(num > square){
                left = mid + 1;
            }else{
                return true;
            }
        }
        return false;
    }
};

note: 

         这道题的目标值是num,left初始值是0,right初始值是num,让mid*mid逐渐逼近目标值。这里我们不是求左右边界,而是去判断有没有找到x。

3.移除元素

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

note:

这道题是要移除所有数值等于val的元素。我们采用双指针的主要思路其实是,让left作为新的数组的索引号,而让right去判断哪些元素可以被我们加到新的数组里。

新数组最后的大小其实就是left的数值大小。

相关题目练习 

 . - 力扣(LeetCode)

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int left = 1;
        for(int right = 1; right < nums.size();right++){
            if(nums[right] != nums[right - 1]){
                nums[left] = nums[right];
                left++;
            }
        }
        return left;
    }
};

note:当数组里的元素和前一个不相等时,更新新数组的值。

 283. 移动零 - 力扣(LeetCode)

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int left = 0;
        for(int right = 0; right < nums.size(); right++){
            if(nums[right] != 0){
                nums[left] = nums[right];
                left++;
            }
        }
        for(int i = left; i < nums.size(); i++){
            nums[i] = 0;
        }
    }
};

note:第一个循环,将所有不等于0的元素加入到新数组中;第二个循环,将新数组的剩余部分都变为0. 

844. 比较含退格的字符串 - 力扣(LeetCode)

class Solution {
public:
    bool backspaceCompare(string s, string t) {
        int i = s.length() - 1;
        int j = t.length() - 1;
        int skipS = 0; // 记录#的个数
        int skipT = 0;
        while(i >= 0 || j >= 0){
            // 处理退格问题
            while(i >= 0){
                if(s[i] == '#'){
                    skipS++;
                    i--;
                }else if(skipS > 0){
                    skipS--;
                    i--;
                }else{
                    break;
                }
            }
            while(j >= 0){
                if(t[j] == '#'){
                    skipT++;
                    j--;
                }else if(skipT > 0){
                    skipT--;
                    j--;
                }else{
                    break;
                }
            }
            // 比较
            if(i >= 0 && j >= 0){
                if(s[i] != t[j]){
                    return false;
                }
            }else{
                if(i >= 0 || j >= 0){
                    return false;
                }
            }
            i--;
            j--;
        }
        return true;
    }
};

 note:这里用了skipS和skipT来记录'#'的数量,我们在遍历数组进行比较前,首先会跳过'#'字符,同时会根据此时的skip的大小来决定是否跳过其它的字符。

在排除这些干扰后,就把所有不等的情况找出来——两个字符串都没有遍历完,出现了字符不等的情况;在同步遍历比较的时候,有一个字符串提前遍历完了,说明两个字符串连长度都不想等。

这里的易错点是:为了把长度不等的情况也找出来,最外层的循环的条件判断用的是或运算。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值