Day1 数组理论基础,704. 二分查找,27. 移除元素

目录

数组理论基础

704. 二分查找

题目建议

左闭右闭法 [left, right]:

左闭右开法 [left, right):

PS: 整数溢出与回绕

27. 移除元素

题目建议

暴力求解法:

双指针法:

双向指针法:

PS: while循环中注意数组的索引范围


第一天做题,忘记了不少C++ 的语法,抽空一定要多看看书。


数组理论基础

  • 数组在内存中的存储方式:数组是存放在连续内存空间上的相同类型数据的集合。
  • 数组可以方便的通过下标索引的方式获取到下标下对应的数据。

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的。C++中二维数组在地址空间上亦是连续的。
  • 因为数组的在内存空间的地址是连续的,所以我们在“删除或者增添”元素的时候,就难免要移动其他元素的地址。
  • vector 和 array 的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
  • 数组的元素是不能删的,只能覆盖。
  • “删除或增添元素”时,数组真正的内存空间不会改变,但对于一些编程语言中,对数组进行了一层包装,如C++中的vector提供了各式各样使用数组的接口,删除(erase)了一个元素后,后面的元素会自动前移,长度(size)会自动减1。

摘自:代码随想录 (programmercarl.com)-数组理论基础


704. 二分查找

题目链接:704. 二分查找

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

题目建议

熟悉根据 左闭右开,左闭右闭 两种区间规则 写出来的二分法

使用二分法的前提:1. 数组为有序数组;2. 数组中无重复元素;

左闭右闭法 [left, right]:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        
        int n = nums.size();
        int left = 0;
        int right = n-1;
      
        
        while(left <= right){   //设定合法区间
            int mid = (left + right) / 2;   //可能有溢出风险!!
            
            if(nums[mid] > target){
               right = mid - 1;
            } 
            else if(nums[mid] < target){
                left = mid + 1;
            }
            else{
                return mid;
            }
        }
        return -1;
    }
};

左闭右开法 [left, right):

class Solution {
public:
    int search(vector<int>& nums, int target) {
        
        int n = nums.size();
        int left = 0;
        int right = n;
      
        
        while(left < right){   //设定合法区间
            int mid = (left + right) / 2;   //可能有溢出风险!!

            if(nums[mid] > target){
               right = mid;
            } 
            else if(nums[mid] < target){
                left = mid + 1;
            }
            else{
                return mid;
            }
        }
        return -1;
    }
};

PS: 整数溢出与回绕

两个int型相加可能会溢出 [1]。

int mid = (left + right) / 2;

替换为

int mid = left + (right - left) / 2; 

[1]  参考链接:整数溢出与回绕

#include <iostream>

int main() {
    unsigned int a = 0xffffffff;    //a = 4294967295;
    unsigned int b = 1;
    unsigned int c = 0;

    c = (a + b)/2;  //出现越界:c = 0;
    c = a + (b - a)/2;  //出现越界:c = 0;

    //防止越界
    c = b + (a - b)/2;  // c = 2147483648;

    std::cout << a << std::endl;
    std::cout << c << std::endl;

    return 0;
}

27. 移除元素

题目链接:27.移除元素

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 3:(很典型的输入)

输入:nums = [1], val = 1
输出:0, nums = []

题目建议

暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。 双指针法 是本题的精髓,今日需要掌握,至于拓展题目可以先不看。


暴力求解法:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int len = nums.size();
        int new_len = len;

        for(int i = 0; i < new_len;){
            if(nums[i] == val){
                for(int j =i; j < new_len-1; ++j ){
                    nums[j] = nums[j+1];
                }
                new_len--;
            }

            if(nums[i] != val) i++;     //把判断条件放到了程序块中
        }
        return new_len;

    }
};

双指针法:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {

        // 双指针解法
        int len = nums.size();
        int fast = 0;   //快指针,存储数组索引
        int slow = 0;   //慢指针,存储数组索引

        for(int fast = 0; fast < len; fast++){
            if(nums[fast] != val){
                nums[slow] = nums[fast];
                slow++;
            }

        }
        return slow;

    }
};

双向指针法:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {

        // 双向指针解法
        int left_index = 0;
        int right_index = nums.size() - 1;

        while (left_index <= right_index){
            
            while(left_index <= right_index && nums[left_index] != val){ 
                ++left_index;
            }

            while(left_index <= right_index && nums[right_index] == val ){
                                    //注意:nums[right_index]的判断一定要放在后面
                --right_index;
            }
              
            if(left_index < right_index){       //这一行的理解在末尾
                nums[left_index] = nums[right_index];
                ++left_index;
                --right_index;
            } 
        }

        return left_index;  //一定指向最终数组末尾的下一个元素,也就是新数组的长度

    }
};

/*上面while 和 if 括号中的 left_index <= right_index 
或者 left_index < right_index
都是针对当 left_index = right_index 时,
如果等于 val ,则 left_index 直接+1,不用在此进入判断赋值*/

PS: while循环中注意数组的索引范围

在双向指针法力扣网站的调试中,遇到while循环中 nums[left_index] != val 的判断放到 left_index <= right_index 前后时,提交代码具有不同的效果:

正确的:

while(left_index <= right_index && nums[left_index] != val){ 
        ++left_index;
    }
while(left_index <= right_index && nums[right_index] == val ){
        --right_index;
    }

报错的:

while(nums[left_index] != val && left_index <= right_index){ 
        ++left_index;
    }
while(nums[right_index] == val && left_index <= right_index ){
        --right_index;
    }

针对上述语句报错: 

执行出错信息:

Line 1034: Char 34: runtime error: addition of unsigned offset to 0x602000000310 overflowed to 0x60200000030c (stl_vector.h)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_vector.h:1043:34


最后执行输入:

[1]

1

最后发现因为left_index 或 right_index 做 ++ 或 -- 运算时,有超出数组索引范围 (nums().size 或-1)  的可能,在内while循环中进行判断时就会产生报错。(奇怪的时,相同的代码,在CLion中不会报错)

一般的,对于数组的索引会使用 for循环 来控制索引范围,while循环中要特别注意数组的索引范围。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值