代码随想录算法训练营第一天|数组理论基础、704 二分查找、27 移除元素

代码随想录算法训练营第一天|数组理论基础、704 二分查找、27 移除元素

1.数组理论基础

文章链接:https://programmercarl.com/%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html

什么是数组?

数组是存放在连续地址空间中相同数据类型的集合。

数组可以直接通过下标索引找到对应的数据,数组下标一般从0开始

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SOCliHOW-1663751939028)(%E6%95%B0%E7%BB%84-09.21.assets/image-20220921154713554.png)]

由于在数组中,所有的地址空间都是连续的,那么当我需要删除或者添加一个元素的话应该怎么办呢?

对于删除元素来说:

​ 例如我需要删除下标为3的元素的话,那么我们不能直接删除掉其所对应的空间,不然数组不再连续。所以我们做的是,需要将J这个数据删除掉,而不是删除空间。删除数据的话我们可以直接将后面的数据依次往前覆盖。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4D9pvttz-1663751939030)(%E6%95%B0%E7%BB%84-09.21.assets/image-20220921155715810.png)]

那么添加元素来说就相对简单了,只需要再数组后面开辟出需要的空间,将对应位置的数据依次后移即可添加了。

对于数组需要主要三点:

  • 数组地址空间连续
  • 数组下标从0开始
  • 数组中的数据不能删除,只能覆盖

了解一维数组之后,那么二维数组就没那么困难了
在这里插入图片描述

二维数组的内存是怎么分布的呢?

对于不同的编程语言,二维数组在内存中的分布可能会有所不同。

例如在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();
}
测试结果如下:
    
0x7ffee4065820 0x7ffee4065824 0x7ffee4065828
0x7ffee406582c 0x7ffee4065830 0x7ffee4065834

由此可知,在c++中,存放二维数组实际上就是在一串连续的地址空间上存储的。

而在Java中,二维数组的空间又是怎么分布的呢?

由于在java中是无法看到每一个元素的地址的,在这里我们就只看首行的地址,若首行地址都是有规律的,那么可以证明是连续分布的,否则就是分散的。

public static void test_arr() {
    int[][] arr = {{1, 2, 3}, {3, 4, 5}, {6, 7, 8}, {9,9,9}};
    System.out.println(arr[0]);
    System.out.println(arr[1]);
    System.out.println(arr[2]);
    System.out.println(arr[3]);
}
测试结果如下:

[I@7852e922
[I@4e25154f
[I@70dea4e
[I@5c647e05

在这可以看出来,每一行的首地址是分散开的,没有任何规律。查阅相关的资料之后,我们会发现在Java中二维数组的空间分布是像这样的。

在这里插入图片描述

也就是,在Java中,会有一块连续的地址空间来存放二维数组的每行的行首地址,这个行首地址是分散开的。通过行首地址就能找到对应行中的所有元素,而每一行就相当于是一个一维数组,也就是连续的空间。

2.leetcode 704 二分查找

题目链接: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

题目描述:

在这里插入图片描述

对于本题来说,最开始想到的肯定就是暴力一次for循环解决,通过一次遍历就可以找到对应的target,时间复杂度为O(n),也是挺可观的,但是在想想过后,我们会发现数组是升序的,那么在一个升序数组中找到对应的target,我们就可以采用二分法了,时间复杂度O(log n)。

方法一:一次遍历
class Solution {
    public int search(int[] nums, int target) {
        int result=-1;
        for(int i=0;i<nums.length;i++){
            if(nums[i]==target){
                result=i;
                break;
            }
        }
        return result;
    }
}
方法二:二分法

二分查找的区间一般有两种写法:左闭右闭[left,right],或者左闭右开[left,right)

那么这两种的区别在哪呢?循环终止条件是什么?是left<right?还是 left<=right?,当nums[mid]>target时,应该时right=mid,还是right=mid-1

​ 假设数组中只有一个元素[1],对于左闭右闭:那么二分区间应该是[1,1],也就是说当left== right时,也是需要进行判断的,所以循环终止条件应该为left<=right。而对于左闭右开应该是[1,2),所以不需要判断left==right时的情况,在left<right时就已经判断结束。

​ 在我看来,这两种二分方法都是一样的,只不过是每次二分之后,得保证下次需要判断的元素全部在这个区间内,所以对于左闭右闭,当nums[mid]>target时 right=mid-1; 因为nums[mid]已经判断过,而下一个区间就是[left,mid-1]了,因为是右闭,所以会包含mid-1;而对于左闭右开,当nums[mid]>target时 right=mid;因为nums[mid]已经判断过,下次判断的话mid-1应该需要在区间内,根据开区间特性,为了让mid-1在区间内的话,那么下次判断区间应该为[left,mid)。

​ 在平时刷题的过程中,我们只需要记住一种方法就好了,像我一般都是直接使用左闭右闭的方法。

版本一:左闭右闭
class Solution {
    public int search(int[] nums, int target) {
        int left=0;
        int right=nums.length-1;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]>target){
                right=mid-1;
            }else{
                left=mid+1;
            }
        }
        return -1;
    }
}
版本二:左闭右开
class Solution {
    public int search(int[] nums, int target) {
        int left=0;
        int right=nums.length;
        while(left<right){
            int mid =left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]>target){
                right=mid;
            }else{
                left=mid+1;
            }
        }
       return -1;
    }
}

3.leetcode 27 移除元素

题目链接:https://leetcode.cn/problems/remove-element/

文章链接:https://programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html

视频链接:https://www.bilibili.com/video/BV12A4y1Z7LP/

题目描述:

在这里插入图片描述

在这里插入图片描述

思路:

​ 因为题目说了必须在原地修改数组,所以不能使用额外的数组来解题。首想的还是暴力,之后发现也能使用双指针。前面数组的特性说到过了,我们只能覆盖元素,不能真正删除该元素的空间。

方法一:暴力法

该题需要移除数组中等于val的元素,我们可以遍历数组,每次碰到val之后,可以将后面的元素全部往前移动。

class Solution {
    public int removeElement(int[] nums, int val) {
        //解法一:暴力法
        //两层for循环,一次遍历整个数组,一次将后面的元素前移
        int count=0;//计算val的个数
        for(int i=0;i<nums.length-count;i++){
            if(nums[i]==val){
                for(int j=i+1;j<nums.length-count;j++){
                    nums[j-1]=nums[j];
                }
                count++;
                i--;//这里i--的意思就是,删除完val之后,后面的元素又覆盖到该位置上了,下次判断还需要从						该位置判断,所以i--
            }
        }
        return nums.length-count;
    }
}
方法二:双指针法

原理就是快慢指针,快指针用来遍历整个数组,慢指针用来填充新数组

当快指针遍历到与val不等的时候,那么慢指针的位置就可以填充元素,

当快指针遍历到与val相等的时候,那么快指针会跳过,继续往后遍历,而慢指针不会对其进行填充。

class Solution {
    public int removeElement(int[] nums, int val) {
        //解法:双指针
        //思路:一个指针用来遍历数组,一个指针用来填充数组(覆盖原数组)
        //当nums[i]==val时,不会填充,i后移,j不动
        //当nums[i]!=val时,填充数组,i往后继续遍历,填充指针j后移一位等待下个元素填充
        int i=0,j=0;
        for(i=0;i<nums.length;i++){
            if(nums[i]!=val){
                nums[j++]=nums[i];
            }
        }
        return j;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Janice0721

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值