1. 977. 有序数组的平方
题目:
给你一个按 非递减顺序 排序的整数数组
nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。示例 1:
输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变为 [16,1,0,9,100] 排序后,数组变为 [0,1,9,16,100]示例 2:
输入:nums = [-7,-3,2,3,11] 输出:[4,9,9,49,121]提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
已按 非递减顺序 排序进阶:
- 请你设计时间复杂度为
O(n)
的算法解决本问题
1.1 暴力
平方后再排序。
1.2 双指针
如何想到双指针的写法:因为最大的数一定在两个指针之一
class Solution {
public int[] sortedSquares(int[] nums) {
int[] ans = new int[nums.length];
int left = 0, right = nums.length - 1;
int a = nums.length - 1;
while (left <= right) {
if (nums[left] * nums[left] >= nums[right] * nums[right]) {
ans[a--] = nums[left] * nums[left];
left++;
} else {
ans[a--] = nums[right] * nums[right];
right--;
}
}
return ans;
}
}
2. 209. 长度最小的子数组
题目
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的
子数组
[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:输入:target = 4, nums = [1,4,4]
输出:1
示例 3:输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
进阶:
如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。
2.1 暴力
是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
2.2 滑动窗口
思路
- 循环索引下标应为终止位置。若为起始位置,最差的情况下所有起始位置和所有终止位置都要遍历,则跟暴力解法一样了。
- 滑动窗口最重要的是如何移动起始位置。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了(也就是wihle条件是sum与s的关系),窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int ans = Integer.MAX_VALUE;
int left = 0, right = 0;
int sum = 0;
for (;right < nums.length;right++) {
sum += nums[right];
while (sum >= target) {
ans = Math.min(ans, right - left + 1);
sum -= nums[left];
left++;
}
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
2.3 相关题目
3. 59. 螺旋矩阵 II
题目
给你一个正整数
n
,生成一个包含1
到n2
所有元素,且元素按顺时针顺序螺旋排列的n x n
正方形矩阵matrix
。示例 1:
输入:n = 3 输出:[[1,2,3],[8,9,4],[7,6,5]]示例 2:
输入:n = 1 输出:[[1]]提示:
1 <= n <= 20
思路:
1、每圈处理:有几圈
2、每条边处理:如何定义区间(左闭右开、还是左闭右闭),左闭右开每条边处理逻辑才是类似的。
3.1 分层解法
class Solution {
public int[][] generateMatrix(int n) {
int[][] ans = new int[n][n];
// 00,01,02,12,22,21,20,10,11
// 1、每圈作为一个循环:知道几个圈,每个圈边长,起始点:00、11、22...
// 2、每条边左闭右开:每条边处理第一个节点,不处理最后一个节点
int count = n / 2 + n % 2;
System.out.println(count);
int cur = 1;
for (int i = 0; i < count; i++) {
// 边长
int len = n - i * 2;
// 起始点
int j = i, k = i;
// System.out.println(count + " " +cur + " " +len + " "+ j + " " +k );
// 只有一个节点,一个节点即为最后一个节点,特殊处理,且循环结束
if (k == i + len - 1) {
ans[j][k] = cur;
break;
}
// 第一条边,都不处理最后一个节点
while (k < i + len - 1) {
ans[j][k++] = cur++;
}
while (j < i + len - 1) {
ans[j++][k] = cur++;
}
while (k > i) {
ans[j][k--] = cur++;
}
while (j > i) {
ans[j--][k] = cur++;
}
}
return ans;
}
}
3.2 类似题目
4 总结
数组的经典方法:
- 二分法:例如力扣上的704.二分查找,通过从中间元素开始往左右查找,避免一次遍历所有元素。二分查找法的时间复杂度为O(logn)。
- 双指针法:通过一个快指针和一个慢指针在一个for循环下,完成两个for循环的工作。也存在使用左右指针的方式。快慢指针从同一个地点遍历方向相同,左右指针从一头一尾往中间进行遍历。
- 滑动窗口:通过两个指针建立一个滑动窗口,滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
- 模拟行为:同样在数组内很常见,要确定好模拟的需求,确定循环体,保持循环不变量原则。