代码随想录感悟(3)
有序数组的平方
977. 有序数组的平方
自己思路
非常简单直接的暴力排序——先将该有序数组每个项平方,然后再排序;
这里的新数组排序非常关键,先开始我没有想到会有负数,平方后的大小就会发生变化,因此后续的排序满足非递减顺序:
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+logn)
题解——双指针法
才发现双指针的运用是非常广泛,且可以有效的降低时间复杂度——O(n)
思路:在该有序数组中,数组平方的最大项也一定在原数组的最左边或最右边,
满足双指针——i 指向起始位置,j 指向终止位置
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
如果
A[i] * A[i] < A[j] * A[j]
那么result[k--] = A[j] * A[j];
。如果
A[i] * A[i] >= A[j] * A[j]
那么result[k--] = A[i] * A[i];
代码:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int k = nums.size() - 1;
vector<int> result(nums.size(), 0);
for (int i = 0, j = nums.size() - 1; i <= j;) {
if (nums[i] * nums[i] < nums[j] * nums[j]) {
result[k--] = nums[j] * nums[j];
j--;
}
else {
result[k--] = nums[i] * nums[i];
i++;
}
}
return result;
}
};
此时将该数组一分为二,时间复杂度确实减少成了O(n)。
由此可见双指针的用处很广泛——从数组的查找到数组的排序。
209. 长度最小的子数组
自己思路
当看到这道题,首先想到可以双层for循环的架构,第一层是对数组的每一位作为起点的遍历,第二层for是确定多长的数组子序列可以满足该题条件,
但在实现的时候遇到了一个问题——怎样对第一层循环得出满足大于等于条件的子序列进行比较、每一个新的子序列怎么承装。
第一个问题——通过
result = result < subLength ? result : subLength;
一个if判断
第二个问题——直接给出一个result数组(表示最大值)
可是这样显然易见:
时间复杂度O(n^2)
空间复杂度O(1)
代码如下:
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX; // 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;
}
};
滑动窗口
这是官方题解,在我的理解中,大体含义与暴力解法相似——不断调节子序列的起始项和终止项,直到得到答案为止
但区别是他可以随次改变起始项i使得时间复杂度降至O(n)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口起始位置
int subLength = 0; // 滑动窗口的长度
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};
在给出的这段代码中,
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
这一段表述了如何消减的时间复杂度,因为移动一格也只需要减去对应i的值就是后续窗口的sum了,所以一行代码解决了移动窗口。
在窗口移动中的判断既是逐个向后推进初始位置也是省略了每次都需要对数列遍历的暴力解法的高时间复杂度。