LeetCode 977.有序数组的平方
第一眼看到这道题的时候,想到的是暴力解法,就是先把所有数据先进行平方,然后再把数组进行排列。
class Solution {
public:
vector<int> sortedSquares(vector<int>& A) {
for (int i = 0; i < A.size(); i++) {
A[i] *= A[i];
}
sort(A.begin(), A.end()); // 快速排序
return A;
}
};
但是这样子就算用快排,在时间复杂度的角度来说,也是O(n+logn)不能达到极简,而且题目中也要求时间复杂度为O(n)的算法,也就是我们只能遍历一遍数组。
而且题目中明确告诉我们数组是非递减排列的,也就是说,数组里的数经过平方后,会出现两端大,中间小的情况。我们不妨用上上一天所用过的双指针思路,但这个双指针不是一快一慢的关系,而是分布在数组两端,然后一起向中间移动。即首先比较当前位置的一头一尾指针哪个绝对值更大,将更大的数放到新的数组里面进行保存。但要注意,我们得到的是数最大的,要想在新数组中从小到大排列,我们就需要从后往前去放数据。
代码如下:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size() - 1;
vector<int> res(n + 1, 0);
int Head = 0, Tail = n;
while (Head != Tail)
{
if (abs(nums[Head]) > abs(nums[Tail]))
{
res[n--] = nums[Head] * nums[Head++];
}
else
{
res[n--] = nums[Tail] * nums[Tail--];
}
}
res[n] = nums[Head] * nums[Head]; //记得最后补上两个指针相遇时的数
return res;
}
};
我们发现现在的情况就是时间复杂度为O(n),就省去了后面的排序。但是也会发现我们开辟了一个新的数组,也就是进行了拿空间换时间的操作。
LeetCode 209.度最小的子数组
看到题目的一瞬间我是懵的,因为这是我第一次看到这样的题目,首先是不懂题目的意思,子数组的概念没搞明白,然后就是不知道怎么去找。
后来查了之后才知道,子数组的定义:一个或连续
多个数组中的元素组成一个子数组(子数组最少包含一个元素)。这样子就明白了很多,它跟子序列不同,子序列的定义:子序列就是在原来序列中找出一部分组成的序列(子序列不一定连续
)。所以我们首先可以用暴力解法去破解它。
暴力解法:使用两个for循环,外层for循环是起始位置,逐渐向数组尾端移动,另一组for循环是终止位置,由不同的起始位置出发,一步一步向末尾位置移动,途中记录满足条件的最小长度。这样子的时间复杂度为O(n²)。
代码如下:
class Solution {
public:
int minSubArrayLen(int s, vector<int> &nums)
{
int result = INT32_MAX; // 最终的结果
int sum = 0; // 子数组的数值之和
int subLength = 0; // 子数组的长度
for (int i = 0; i < nums.size(); i++)
{ // 设置子数组起点为i
sum = 0;
for (int j = i; j < nums.size(); j++)
{ // 设置子数组终止位置为j
sum += nums[j];
if (sum >= s)
{ // 一旦发现子数组和超过了s,更新result
subLength = j - i + 1; // 取子序列的长度
result = result < subLength ? result : subLength;//拿这一次的跟之前的作比较
break; // 因为我们是找符合条件最短的子数组,所以一旦符合条件就break
}
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子数组
return result == INT32_MAX ? 0 : result;
}
};
然后就是用双指针,这次的双指针的作用是做一个滑动窗口。用一个for循环去遍历,这里for循环里面遍历的是终止位置(即Tail),从0开始,一直到最后一个元素。我们在for循环里面去把每一个Tail所遍历的元素都相加起来得到sum,直到sum大于等于目标值,记录这时候的长度。然后让起始位置(Head)开始一点一点往后移动,每移动一次更新一次最小长度值Res,直到sum小于目标值,这时候重回for循环里面,继续移动Tail。
窗口的起始位置(Head)如何移动:如果当前窗口的值大于sum了,窗口就要向前移动了(也就是该缩小了)。
窗口的终止位置(Tail)如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int N = nums.size();
int Head = 0, Tail = 0;
int Sum = 0;
int Res = INT32_MAX; //初始化的时候,让Res为一个32位整型最大值,方便后面找出更小的
for (; Tail < N; Tail++)
{
Sum += nums[Tail];
while (Sum >= target)
{
Res = min(Res, Tail - Head + 1);
Sum -= nums[Head++];
}
}
return Res == INT32_MAX ? 0 : Res;
}
};
LeetCode 59.螺旋矩阵
这道题看起来并不麻烦,不涉及算法的设计,主要注重一个过程的模拟。四条边的循环,应该设置什么条件?怎么去设置?奇偶层的变化数组中心位置怎么处理?这些才是这道题应该注意的。
就像当初写二分法的时候一样,我们都要追寻一个循环不变量,制定一个统一的规则,让我们在写代码或者理解的时候能够更加容易分辨界限,不会陷入到循环的漩涡中,也不会写出每条边不一样的判定条件。
在这里我们设定左闭右开原则,即同一条边的最后一个元素(即拐角)留给下一条边作为起点,这样子就能避免重复、遗漏的问题。
正确代码如下:
class Solution {
public:
vector<vector<int>> generateMatrix(int n)
{
vector<vector<int>> Res(n, vector<int>(n, 0));
int startX = 0, startY = 0;
int offset = 1, count = 1;
int Round = n / 2;
while (Round--)
{
int x = startX;
int y = startY;
for (; x < n - offset; x++)
{
Res[y][x] = count++; // 注意数组x,y的位置,是Res[y][x],并不是Res[x][y]
}
for (; y < n - offset; y++)
{
Res[y][x] = count++;
}
for (; x > startX; x--)
{
Res[y][x] = count++;
}
for (; y > startY; y--)
{
Res[y][x] = count++;
}
startX++;
startY++;
offset++;
}
if (n % 2 == 1)//如果使奇数,中心位置补充一个
{
Res[startY][startX] = count;
}
return Res;
}
};