打卡学习第一天

数组理论基础

数组是存放在连续内存空间上的相同类型数据的集合。

需要两点注意的是

  • 数组下标都是从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

而在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

基于此我总结了一下我们常见的一些语言:

二维数组在内存中连续存储的语言

  • C语言:与C++类似,C语言中的二维数组在内存中也是连续存储的。例如声明一个二维数组int arr[3][4],其在内存中会按照arr[0][0]、arr[0][1]、arr[0][2]、arr[0][3]、arr[1][0]、arr[1][1]……的顺序连续存放,相邻元素的地址差值与数组元素类型所占字节大小有关。
  • C++语言:如你提供的测试代码和结果所示,在C++中二维数组是连续分布的,其元素在内存中按照声明的顺序依次排列,相邻元素的内存地址是连续的,且地址差值与数组元素类型所占字节大小相关。
  • Fortran语言:Fortran中的二维数组在内存中也是连续存储的,并且采用列优先存储方式,即先存储列元素,再存储行元素。例如一个二维数组arr(3,4),其在内存中会按照arr(1,1)、arr(2,1)、arr(3,1)、arr(1,2)、arr(2,2)……的顺序连续存放。
  • C#语言:C#中的二维数组在内存中是连续存储的。例如声明一个二维数组int[,] arr = new int[3,4],其元素在内存中会按照一定的顺序连续存放,相邻元素的内存地址是连续的。
  • Go语言:Go语言中的数组是值类型,二维数组在内存中也是连续存储的。当声明一个二维数组时,编译器会为其分配一块连续的内存区域来存储所有元素。

二维数组在内存中不连续存储的语言

  • Java语言:如你提供的测试代码和结果所示,Java中的二维数组并不是连续存储的。Java的二维数组实际上是一个数组的数组,每个一维数组(即二维数组的每一行)是独立的对象,它们在堆内存中的存储位置是分散的,没有固定的顺序和连续性。通过输出的地址也可以看出,二维数组的每一行头结点的地址是没有规则的。
  • Python语言:Python中的二维数组(列表的列表)在内存中也是不连续存储的。Python的列表是动态数组,其内部存储的是对象的引用,二维数组中的每个一维列表(即二维数组的每一行)是独立的对象,这些对象在内存中的存储位置是分散的。例如arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]arr中的每个子列表在内存中的位置是不连续的。
  • JavaScript语言:JavaScript中的二维数组也是不连续存储的。JavaScript的数组是一种特殊的对象,二维数组中的每个一维数组(即二维数组的每一行)是独立的对象,它们在内存中的存储位置是分散的,通过索引访问元素时,会根据哈希表的方式查找对应的内存地址。
  • Ruby语言:Ruby中的二维数组是数组的数组,每个一维数组是独立的对象,在内存中的存储位置是分散的,没有固定的顺序和连续性。
  • PHP语言:PHP中的二维数组也是不连续存储的。PHP的数组本质上是有序映射,二维数组中的每个一维数组是独立的映射对象,它们在内存中的存储位置是分散的。

704二分查找

左闭右开区间

[left, right),初始时right为数组长度,表示开区间。循环条件是left < right,在更新边界时,如果中间元素小于目标值,左边界更新为mid + 1;如果中间元素大于目标值,右边界更新为mid,因为mid位置的元素已经检查过,且右边界是开区间,不包括mid

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) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return -1;
}

左闭右闭区间

[left, right],初始时right为数组最后一个元素的索引,表示闭区间。循环条件是left <= right,在更新边界时,如果中间元素小于目标值,左边界更新为mid + 1;如果中间元素大于目标值,右边界更新为mid - 1,因为mid位置的元素已经检查过,且右边界是闭区间,需要排除mid

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) { // 如果中间元素小于目标值
            left = mid + 1; // 更新左边界为中间位置的下一个位置
        } else { // 如果中间元素大于目标值
            right = mid - 1; // 更新右边界为中间位置的前一个位置,因为是闭区间,所以不包括mid
        }
    }
    return -1; // 如果循环结束仍未找到目标值,返回-1
}

35. 搜索插入位置

左闭右开区间

[left, right),初始时right为数组长度,表示开区间。循环条件是left < right,在更新边界时,如果中间元素小于目标值,左边界更新为mid + 1;如果中间元素大于目标值,右边界更新为mid。最终返回left,因为left会指向目标值应该插入的位置。

public int searchInsert(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) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

左闭右闭区间

[left, right],初始时right为数组最后一个元素的索引,表示闭区间。循环条件是left <= right,在更新边界时,如果中间元素小于目标值,左边界更新为mid + 1;如果中间元素大于目标值,右边界更新为mid - 1。最终返回left,因为left会指向目标值应该插入的位置。

public int searchInsert(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) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return left;
}

27.移除元素

单循环暴力解法

通过一个循环遍历数组,遇到不等于val的元素就将其移动到数组前面,同时记录不等于val的元素数量。这种方法简单易懂,但可能需要移动很多元素。

public int removeElement(int[] nums, int val) {
    int k = 0;
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != val) {
            nums[k] = nums[i];
            k++;
        }
    }
    return k;
}

双指针解法

使用两个指针,左指针指向不等于val的元素,右指针遍历数组。当右指针遇到不等于val的素时,将其移动到左指针位置,同时左指针右移。这种方法可以减少元素的移动次数,提高效率。

public int removeElement(int[] nums, int val) {
    int left = 0;
    int right = 0;
    while (right < nums.length) {
        if (nums[right] != val) {
            nums[left] = nums[right];
            left++;
        }
        right++;
    }
    return left;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值