代码随想录day1| 704. 二分查找、27. 移除元素

数组理论基础

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

注意:

  • 因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址
  • vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。

C++中二维数组在地址空间上是连续的。
测试代码:

void test_arr() {
    int array[2][3] = {
		{0, 1, 2},
		{3, 4, 5}
    };
    cout << &array[0][0] << " " << &array[0][1] << " " << &array[0][2] << endl;
    cout << &array[1][0] << " " << &array[1][1] << " " << &array[1][2] << endl;
}

int main() {
    test_arr();
}

704.二分查找

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]之间。

第一次尝试

class Solution {
public:
    int left = 0;//数组所以第一个为0
    int right = nums.size()-1;//最后一个
    int search(vector<int>& nums, int target) {
        //注意确定左右区间开闭
        //这里设置为左闭右开
        if(left>right) return -1;
        int mid=left+(right-left)/2;
        if(target==nums[mid]) return mid;
        else if(target>nums[mid]){
            left=mid+1;
            search(nums,target);
        }
        else{
            right=mid;
            search(nums,target);
        }
    }
};

报错:
Line 4: Char 17: error: use of undeclared identifier ‘nums’
4 | int right = nums.size()-1;//最后一个
| ^
1 error generated.
卡住的原因:
没有想到怎么能在第一次的时候在函数里定义左右区间,然后在接下来的循环中不执行这两个初始化定义

看完carl代码随想录,思考:
因为一定要用nums确定右区间,所以初始化一定是在子函数里的
在这个基础上,要不断循环修改左右区间:
不需要递归,直接while循环,在满足区间中还有可能有目标值的情况下,不断执行while即可

  • 循环 - 递归/while循环

第二次尝试

class Solution {
public:
  
    int search(vector<int>& nums, int target) {
        //注意确定左右区间开闭
        //这里设置为左闭右开  
        int left = 0;//数组所以第一个为0
        int right = nums.size()-1;//最后一个     
        
        while(left<right) {
            int mid=left+(right-left)/2;
            if(target==nums[mid]) return mid;
            else if(target>nums[mid]){left=mid+1;}
            else{right=mid;}
        }
        if(nums.size()==1) return target==nums[0]?0:-1;
        return -1;
    }
};

[2,5] 输出-1
对比carl发现由于我设定的是左闭右开 而int right = nums.size()-1;//最后一个 中没有把最后一个包含进来

修改后通过了

class Solution {
public:
  
    int search(vector<int>& nums, int target) {
        //注意确定左右区间开闭
        //这里设置为左闭右开  
        int left = 0;//数组所以第一个为0
        int right = nums.size();//最后一个     
        
        while(left<right) {
            int mid=left+(right-left)/2;
            if(target==nums[mid]) return mid;
            else if(target>nums[mid]){left=mid+1;}
            else{right=mid;}
        }
        if(nums.size()==1) return target==nums[0]?0:-1;
        return -1;
    }
};

并且由于初始右区间,我多了一个判断数组长度为1的情况
这个可以注释掉 if(nums.size()1) return targetnums[0]?0:-1;

  • Q:为什么在数组长度为1 且该数等于target的时候能顺利运行呢int
    mid=left+(right-left)/2;这里的mid不应该是等于1
    然后在下面判断if(target==nums[mid])的时候不应该数组越界了吗
    为什么mid = 0 + (1-0)/2 = 0

A:因为在整数除法中,结果会向下取整 TUT

再写一个左闭右闭情况

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //注意确定左右区间开闭
        //这里设置为左闭右开  
        /*int left = 0;//数组所以第一个为0
        int right = nums.size();//最后一个     
        while(left<right) {
            int mid=left+(right-left)/2;
            if(target==nums[mid]) return mid;
            else if(target>nums[mid]){left=mid+1;}
            else{right=mid;}
        }
        return -1;*/
        int left=0;
        int right=nums.size()-1;

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

错误汇总:

  1. 循环 - 递归/while循环
  2. 左闭右开没判断好 nums.size()是最后一个元素的下一个
  3. 在整数除法中,结果会向下取整

27. 移除元素

27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。
用户评测:

评测机将使用以下代码测试您的解决方案:

int[] nums = […]; // 输入数组
int val = …; // 要移除的值
int[] expectedNums = […]; // 长度正确的预期答案。
// 它以不等于 val 的值排序。

int k = removeElement(nums, val); // 调用你的实现

assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,,]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,,,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

提示:

0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100

第一次尝试

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

想到双指针了
但是对于怎么判断循环终止 、如何终止 、怎么不越界
没想明白
(烦)
直接看carl代码代码随想录不想想了

暴力解法:一个for循环遍历数组元素 ,第二个for循环更新数组。
第一个循环找到val就移动元素覆盖 第二遍找val缩短数组长度

// 时间复杂度: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;

    }
};

感觉这里的i<size 和i–; 都挺难想而且易错(sad)
双指针法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组(这个我想的是对的)
慢指针:指向更新 新数组下标的位置(这个不太对)
很多同学这道题目做的很懵,就是不理解 快慢指针究竟都是什么含义,所以一定要明确含义,后面的思路就更容易理解了。

// 时间复杂度:O(n)
// 空间复杂度:O(1)
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;
    }
};

注意这些实现方法并没有改变元素的相对位置!(可怕)(可恶(〃>皿<))
看完这个注意也没啥思路(呜呜)
nums = [0,1,2,2,3,0,4,2], val = 2
区别:找到那个非目标的3 把前面的2覆盖 而不是交换
对于非目标的01 做一个无用功 赋给自己
对于目标的2 左边不动 等待被覆盖 右边向右走一
然后再进循环里判断新走到的这个是否是目标值 如果是 就再向右走 如果不是 就把左边的覆盖 左边向右走一
右边也走一(已经把这个非目标值存起来了 所以可以右移)
也就是左边指针左侧的都是已经判断是非目标值的
右边指针右侧的是没判断过的 左右指针中间的是目标值或者是已经保存过的非目标值

是暴力解法的一种缩减 不太理解暴力 所以双指针也很难想

好神奇。。。
感觉一辈子想不出来(悲伤)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值