JAVA数组基础
基础知识
- 数组在Java中是一种引用数据类型(不是基本数据类型),父类为Object。
- 数组的存储位置在JVM(java虚拟机)内存划分中的堆内存中,用new来创建的内存空间,是一串连续的内存地址。每一个元素类型相同,因此占用内存空间大小一样。由于很难在内存空间上找到连续的特大空间,因此数组无法存储大量数据。
- 元素查询/检索方便,每个元素效率相同。随机删除/添加元素时效率较低。
初始化
-
静态初始化
int[] arr = new int[]{1,2,3}; int[] arr = {1,2,3};
指定内容,长度等于内容个数
-
动态初始化
int[] arr = new int[3];
指定长度,默认初始值由数组的字符类型而定
存储
- 数组是存放在连续内存空间上的相同类型数据的集合。
- 数组下标都是从0开始的。
- 内存空间的地址是连续的,所以在删除或者增添元素的时候,就难免要移动其他元素的地址
题目
LeetCode 704. 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
-
一开始的思路
- 二分查找,把数组分为两半,比较中间值的大小,小于则走前半,大于则走后半,利用递归的思想调用search函数,遇到的问题:递归传递的数组只有一半,不知道怎么去处理这个参数传递。
-
题解:不用递归,只需要设定循环条件:start < end即可
-
错误:
-
在循环外面定义了mid,没有考虑到mid每轮循环都是改变的。
-
左右怎么移动?start=mid,end=mid?(我一开始简单的想成了这个,但是会导致指针不动。)应该等于mid-1或者mid+1、
-
循环的结束条件是什么?start != end(当只有一个元素时,那么循环就变成无限循环了!)应该是start <= end
-
寻找mid的时候进入死循环,应该是int mid = start + (end-start)/2,这样写防止溢出
-
class Solution {
public int search(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while(start <= end){
int mid = start + (end-start)/2;
if(target == nums[mid]){
return mid;
}
else if(target < nums[mid]){
end = mid - 1;
}
else{
start = mid + 1;
}
}
return -1;
}
}
总结:
-
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素
-
区间的写法:左闭右闭or左闭右开or左开右闭?
-
定义 target 是在一个在左闭右闭的区间里,**也就是[left, right] **。区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution { public int search(int[] nums, int target) { // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算 if (target < nums[0] || target > nums[nums.length - 1]) { return -1; } int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); if (nums[mid] == target) return mid; else if (nums[mid] < target) left = mid + 1; else if (nums[mid] > target) right = mid - 1; } return -1; } }
-
定义 target 是在一个在左闭右开的区间里,也就是[left, right) 。有如下两点:
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
class Solution { public int search(int[] nums, int target) { int left = 0, right = nums.length; while (left < right) { int mid = left + ((right - left) >> 1); if (nums[mid] == target) return mid; else if (nums[mid] < target) left = mid + 1; else if (nums[mid] > target) right = mid; } return -1; } }
-
LeetCode 27. 移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
-
一开始的思路
- O(1)额外空间,思考的是交换,把等于target的元素与数组最后的元素交换,然后最后的数组元素指针往前挪一个,直到前指针大于后指针
class Solution { public int removeElement(int[] nums, int val) { int left = 0; int right = nums.length - 1; while(left <= right){ if(nums[left] == val){ nums[left] = nums[right]; nums[right] = val; right = right - 1; } else{ left = left + 1; } } return right + 1; } }
上面的可以解,但是数组顺序会改变,如果数组顺序不改变的话?
- 暴力解法,两个for循环,第一个for遍历数组,第二个for用于遇到了val值,把后面数组元素移动到前面来
class Solution { public int removeElement(int[] nums, int val) { int size = nums.length; 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--; size--; } } return size; } }
注意:
1. i--这一步,数组往前移动了一位以后,该判断的是移位以后的第一个,也就是现在i位置的元素,但是for循环会i++,所以这里应该i--
1. for循环i的终止条件应该是size而不是nums.length,因为nums.length是定值,去掉元素后数组大小会发生变化,所以应该为size。
-
双指针法
- 快指针&慢指针指向什么?
- 新数组就是不含有目标元素的数组。
- 快指针:判断是否应该加入新数组的元素,即寻找新数组的元素 。
- 慢指针:指向更新后新数组的第一个元素,即新数组下标的位置
class Solution { public int removeElement(int[] nums, int val) { int fast = 0; int slow = 0; for(fast = 0; fast < nums.length; fast++){ if(nums[fast] != val){ nums[slow] = nums[fast]; slow++; } } return slow; } }
- 快指针&慢指针指向什么?
LeetCode 977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序
输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变为 [16,1,0,9,100] 排序后,数组变为 [0,1,9,16,100]
-
一开始的思路:
- 先把整个数组平方后再排序
- 时间复杂度是 O(n + nlogn), 即O(nlogn)的时间复杂度。
-
题解:
- 数组其实是有序的,暴力解法并没有利用到这一特点。由于负数的存在,负数平方之后可能就会成为最大数,因此数组平方后的最大值就应该在数组的两端,不是最左边就是最右边,不可能是中间。
- 所以设置两个指针,一个指向最左一个指向最右,比较两数平方大小,将较大值放入新数组,再把指针往里移动,直到左指针大于右指针
class Solution {
public int[] sortedSquares(int[] nums) {
int left = 0;
int right = nums.length - 1;
int[] newnums = new int[nums.length];
int nn = nums.length - 1;
while(left <= right){
if(nums[left] * nums[left] < nums[right] * nums[right]){
newnums[nn--] = nums[right] * nums[right];
right--;
}
else{
newnums[nn--] = nums[left] * nums[left];
left++;
}
}
return newnums;
}
}
LeetCode 209.长度最小的子数组
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其和 ≥ target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
输入:target = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
-
一开始的思路:
- 按照数组长度遍历,长度为1时遍历一次找下有没有,然后长度+1继续遍历
- 后来仔细思考了一下,是要连续子数组,所以只需要遍历一次,第一个元素开头时,计算大于target的长度最小的 连续子数组,然后右移计算第二个元素开头时长度最小的连续子数组,如果是1就可以直接返回。
-
题解:
-
滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。用一个for循环来做2个for循环所做的事情。
-
这个索引下标
j
表示的究竟是滑动窗口里面的终止位置还是起始位置?-
可以先假设
j
表示的是起始位置,若j
表示的是起始位置,这个for循环一次一次把索引下标向后移动,这个终止位置要把后面所有的元素遍历一遍,才能返回所有以这个起始i
为起始位置的集合,然后我们再去判断这个集合里面的所有元素:如果>= target
,去搜集所有>= target
这些集合里面的所有的长度,再取一个最小的。 如果终止位置是一个一个向后移动的话,那么和这个暴力的解法又存在什么区别呢? -
此时你会发现:如果这一个for循环里面的这个j表示的是起始位置的话,那终止位置依然要把所有位置都遍历一遍,那么它的思路就和暴力是一样的。因此这一个for循环里面的j一定指向的是终止位置,而起始位置需要我们用动态移动的策略来移动起始位置,这样才能用一个for循环的思路来解决这道题。
-
-
应该写
if(sum >= target)
还是while(sum >= target)
?- 看看这个样例: 输入:target = 100, nums = [1,1,1,1,1,100]
- 时间复杂度是O(n) 。每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)
class Solution { // 滑动窗口 public int minSubArrayLen(int target, int[] nums) { int left = 0; int sum = 0; int min = nums.length + 1; for(int right = 0;right < nums.length; right++){ sum = sum + nums[right]; while(sum >= target){ int len = right - left + 1; min = min > len? len: min; sum = sum - nums[left]; left++; } } return min == nums.length + 1? 0 : min; } }
-
LeetCode 59.螺旋矩阵II
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int start = 0;
int count = 1;
int loop = 0;
int i = 0;
int j = 0;
while(loop++ < n / 2){
for(j = start; j < n - loop; j++){
matrix[start][j] = count++;
}
for(i = start; i < n - loop; i++){
matrix[i][j] = count++;
}
for(;j > start; j--){
matrix[i][j] = count++;
}
for(;i > start; i--){
matrix[i][j] = count++;
}
start++;
}
if(n % 2 !=0){
matrix[n / 2][n / 2] = n * n;
}
return matrix;
}
}
题解:
-
循环不变量:把一个正方形分为四条边,每一条边的代码都设置为一样的循环。即每条边都数组前闭后开区间,只控制每条边的第一个元素而不控制每条边的最后一个元素,把最后一个元素放在下一个循环中作为第一个元素。
-
转几圈?
- 转n/2圈,于是涉及到n为奇数或者偶数,n为基数时,转完圈中间最后剩一格,需要最后手动赋值最后一格。
-
起始位置?
- 第一圈起始位置为(0, 0),起始位置每圈都要变,即每圈+1,所以for循环里面i, j的起始值也会变,把i, j赋值为0显然不对,应该赋值为start,每圈的起始位置。