代码随想录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 左边不动 等待被覆盖 右边向右走一
然后再进循环里判断新走到的这个是否是目标值 如果是 就再向右走 如果不是 就把左边的覆盖 左边向右走一
右边也走一(已经把这个非目标值存起来了 所以可以右移)
也就是左边指针左侧的都是已经判断是非目标值的
右边指针右侧的是没判断过的 左右指针中间的是目标值或者是已经保存过的非目标值

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值