977.有序数组的平方
没看解题思路前:遍历数组,对每个元素平方后放入新的数组中,再对新数组做插入排序
public class Solution {
public int[] SortedSquares(int[] nums) {
int[] newNums = new int[nums.Length];
for (int i = 0; i < nums.Length; i++)
{
newNums[i] = (int)Math.Pow(nums[i], 2);
int temp = newNums[i];
int j;
for (j = i - 1; j >= 0; j--)
{
if (newNums[j] < temp)
{
break;
}
else
{
newNums[j + 1] = newNums[j];
}
}
newNums[++j] = temp;
}
return newNums;
}
}
看完解题思路后:双指针分别指向数组的首和尾,循环里每次比较两个指针所指向的数 的平方,大的元素放置到新数组中(从后往前),直到左指针大于右指针时循环结束,时间复杂度为O(n)
public class Solution
{
public int[] SortedSquares(int[] nums)
{
int[] newNums = new int[nums.Length];
int k = nums.Length - 1;
for (int low = 0, high = nums.Length - 1; low <= high;)
{
int lowNum = (int)Math.Pow(nums[low], 2);
int highNum = (int)Math.Pow(nums[high], 2);
if (lowNum >= highNum)
{
newNums[k--] = lowNum;
low++;
}
else
{
newNums[k--] = highNum;
high--;
}
}
return newNums;
}
}
PS:知道这题是用双指针法,但是没有想到如何去用。并且本来打算暴力一点直接整个数组平方后再排序(可以用快速,这样时间复杂度只要O(nlogn),但是后来有另一个想法想试试,就是在每次平方后立马对新数组进行一次插入排序,结果时间复杂度更高O(n²)。
反思:没有想到如何去用双指针法,主要是因为没有观察到这组数的特点:有序数组中,平方后最大的数一定是在数组的两端,以后还是得多观察下规律。
209.长度最小的子数组
没看解题思路前:事先知道使用滑动窗口的方法,所以尝试带入:在循环中记录开始索引start,并不断计算总和total,直到total>=target的时候,记录结束索引end,并计算出本轮有多少个元素相加,下一轮再将开始索引start+1作为新的start开始计算,直到start等于数组最后一个索引时,停止循环。(这看着跟暴力解法没什么区别。。。看似是一个循环,实际也是两个循环,不同的是通过在一个循环中不停地修改i的值来实现的)
public class Solution
{
public int MinSubArrayLen(int target, int[] nums)
{
int start = 0;
int end = 0;
int minLength = 0;
int total = 0;
for (int i = 0; i < nums.Length; i++)
{
if (total == 0) start = i;
total += nums[i];
if (total >= target)
{
end = i;
int temp = end - start + 1;
minLength = (temp < minLength || minLength == 0) ? temp : minLength;
i = start;
total = 0;
}
}
return minLength;
}
}
看完解题思路后:使用滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果
实现滑动窗口前需要明确的事:
- 窗口内是什么?满足其和 ≥ target 的长度最小的 连续 子数组
- 何时移动窗口的起始位置?如果当前窗口的值大于等于target了,说明窗口要向前移动了,也就是该缩小了,此时移动起始位置
- 何时移动窗口的结束位置?窗口的结束位置就是遍历数组的索引,如果当前窗口的值小于target了,说明窗口该扩大了,此时移动结束位置
public class Solution
{
public int MinSubArrayLen(int target, int[] nums)
{
int start = 0;
int minLength = int.MaxValue;
int total = 0;
for (int end = 0; end < nums.Length; end++)
{
total += nums[end];
while (total >= target)
{
minLength = Math.Min(end - start + 1, minLength);
total -= nums[start];
start++;
}
}
return minLength == int.MaxValue ? 0 :minLength;
}
}
反思:滑动窗口是在原来窗口的基础上进行移动(即减去起始位置的元素,更新原来的总和),而不是像我原先认为的只移动起始位置,然后重新计算窗口(即从起始位置开始,重新计算总和)
⭐️59.螺旋矩阵
没看解题思路前:在试图找规律,行i,列j,j先变化,再变化i,然后再变化j,再变化i,最后遍历到终点 [(n/2),(n-1)/2]时停止。但是在实现过程中越写越乱,边界搞不清,所以没实现出来。
看了解题思路后:本题不涉及任何算法,考察的是对代码的掌控能力(感觉挺主观的)
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去
这四条边怎么画,画每条边时都要统一规则,坚持一致的左闭右开(或者左开右闭的原则),这样这一圈才能按照统一的规则画下来,如图:
public class Solution
{
public int[][] GenerateMatrix(int n)
{
int[][] arr = new int[n][];
for (int k = 0; k < n; k++)
arr[k] = new int[n];
int startX = 0, startY = 0;
int num = 1;
int i, j;
int offset = 1;
// 除2是因为:搜索一圈之后,下一圈的上边会往下走,下边会往上走,高度就少2,
// 左边会往右走,右边会往左走,宽度就少2,每次下一圈都会比上一圈的高度宽度都少2
int loop = n / 2; //循环几圈
while ((loop--) > 0)
{
i = startX;
j = startY;
// 下面开始的四个for就是模拟转了一圈(左闭右开)
// 上行从左到右
for (; j < n - offset; j++)
arr[i][j] = num++;
// 右列从上到下
for (; i < n - offset; i++)
arr[i][j] = num++;
// 下行从右到左
for (; j > startY; j--)
arr[i][j] = num++;
// 左列从下到上
for (; i > startX; i--)
arr[i][j] = num++;
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startX++;
startY++;
// offset 控制每一圈里每一条边遍历的长度,圈小了长度自然也变小了
offset++;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值(最中间的位置可以说是最后一圈的第一个)
if (n % 2 > 0)
arr[startX][startY] = n * n;
return arr;
}
}
解题步骤:
- 初始化矩阵和起始位置
- 循环遍历每一圈,模拟填充上行、右列、下行、左列
- 控制每一圈里每一条边遍历的长度,起始位置的更新
- 单独处理奇数情况下矩阵中间位置的赋值