代码随想录算法训练营第二天 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵

代码随想录算法训练营第二天| 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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值