目录
977.有序数组的平方
思路
1.暴力思路
2.双指针思路
代码实现
1.暴力思路
(1)冒泡排序
(2)快速排序
2.双指针思路
总结
209.长度最小的子数组
思路
代码实现
总结
59.螺旋矩阵II
思路
代码实现
总结
题目链接:977.有序数组的平方
拿到题目最直接的思路就是暴力方法,首先将数组元素进行平方,然后再进行排序。当使用O()的排序算法时,直接超时了;使用O(nlogn)的排序算法时,能通过,但是不满足进阶要求:设计时间复杂度为O(n)的算法解决问题。
题目建议用双指针的思路进行解答,于是我首先试着用双指针的思路进行分析。昨天见到了两种简单的双指针算法(快慢双指针、左右双指针),于是我首先想向这两种算法靠拢。
已知nums已经按非递减顺序排列完毕,那么说明其已经是有序数组,分析 0 元素,那么,0 之后的平方数就是非递减顺序,而 0 之前的平方数则是递减顺序,只需要将 0 之前的数依次按大小插入 0 之后的数组中就能完成任务。
我看到题目中并没有要求空间复杂度,我想可以单独创建一个数组,直接用来记录排序后的非递减数组,设双指针从两头开始为非递减数组大端向中间遍历,这样问题就很简单了。
时间复杂度过高,会超出时间限制
class Solution {
public:
vector sortedSquares(vector& nums) {
int size = nums.size();
for(int i = 0; i < size; ++i){
nums[i] = nums[i] * nums[i];
}
for(int i = 0; i < size; ++i){
for(int j = size - 1; j > i; --j){
if(nums[j] < nums[j-1])
swap(nums[j], nums[j-1]);
}
}
return nums;
}
};
class Solution {
public:
void quick_sort(vector& q, int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[(l + r >> 1)];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
vector sortedSquares(vector& nums) {
int size = nums.size();
for(int i = 0; i < size; ++i){
nums[i] = nums[i] * nums[i];
}
quick_sort(nums, 0, size-1);
return nums;
}
};
class Solution {
public:
vector sortedSquares(vector& nums) {
vector res;
int size = nums.size();
res.resize(size);
for(int i = 0; i < size; ++i) nums[i] = nums[i] * nums[i];
int i = 0, j = size - 1;
int index = size - 1;
while(i <= j){
res[index--] = (nums[i] > nums[j] ? nums[i++] : nums[j--]);
}
return res;
}
};
上面我自己写的版本,但是看了解析以后发现,这样的时间复杂度是O(2n),如果能在一个循环里完成好像时间复杂度会减少,所以还是尽量在一个时间复杂度里完成,如下:
class Solution {
public:
vector sortedSquares(vector& A) {
int k = A.size() - 1;
vector result(A.size(), 0);
for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
if (A[i] * A[i] < A[j] * A[j]) {
result[k--] = A[j] * A[j];
j--;
}
else {
result[k--] = A[i] * A[i];
i++;
}
}
return result;
}
};
本题不难,能找到潜在规律,但是一开始走错方向了,双指针从两边到中间是正解,而我一开始想的是先找其中 0 元素,再从 0 到两边,这样还得讨论是否有 0、是否全正或全负、0 是否在两段等特殊情况,这就把问题想复杂了。有时候做算法就是这么神奇,明明已经找到了正确的规律,思路却偏偏会跑偏。
题目链接:209. 长度最小的子数组
这题的建议是用滑动窗口的思想,之前好像学习过,于是先理一下自己的思路:
1.滑动窗口,首先设立左右窗口边界,初始窗口大小为 0,左右边界在数组最左边;
2.若当前窗口内数的总和小于target,则右边界向右扩张,直至大于等于target;若当前窗口内数的总和大于等于target,则左窗口向右进行收缩,直至等于target,每次收缩前将当前窗口大小与最小窗口大小比较,实时更新窗口最小值。
3.当右窗口到最右端,做窗口不再收缩时,查看窗口最小值,即为所求结果。
class Solution {
public:
int minSubArrayLen(int target, vector& nums) {
int size = nums.size();
int left = 0, right = 0; // 设窗口边界,采取左闭右开原则
int sum = 0; // 记录窗口内数的综合
int minmum = size; // 窗口大小最小值
while(1){
while(sum >= target){ // 数和足够,左边界向右收缩
int len = right - left;
//minmum = (minmum < len ? minmum : len);
minmum = min(minmum, len);
sum -= nums[left++];
}
if(sum < target){ // 数和不足,向右扩张
if(right < size) sum += nums[right++];
else{
if(left == 0) return 0;
else break;
}
}
}
return minmum;
}
};
看了下代码随想录的解析,基本思想是大致相同的,我就不贴了。
这一题是看到了建议用滑动窗口,所以就直奔这个思路去了,因为之前学过,所以很轻松就能想到解法,不知道如果没有看到建议,能不能想到这个滑动窗口的方法,这还需在后面多做练习积累经验。
题目链接:59.螺旋矩阵II
这一题拿到我什么建议也没有看。读过题后的思路是设置边界,矩阵元素的遍历顺序很明确,先向右——向右不通就向下——向下不通就向左——向左不通就向上——向上不通再向右,如此反复,我想只要确定好边界,使其向一个方向遍历到边界不通,自动转向,最终实现全部遍历。
问题遇到的第一个难点是二维vector容器的使用语法,初始化、引用等。引用同数组,初始化可如下方式:
vector> res(n, vector(n, 0)); // 使用vector定义一个二维数组
但是在算法实现过程中,由于我想着四个方向的趋势一样,且大小有关,想放在一个循环里实现,就多了很多复杂的情况,我修改多次始终有误,遂放弃,查看解析发现原来是用四个循环单独考虑,则情况就简单了很多,虽然看起来好像用了4个循环,不过都是相加的关系,时间复杂度上并没有数量级上的差别,且问题显然分析起来简单了很多。
class Solution {
public:
vector> generateMatrix(int n) {
vector> res(n, vector(n, 0));
int loop = n / 2; // 设置循环圈数
int mid = n / 2; // 数组的中间位置
int start = 0; // 循环的起始处
int count = 1; // 从1到n2的元素
int offset = 1; //用于控制边界
while(loop--){
int i, j;
for(j = start; j < n - offset; ++j) res[start][j] = count++;
for(i = start; i < n - offset; ++i) res[i][j] = count++;
for(; j > start; --j) res[i][j] = count++;
for(; i > start; --i) res[i][j] = count++;
++start;
++offset;
}
if(n%2) res[mid][mid] = n * n;
return res;
}
};
这一题首先学会了vector的相关语法;
另外,其实思路是没有问题的,只是弄巧成拙,选择了一条以为简省实则复杂的路径,浪费了不少时间。现在明白了,在时间复杂度差不多的时候,先选择思路更简洁明了的方法将题目解出来,甚至是没思路时,先想暴力解法,再想可能的相关算法优化复杂程度。