今日任务
第一章 数组part01
数组理论基础,704. 二分查找,27. 移除元素
详细布置
数组理论基础
文章链接:代码随想录
题目建议: 了解一下数组基础,以及数组的内存空间地址,数组也没那么简单。
704. 二分查找
题目建议: 大家今天能把 704.二分查找 彻底掌握就可以,至于 35.搜索插入位置 和 34. 在排序数组中查找元素的第一个和最后一个位置 ,如果有时间就去看一下,没时间可以先不看,二刷的时候在看。
先把 704写熟练,要熟悉 根据 左闭右开,左闭右闭 两种区间规则 写出来的二分法。
题目链接:. - 力扣(LeetCode)
文章讲解:代码随想录
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
核心问题:
正确理解对于left->right 双指针区域的含义,那么每次缩小区间时,都需要保障最终取值都可能在缩小后的每个位置上,也就是文章中提到的: 循环不变量规则
27. 移除元素
题目建议: 暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。 双指针法 是本题的精髓,今日需要掌握,至于拓展题目可以先不看。
题目链接:. - 力扣(LeetCode)
文章讲解:代码随想录
视频讲解:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili
public int removeElement(int[] nums, int val) {
if(nums == null || nums.length == 0)return 0;
int i=0,k=0;
while(i < nums.length) if(nums[i++]!=val) nums[k++] = nums[i-1];
return k;
}
977.有序数组的平方
题目建议: 本题关键在于理解双指针思想
题目链接:. - 力扣(LeetCode)
文章讲解:代码随想录
视频讲解: 双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili
public static int[] sortedSquares(int[] nums) {
if(nums == null || nums.length == 0){
return nums;
}
int l=0,r=nums.length-1,t=nums.length-1;
int[] res = new int[nums.length];
while(l<=r){
int tmpR = nums[r] * nums[r];
int tmpL = nums[l] * nums[l];
if(tmpR>tmpL){
res[t--] = tmpR;
r--;
}else{
res[t--] = tmpL;
l++;
}
}
return res;
}
总结:
【数组】这个数据结构我也希望你能真正理解它
1. 定义与基本概念
- 定义:数组是一种基础的数据结构,用于在计算机内存中连续存储相同类型的数据。每个元素可以通过索引(或下标)进行访问,索引通常是从0开始的。
- 特点:固定大小、连续存储、随机访问。
2. 数组的内存表示
- 连续存储:数组中的元素在内存中连续存放,这使得通过索引访问元素非常高效,因为可以通过简单的数学计算(索引乘以元素大小)直接定位到元素的内存地址。
- 内存分配:在声明数组时,需要指定数组的大小(或长度),这决定了数组在内存中占用的空间大小。一旦分配,数组的大小就不能改变(静态数组)。
3. 数组的操作
- 访问元素:通过索引访问数组中的元素,如
arr[i]
。 - 插入元素:在静态数组中插入元素通常涉及元素的移动,因为需要为新元素腾出空间。这可能导致效率问题,特别是在数组接近满时。
- 删除元素:同样,删除元素也需要移动其他元素来填补被删除元素留下的空位。
- 遍历数组:通过循环遍历数组中的每个元素,执行相应的操作。
4. 数组的应用场景
- 基础数据结构:数组是许多高级数据结构(如栈、队列、链表、哈希表等)的基础。
- 数据存储:用于存储一系列有序的数据项,如学生的成绩、商品的库存等。
- 算法实现:在排序、搜索等算法中,数组是常用的数据结构。
5. 数组的变种
- 动态数组(如C++中的
std::vector
,Java中的ArrayList
):动态数组在需要时可以自动调整大小,解决了静态数组大小固定的问题。 - 多维数组:用于存储二维或更高维度的数据,如矩阵、图像等。
- 关联数组(或映射、字典):虽然不严格属于数组范畴,但它们在许多编程语言中以类似数组的方式实现,允许使用非整数索引(如字符串)来访问元素。
6. 深入理解数组的复杂度
- 时间复杂度:访问数组元素的时间复杂度为O(1),因为可以直接通过索引定位到元素。但在插入和删除元素时,如果数组大小固定,可能需要O(n)的时间复杂度(因为需要移动元素)。
- 空间复杂度:静态数组的空间复杂度为O(n),其中n是数组的大小。动态数组的空间复杂度可能更高,因为需要额外的空间来存储数组的大小和可能的扩容空间。
7. java中创建数组的方式
说起来很可笑,第一次面试的时候,不让用ide,我忘记怎么创建数组了
声明数组并分配空间,初始化数组元素:
int[] numbers = new int[5]; // 创建一个长度为5的整数数组
numbers[0] = 1; // 给数组元素赋值
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;
使用花括号(初始化时直接赋值):
int[] numbers = {1, 2, 3, 4, 5}; // 创建并初始化一个整数数组
使用匿名数组:
new int[]{1, 2, 3, 4, 5}; // 匿名数组,通常用在不需要引用的情况下
基本类型数组的简写(自动推导类型):
int[] numbers = new int[]{1, 2, 3, 4, 5}; // 使用var关键字自动推导类型
基本类型数组的简写(使用花括号):
int[] numbers = {1, 2, 3, 4, 5}; // 使用var关键字和花括号初始化数组
多维数组:
int[][] multiNumbers = {
{1, 2, 3},
{4, 5, 6}
}; // 创建一个二维整数数组
使用Arrays类的静态方法fill()来初始化数组:
int[] numbers = new int[5];
Arrays.fill(numbers, 10); // 将数组所有元素初始化为10
使用Arrays类的静态方法copyOf()来复制数组:
int[] original = {1, 2, 3};
int[] copy = Arrays.copyOf(original, 5); // 复制数组,长度为5
循环不变量规则:
循环不变量(loop invariant)是指在循环体内、每次迭代均保持为真的某种性质,它常被用来证明程序或算法的正确性。
对于一个给定的循环不变量,需要遵循以下三个关键属性:
- 初始化:在循环的第一次迭代之前,循环不变量为真。这意味着在循环开始时,该性质就已经成立。
- 保持:如果在循环的一次迭代之前循环不变量为真,那么在下一次迭代之前循环不变量同样为真。也就是说,只要前一轮迭代满足该性质,那么下一轮迭代开始前也会满足该性质。这类似于数学归纳法中的归纳步。
- 终止:当循环结束时,不变量能够提供有用的属性,用于帮助证实算法是正确的。通常,需要保证在循环结束时“循环不变量”和“循环终止条件”同时成立。这一点与数学归纳法有所不同,数学归纳法常采用无限的归纳步,而循环不变量的归纳往往随着循环的终止而结束
示例:
二分法的循环不变量: 每次迭代时目标元素始终在当前搜索区间内
冒泡排序的循环不变量: 每次迭代后,子序列末尾已排好序的元素个数是不断增加
插入排序的循环不变量: 每次迭代后,前面已排好序的子序列始终保持有序
快排的循环不变量: 每次迭代后,选定的基准元素左侧的元素都不大于它,右侧的元素都不小于它。