目录
一、977.有序数组的平方
题目链接:977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
已按 非递减顺序 排序
第一眼看到题目的思路:暴力,是肯定可以解决的,每个数平方之后,排个序。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++) {
nums[i] = nums[i] * nums[i];
}
sort(nums.begin(), nums.end()); //快排
return nums;
}
};
这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)。
双指针法
仔细一想,昨天写过的双指针法也是可以解决的。
数组是有序的,只不过因为有负数的存在,平方之后最小的数可能变为最大的数,但最大的数只可能出现在数组两端,不可能出现在数组中间。
所以用leftIndex指向数组左端,rightIndex指向数组右端,然后定义一个与原数组相同长度的数组ans,让flag指向ans数组终止位置。
如果 nums[leftIndex] * nums[leftIndex] > nums[rightIndex] * nums[rightIndex] 那么 ans[flag--] = nums[leftIndex] * nums[leftIndex]; leftIndex++;
如果 nums[leftIndex] * nums[leftIndex] <= nums[rightIndex] * nums[rightIndex] 那么 ans[flag--] = nums[rightIndex] * nums[rightIndex]; rightIndex++;
代码如下:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int size = nums.size();
vector<int> ans(size);
int flag = size - 1;
int leftIndex = 0, rightIndex = size -1;
while (leftIndex <= rightIndex) {
if (nums[leftIndex] * nums[leftIndex] > nums[rightIndex] * nums[rightIndex]) {
ans[flag--] = nums[leftIndex] * nums[leftIndex];
leftIndex++;
} else {
ans[flag--] = nums[rightIndex] * nums[rightIndex];
rightIndex--;
}
}
return ans;
}
};
此时的时间复杂度为O(n),相对于暴力排序的解法O(n + nlog n)还是提升不少的。
由于最开始没考虑将平方后的数逆序存储在ans中,导致得到的ans数组需要反转操作,所以去查了一下reverse函数的用法。
C++ reverse函数的用法:
reverse函数功能是逆序(或反转),多用于字符串、数组、容器。reverse函数用于反转在[first,last)范围内的顺序(包括first指向的元素,不包括last指向的元素),reverse函数无返回值
eg.
string str = "hello world , hi";
reverse(str.begin(), str.end()); //str结果为 ih , dlrow olleh
vector v = {5,4,3,2,1};
reverse(v.begin(), v.end());//容器v的值变为1,2,3,4,5
二、209.长度最小的子数组
题目链接:209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
第一眼看到题目的思路:用昨天双指针法的思路可以解决。定义fastIndex和slowIndex两个指针,子数组的和sum = 0。
当sum < target时,sum += nums[fastIndex++];
当sum >= target时,sum -= nums[leftIndex++]; 同时更新子数组的长度。这一步需要一直进行到sum < target为止,所以sum >= target要用while循环。
代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int size = nums.size();
int slowIndex = 0;
int num = size;
int sum = 0, ans = size + 1;
for (int fastIndex = 0; fastIndex < size; fastIndex++) {
sum += nums[fastIndex];
while (sum >= target) {
if ((fastIndex - slowIndex + 1) < ans) {
ans = fastIndex - slowIndex + 1;
}
sum -= nums[slowIndex++];
}
}
if (ans == size + 1) {
return 0;
} else {
return ans;
}
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
if ((fastIndex - slowIndex + 1) < ans) {
ans = fastIndex - slowIndex + 1;
}
sum -= nums[slowIndex++];
if (ans == size + 1) {
return 0;
} else {
return ans;
}
上面这两段代码可以用三目运算符简化,当时没考虑这么多,以后需要注意。
相关题目推荐
三、59.螺旋矩阵II
题目链接:59. 螺旋矩阵 II
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:
输入:n = 3 输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1 输出:[[1]]
提示:
1 <= n <= 20
这题没有简单方法,只能模拟。
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去。
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
这也是坚持了每条边左闭右开的原则,坚持循环不变量原则。
代码如下:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
int start_x = 0, start_y = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 1; // 用来给矩阵中每一个空格赋值
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i, j;
while(loop--) {
i = start_x;
j = start_y;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = start_y; j < n - offset; j++) {
res[start_x][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = start_x; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > start_y; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > start_x; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
start_x++;
start_y++;
// offset 控制每一圈里每一条边遍历的长度
offset++;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};
相关题目推荐
四、总结
1. 看到前两道题目的时候,能想到用双指针法去做,但掌握的还不够熟练,需要多刷题强化训练。(滑动窗口)
2. 循环不变量原则的重要性,在明确这一原则后,题目的思路就很清晰了。