本篇博客练习数组专题:
1.二分查找
本题的关键在于处理好边界问题,例如
- 进行循环时,到底是 while(left < right) ,还是 while(left <= right)?
- 区间指针移动时,到底是 right=middle,还是right=middle - 1?
答案是:
- 如果target 是在一个在左闭右闭的区间里,也就是[left, right], 此时就是while (left <=right),right = middle - 1。
- 如果target 是在一个在左闭右开的区间里,也就是[left, right), 此时就是while (left <right),right = middle。
弄明白了这个问题,二分查找的题就比较清楚了。以下给出第一种写法:
/*
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,
写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
*/
public static void main(String[] args) {
search(new int[]{-1,0,3,5,9,12}, 9);
search(new int[]{-1,0,3,5,9,12}, 2);
}
// 双指针,一左一右
public static int search(int[] nums, int target) {
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int l = 0;
int r = nums.length -1;
// target 是在一个在左闭右闭的区间里,也就是[left, right]
// 决定了middle指针如何移动
while (l <= r) {
int middle = (int) Math.floor(l + r);
if (nums[middle] > target) {
r = middle - 1;
} else if (nums[middle] < target) {
l = middle + 1;
} else {
System.out.println(middle);
return middle;
}
}
System.out.println("-1");
return -1;
}
2.移除元素
本题采用快、慢双指针解法。
快指针指向不需要删除的元素,放入到新数组中;
慢指针指向新数组元素的下标。
/*
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
*/
public int removeElement(int[] nums, int val) {
// 双指针:
// fast指针:指向不需要删除的元素,放入到新数组中
// slow指针:指向新数组元素的下标
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast]; //把fast指针指向的元素 赋值给slow指针指向的元素
slow++;
}
}
return slow; //slow的大小即为新数组长度
}
3.有序数组的平方
本题给出两种解法:
(1)暴力求解: 先把数组全部元素平方,再排序;
(2)双指针解法: 同样是左、右两指针,分别指向数组的头、尾元素。与上题移除元素类似的是, 本题往新数组里更新元素时,先更新值最大的元素(也就是从下标最大处倒着更新)
/*
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组
要求也按 非递减顺序 排序。
*/
// 1.双指针
public int[] sortedSquares(int[] nums) {
int l = 0;
int r = nums.length - 1;
// 往新数组里更新元素时,先更新值最大的元素
// 也就是从下标最大处倒着更新
int[] result = new int[nums.length];
int index = nums.length - 1;
while (l <= r) {
if (nums[l] * nums[l] > nums[r] * nums[r]) {
result[index] = nums[l] * nums[l];
index--;
l++; //l往右走
} else {
result[index] = nums[r] * nums[r];
index--;
r--; //r往左走
}
}
return result;
}
// 2.暴力求解
public int[] sortedSquares2(int[] nums) {
for (int i = 0; i < nums.length; i++) {
nums[i] *= nums[i];
}
Arrays.sort(nums);
return nums;
}
4.长度最短的子数组
本题采用的是滑动窗口的思想:
- 滑动窗口就是 满足其和 ≥ target 的长度最小的 连续 子数组。
- 窗口的起始位置如何移动?
如果当前窗口的所以元素的和大于target了,窗口就要向前移动了(也就是该缩小了)。 - 窗口的结束位置如何移动?
窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
/*
给定一个含有 n 个正整数的数组和一个正整数 target。
找出该数组中满足其总和>=target的长度最小的 连续 子数组,并返回其长度。
如果不存在符合条件的子数组,返回 0 。
*/
// 滑动窗口(双指针)
public int minSubArrayLen(int target, int[] nums) {
int l = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
// 先移动窗口终止位置
for (int r = 0; r < nums.length; r++) {
sum += nums[r];
while (sum >= target) {
// 统计满足条件的 子数组长度
result = Math.min(result, r - l + 1);
// sum先减去起始位置的值,再移动起始指针
sum -= nums[l++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}