977.有序数组的平方
1、题目
2、题解
思路一:暴力排序
讲每个数平方过后重新排序一下,
// 时间复杂度 O(nlogn)
class Solution
{
public:
vector<int> sortedSquares(vector<int>& nums)
{
for (int i = 0; i < nums.size(); i++)
{
nums[i] *= nums[i];
}
sort(nums.begin(), nums.end()); // 快速排序
return nums;
}
};
思路二:双指针法
定义两个指针i和j,指针 i 指向起始位置,j 指向终止位置。
定义一个用来存放答案的新数组 ans。
定义一个变量k用来指向ans的末尾位置
因为这个数组最小的在中间,最大的在两端,所以从两端的数开始平方并进行比较,把大的数存入答案数组中,然后进行下一轮比较,直到所有数都比较完。
当 abs(nums[end]) > abs(nums[front]) 则 result[i] = nums[end] * nums[end];
当 abs(nums[end]) <= abs(nums[front]) 则 result[i] = nums[front] * nums[front];
// 时间复杂度 O(n)
class Solution
{
public:
vector<int> sortedSquares(vector<int>& nums)
{
int end = nums.size() - 1;
int front = 0;
vector<int> result(nums.size());
for (int i = result.size() - 1; i >= 0; i--)
{
if (abs(nums[end]) > abs(nums[front]))
{
result[i] = nums[end] * nums[end];
end--;
}
else
{
result[i] = nums[front] * nums[front];
front++;
}
}
return result;
}
};
3、总结
在这题中,使用的是相向双指针,又称对撞指针。双指针法比暴力排序降低了不少时间复杂度。在前面的移除元素题中用到了同向双指针,又称快慢指针。应该多把这两题联合起来看看,理解双指针的思想。
209.长度最小的子数组
1、题目
2、题解
思路一:暴力解法
使用两个for循环来寻找符合条件的子序列
// 时间复杂度 O(n^2)
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;
}
};
思路二:滑动窗口
滑动窗口 是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。
在本题中实现滑动窗口,主要确定如下三点:
窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?
窗口内是满足题目答案的子序列
窗口的起始位置如何移动:当窗口的值大于s了,起始指针就要向前移动了(缩小窗口)
窗口的结束位置如何移动:当窗口的值小于s了,末尾指针就要向后移动了(扩大窗口)
// 时间复杂度:O(n)
class Solution
{
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int ans = INT32_MAX;// INT_MAX 和 INT_MIN 是 C++ 的两个宏,代表了整型变量能够存储的最大正整数和最小负整数,分别为 2147483647 和 -2147483648
int sum = 0; // 滑动窗口数值之和
int front = 0;
int end = 0;
while (end <= nums.size() - 1)
{
sum += nums[end];
while (sum >= target)
{
ans = min(ans, (end - front + 1)); // 更新最小子序列
sum -= nums[front++];// 缩小窗口的时候不要忘记把新窗口外的数删除
}
end++;
}
return ans == INT32_MAX ? 0 : ans;
}
};
3、总结
可以直观的看到,滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。滑动窗口法可以看做是一种双指针方法的特例,两个指针都起始于原点,并一前一后向终点前进。
什么情况可以用滑动窗口来解决实际问题呢?
一般给出的数据结构是数组或者字符串
求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
该问题本身可以通过暴力求解
59.螺旋矩阵II
1、题目
2、写法
这题不涉及算法,是一道模拟过程的题目。
做题时需要注意坚持循环不变量原则,也就就是对边界的处理时,对每一条边都要坚持左闭右开或者左闭右闭的原则,不能对每一条边的处理原则都不相同,在下面的解中我坚持的是左闭右开的原则
模拟顺时针画矩阵的过程
-
填充上行从左到右
-
填充右列从上到下
-
填充下行从右到左
-
填充左列从下到上
// 时间复杂度 O(n^2)
class Solution
{
public:
vector<vector<int>> generateMatrix(int n)
{
vector<vector<int>> result(n, vector(n, 0));
int i = 1;
int row = 0; //行
int col = 0; //列
int top = 0; //上边加
int buttom = n - 1; //下边界
int left = 0; //左边界
int right = n - 1; //右边界
while (i <= n*n)
{
// 从左到右
while (col <= right)
{
result[row][col++] = i++;
}
col--;
row++; //换到下一行
top++; //缩小上边界
// 从上到下
while (row <= buttom)
{
result[row++][col] = i++;
}
row--;
col--; //换到下一列
right--; //缩小右边界
// 从右到左
while (col >= left)
{
result[row][col--] = i++;
}
col++;
row--; //换到下一行
buttom--; //缩小下边界
// 从下到上
while (row >= top)
{
result[row--][col] = i++;
}
row++;
col++; //换到下一列
left++; //缩小下边界
}
//如果n为奇数单独填充中间的数
if (n % 2 == 1)
result[n / 2][n / 2] = n * n;
return result;
}
};
3、总结
注意循环不变量原则,其他的照着模拟过程敲出来就ok