LeetCode 977. 有序数组的平方
题目链接:力扣977. 有序数组的平方
题目思路
这道题要求将一个非递减排序的数组平方后仍然保持非递减顺序,由于数组中可能包含负数,原地平方后可能会导致数组中元素顺序不是非递减顺序。可以首先将数组中的每个元素平方,然后将数组进行排序;第二种方法是利用双指针,从左右两边向内部走,左右两边的元素平方后肯定比较大,然后比较当中最大的,从右向左放入数组中。
解题方法
方法一:(暴力)
将数组中的每个元素平方,然后将数组进行排序,这个时间复杂度是 O(n + nlogn), 可以说是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 指向终止位置。
定义一个新数组 res,和 nums 数组一样的大小,让 k 指向 res 数组终止位置。
如果 nums[i] * nums[i] < nums[j] * nums[j] 那么 res[k–] = nums[j] * nums[j]; 。
如果 nums[i] * nums[i] >= nums[j] * nums[j] 那么 res[k–] = nums[i] * nums[i]; 。
此时的时间复杂度为 O(n)。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size(), 0);
if(nums.size() == 0) return res;
int k = nums.size() - 1;
for(int i = 0, j = k; i <= j;){
if(nums[i] * nums[i] < nums[j] * nums[j]){
res[k--] = nums[j] * nums[j];
j--;
}
else{
res[k--] = nums[i] * nums[i];
i++;
}
}
return res;
}
};
总结
感觉双指针的思路一下子就有眼前一亮的感觉,我最开始只想到了平方后进行排序的方法,双指针的这种做法节省了不少了时间。
LeetCode 209. 长度最小的子数组
题目链接:力扣209. 长度最小的子数组
题目思路
可以采用暴力解法和滑动窗口。
解题方法
方法一:(暴力)
采用两个 for 循环,不断寻找符合条件的子序列。
时间复杂度是 O(n^2)。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int res = INT32_MAX; // 用于存放最终的结果
int sum = 0; // 子序列的数值之和
int subLen = 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 >= target){ // 一旦发现了子序列的和超过了 target,更新 res
subLen = j - i + 1; // 取子序列的长度
res = res < subLen ? res : subLen;
break; // 因为找符合条件最短的子序列,所以一旦符合条件就 break
}
}
}
// 如果 res 没有被赋值的话,就返回 0,说明没有符合条件的子序列
return res == INT32_MAX ? 0 : res;
}
};
力扣增加测试用例超出时间限制了!!!
方法二:(滑动窗口)
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ target 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于 target 了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。
时间复杂度是 O(n)。
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int res = INT32_MAX; // 用于存放最终结果
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口的起始位置
int subLen = 0; // 滑动窗口的长度
for(int j = 0; j < nums.size(); ++j){
sum += nums[j];
// 使用 while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while(sum >= target){
subLen = j - i + 1; // 取子序列的长度
res = res < subLen ? res : subLen;
sum -= nums[i++]; // 滑动窗口的精髓,不断变更 i(子序列的起始位置)
}
}
// 如果 res 没有被赋值的话,就返回 0,说明没有符合条件的子序列
return res == INT32_MAX ? 0 : res;
}
};
为什么时间复杂度是O(n)。
不要以为 for 里放一个 while 就以为是 O(n^2) 啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是 O(n)。
总结
滑动窗口的精髓就在于它不断地移动区间的起始位置和终止位置,最终确定一个子序列,就好像是一个窗口在滑动一样。
LeetCode 59. 螺旋矩阵||
题目链接:力扣59. 螺旋矩阵||
题目思路
模拟整个过程,坚持循环不变量原则,每一条边都采用相同的原则来进行模拟。
解题方法
方法一:(模拟)
求解本题要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用 vector 定义一个二维数组
int startx = 0, starty = 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 = startx;
j = starty;
// 下面开始的四个 for 就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for(j = starty; j < n - offset; ++j){
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for(i = startx; i < n - offset; ++i){
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for(; j > starty; --j){
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for(; i > startx; --i){
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加 1,例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset++;
}
// 如果 n 为奇数的话,需要单独给矩阵最中间的位置赋值
if(n % 2) res[mid][mid] = count;
return res;
}
};
方法二:(维护边界指针)
定义四个边界指针(也称数组下标索引),在遍历过程中不断维护边界指针,直到上下左右都已经遍历过了;
- t 为上边界指针,初始时上边界为 0;
- b 为下边界指针,初始时下边界为 n - 1;
- l 为左边界指针,初始时左边界为 0;
- r 为右边界指针,初始时右边界为 n - 1;
k 为计数器,每走一步 +1;
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int t = 0; // top
int b = n - 1; // bottom
int l = 0; // left
int r = n - 1; //right
vector<vector<int>> ans(n, vector<int>(n));
int k = 1;
while(k <= n * n){
for(int i = l; i <= r; ++i, ++k) ans[t][i] = k;
++t;
for(int i = t; i <= b; ++i, ++k) ans[i][r] = k;
--r;
for(int i = r; i >= l; --i, ++k) ans[b][i] = k;
--b;
for(int i = b; i >= t; --i, ++k) ans[i][l] = k;
++l;
}
return ans;
}
};
总结
本题主要就是模拟整个过程,每条边界都采用相同的原则,要不自己就先乱掉了。