代码随想录算法训练营第二天| 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵
977.有序数组的平方
题目链接:977.有序数组的平方
暴力方法
直接将所有元素平方然后再排序,时间复杂度O(nlogn) (取决于快排)。
需要用到库函数sort()。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for_each(nums.begin(), nums.end(), [](int& elem){elem *= elem;});
sort(nums.begin(), nums.end()); //快速排序
return nums;
}
};
双指针法
刚开始看到题目的时候,想到将0<和>=0的部分分开表示。但是如果要再申请一个数组的话,需要额外的空间。因为数组是非递减的,想到用两个指针标记正负数的范围,从中间开始,找到正负分界线,向两边扩展。因为有很多边界判断,搞了一个很麻烦的方法。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result(nums.size(), 0);
if(nums[0] >= 0){
for_each(nums.begin(), nums.end(), [](int& elem){elem *= elem;});
return nums;
} else if (nums[nums.size()-1] < 0){
for(int i = nums.size()-1, j = 0; i >= 0; i--, j++){
result[j] = (nums[i]*nums[i]);
}
} else {
int pos = 0;
for(int i = 0; i < nums.size() - 1; i++){
if (nums[i] < 0 && nums[i+1] >= 0){
pos = i+1;
}
}
// 正负分界线
int neg = pos -1;
int re = 0;
while(neg >= 0 && pos < nums.size()){
if(-nums[neg] > nums[pos]){
result[re++] = (nums[pos]*nums[pos]);
pos++;
} else {
result[re++] = (nums[neg]*nums[neg]);
neg--;
}
}
// 下面的两个判断只会命中一个
if (neg < 0){
for(int i = pos; i < nums.size(); i++){
result[re++] = (nums[i]*nums[i]);
}
}
if(pos >= nums.size()){
for(int i = neg; i >= 0; i--){
result[re++] = (nums[i]*nums[i]);
}
}
}
return result;
}
};
全负数或者全正数可以直接计算,但是正负数都有的情况下,相比卡哥给的方法,多了一个遍历找到正负分解的过程,增加了时间复杂度。还有就是边界判断太多了。
卡哥的方法,因为数组是非递减的,两头的数字平方后一定比中间的大,双指针分别从两头开始,平方大的填入新数组的最后。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int k = nums.size()-1;
vector<int> result(nums.size(),0);
int i = 0, j = nums.size()-1;
while(i<=j){
if(nums[i]*nums[i] > nums[j]*nums[j]){
result[k--] = nums[i]*nums[i];
i++;
} else {
result[k--] = nums[j]*nums[j];
j--;
}
}
return result;
}
};
209.长度最小的子数组
题目链接:209.长度最小的子数组
暴力解法:
时间复杂度:O(n2)
最直接的想法:2层for循环进行遍历:第一层for循环控制区间的起始位置,第二层for循环控制区间的大小,从当前位置扩展到数组末尾。
枚举所有的满足sum>=target的条件,找到最小的区间进行返回。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT32_MAX;
for(int i = 0; i < nums.size(); i++){
for(int size = 0; i + size < nums.size(); size++){
int sum = 0;
for(int j = i; j <= i+size; j++){
sum += nums[j];
}
if(sum >= target){
result = result > size ? size : result;
}
}
}
return result == INT32_MAX ? 0 : result+1;
}
};
直接超时了,可以看到LeetCode给了一个非常大的测试样例。
滑动窗口:
int minSubArrayLen(int target, 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(sum >= target){
subLength = j-i+1;
result = result > subLength ? subLength : result;
sum -= nums[i++];
}
}
return result != INT32_MAX ? result : 0;
}
卡哥这个图做的很精髓,非常生动的体现了窗口“滑动”的过程。
还有就是题目中的“最小”,个人理解这个是窗口可以滑动的关键。暴力方法相当于列出了所有满足的条件,然而题目只需要最小的区间即可。对于暴力方法来说,当区间和已经大于目标值的情况下,仍然需要对当前位置遍历后续的区间,其实没有必要,这个时候只要把起始位置向前移动,就是滑动窗口的思想了。
59.螺旋矩阵
题目链接:59.螺旋矩阵Ⅱ
经典一看就会一写就废的题目。看了下卡哥的方法,感觉非常注重数理关系的逻辑推理,导致代码相对比较难懂。查阅了一下经典的回形打印数组的解法,虽然边界条件多,但是代码更好理解,更加符合一般人去填入的过程。
以下内容摘抄自《程序员代码面试指南》
在矩阵中用左上角的坐标(tR, tC)和右下角的坐标(dR, dC)就可以表示一个子矩阵。
这本书里给出的思路也是按圈打印矩阵,但是用的是矩阵的四边,而不是已填入的行列和起始位置来约束。
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> result(n, vector<int>(n, 0));
int tR = 0, tC = 0;
int dR = n-1, dC = n-1; // 矩阵的边界是n-1
int count = 1;
while(tR < dR && tC < dC){
int curC = tC, curR = tR;
while(curC < dC){
result[tR][curC++] = count++;
}
while(curR < dR){
result[curR++][dC] = count++;
}
while(curC > tC){
result[dR][curC--] = count++;
}
while(curR > tR){
result[curR--][tC] = count++;
}
tR++, tC++, dR--, dC--;
}
// 上面的过程没有判断tR == dR && tC == dC,也就是奇数边矩阵中心点的数值,所以要在后面加上
if (n % 2) {
result[n / 2][n / 2] = count;
}
return result;
}
};
下面是卡哥的解法。卡哥的方法保持了边界的一致性,也就是始终用左闭右开的方式处理每一条边界。同时,还利用了每转一圈,各边占用2,总共转n/2圈的这个性质。
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> result(n, vector<int>(n, 0));
int startx = 0, starty = 0;
int loop = n / 2;
int count = 1;
int offset = 1;
int i = 0, j = 0;
while(loop--){
i = starty;
j = startx;
for(j = startx; j < n - offset; j++){
result[i][j] = count++;
}
for(i = starty; i < n - offset; i++){
result[i][j] = count++;
}
for(j = n - offset; j > startx; j--){
result[i][j] = count++;
}
for(i = n - offset; i > starty; i--){
result[i][j] = count++;
}
startx++, starty++, offset++;
}
if(n%2){
result[n/2][n/2] = count;
}
return result;
}
};