代码随想录0977.有序数组的平方
双指针法经典题目 | LeetCode:977.有序数组的平方
先寻找非递减数组中第一个非负数
class Solution {
public int[] sortedSquares(int[] nums) {
int i, j, k, n;
n = nums.length;
int[] res = new int[n];
i = 0;
while (i < n && nums[i] < 0) {
++i;
}
// System.out.println(nums[i]);
if (i == 0) {
while(i < n) {
res[i] = nums[i] * nums[i];
++i;
}
} else if (i == n) {
while (i > 0) {
res[n - i] = nums[i - 1] * nums[i - 1];
--i;
}
} else {
j = i - 1;
k = 0;
while (j >= 0 || i < n){
if (j < 0 || i < n && nums[i] + nums[j] <= 0) {
res[k] = nums[i] * nums[i];
++i;
} else if (i > n - 1 || j > -1 && nums[i] + nums[j] > 0) {
res[k] = nums[j] * nums[j];
--j;
}
++k;
}
}
return res;
}
}
其实文章讲解中并不是我这种做法。文章的做法是相反的。我寻找平方最小的元素顺序填充,文章寻找平方最大元素逆序填充。平方最大的元素一定在数组两端,因此双指针从两端向中间靠近。这个做法我也写了一版提交。代码如下
class Solution {
public int[] sortedSquares(int[] nums) {
int[] res = new int[nums.length];
int i, j, k;
i = 0;
j = nums.length - 1;
k = j;
int left, right;
while(i < j) {
left = nums[i] * nums[i];
right = nums[j] * nums[j];
if (left > right) {
res[k] = left;
++i;
} else {
res[k] = right;
--j;
}
--k;
}
res[k] = nums[i] * nums[i];
return res;
}
}
代码随想录209.长度最小的子数组
拿下滑动窗口! | LeetCode 209 长度最小的子数组
非常痛心就是一看到子数组就想动态规划,用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第 i i i个元素开始到第 j j j个元素结束的子数组中能容纳 t a r g e t target target的最小长度。但是初始化就没得办法,状态转移也没得办法。
所以还是尝试了滑动窗口。从 [ 0 , 0 ] [0, 0] [0,0]子数组开始,扩大右边界至窗口内子数组求和大于等于 t a r g e t target target,离开该层循环后并维护一个窗口最小宽度;然后左边界右移,最多移至右边界且窗口内子数组求和小于 t a r g e t target target时离开循环,循环内部维护窗口最小值。右边界超过数组最大长度时结束全部循环,并将维护的窗口最小值作为结果返回,如果始终没有足够大的窗口则返回0。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int res = nums.length + 1;
int i, j, sum;
i = 0;
j = 0;
sum = nums[0];
while(j < nums.length) {
while(sum < target){
if (++j > nums.length - 1) {
break;
}
sum += nums[j];
}
while (j < nums.length && sum >= target) {
res = Math.min(res, j - i + 1);
sum -= nums[i];
++i;
}
}
return res > nums.length ? 0 : res;
}
}
为什么用滑动窗口而不用动态规划。
如果我能找到一个子数组,使其元素之和达到或大于 t a r g e t target target,那么任何含有该子数组的数组都应该有一个解的上界是该子数组的长度。所以动态规划的状态转移应该是这个上界的转移,最优解是最小上界。但是想遍历得到最小上界就跟暴力解法没有区别了。除非首先找到这个最小上界就可以不用遍历,但问题本身就是求这个最小上界,形成了嵌套所以没办法用动态规划。
这个题好在数组元素都是非负的,保证了某数组的元素和一定大于等于其任何一个子数组的元素和。当我停止移动边界的时候,能确保当前左边界和更右边的假想的右边界确定的子数组一定满足性质,所以此时只要移动左边界寻找下一个合适的左边界即可。这个性质决定了滑动窗口方法的合理性。
代码随想录0059.螺旋矩阵II
一入循环深似海 | LeetCode:59.螺旋矩阵II
这个题目不是第一次跟着代码随想录刷了,至少是第三次刷。前两次是跟书做的,先确定螺旋的圈数,每一圈用4个顺次的for循环分别遍历填充4条边,每一圈的4个for循环结束后更新下一圈的起始坐标和边长。所有圈数转完再考虑是否填充最中心的单点。
但由于是三刷,我决定换一个做法,毕竟前两次看题解都没写过,今天试一下。4个方向的集合用一个4行2列的数组表示右下左上。由于矩阵初始化全0,可以通过判断按原方向继续前进的下一个位置是否越界或不越界时是否非零来判断是否该调整为下一个方向。这种做法完全模拟螺旋填充的行为,不需要通过奇偶来判断是否填充中心节点。
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int i, j, count, direction, next_i, next_j;
i = 0;
j = 0;
count = 1;
direction = 0;
while(count < n * n + 1){
matrix[i][j] = count;
next_i = i + directions[direction][0];
next_j = j + directions[direction][1];
if (next_i > n - 1 || next_j > n - 1 || next_i < 0 || next_j < 0) {
direction += 1;
direction %= 4;
} else if (matrix[next_i][next_j] != 0){
direction += 1;
direction %= 4;
}
i += directions[direction][0];
j += directions[direction][1];
++count;
}
return matrix;
}
}
两种方法内存消耗和用时是差不多的,难分高下了。
感觉今天主要还是在用指针遍历数组,977是两边向中间或者中间向两边,209是窗口两个边界向同一边,59是提前设计好的移动方向转移。要说这3个题有什么共性就不好找,感觉最重点的应该还是滑动窗口,其他两个题还是更看重对问题的理解和模拟。