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

977.有序数组的平方

题目链接:977.有序数组的平方

思路

1.暴力思路

拿到题目最直接的思路就是暴力方法,首先将数组元素进行平方,然后再进行排序。当使用O(n^{2})的排序算法时,直接超时了;使用O(nlogn)的排序算法时,能通过,但是不满足进阶要求:设计时间复杂度为O(n)的算法解决问题。

2.双指针思路

题目建议用双指针的思路进行解答,于是我首先试着用双指针的思路进行分析。昨天见到了两种简单的双指针算法(快慢双指针、左右双指针),于是我首先想向这两种算法靠拢。

已知nums已经按非递减顺序排列完毕,那么说明其已经是有序数组,分析 0 元素,那么,0 之后的平方数就是非递减顺序,而 0 之前的平方数则是递减顺序,只需要将 0 之前的数依次按大小插入 0 之后的数组中就能完成任务。

我看到题目中并没有要求空间复杂度,我想可以单独创建一个数组,直接用来记录排序后的非递减数组,设双指针从两头开始为非递减数组大端向中间遍历,这样问题就很简单了。

代码实现

1.暴力思路

(1)冒泡排序

时间复杂度过高,会超出时间限制

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int size = nums.size();
        for(int i = 0; i < size; ++i){
            nums[i] = nums[i] * nums[i];
        }
        
        for(int i = 0; i < size; ++i){
            for(int j = size - 1; j > i; --j){
                if(nums[j] < nums[j-1])
                    swap(nums[j], nums[j-1]);
            }
        }
        return nums;
    }
};
(2)快速排序
class Solution {
public:
    void quick_sort(vector<int>& q, int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[(l + r >> 1)];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

    vector<int> sortedSquares(vector<int>& nums) {
        int size = nums.size();
        for(int i = 0; i < size; ++i){
            nums[i] = nums[i] * nums[i];
        }
        
        quick_sort(nums, 0, size-1);
        
        return nums;
    }
};

2.双指针思路

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> res;
        int size = nums.size();
        res.resize(size);

        for(int i = 0; i < size; ++i)   nums[i] = nums[i] * nums[i];

        int i = 0, j = size - 1;
        int index = size - 1;
        while(i <= j){
            res[index--] = (nums[i] > nums[j] ? nums[i++] : nums[j--]);
        }
        return res;
    }
};

上面我自己写的版本,但是看了解析以后发现,这样的时间复杂度是O(2n),如果能在一个循环里完成好像时间复杂度会减少,所以还是尽量在一个时间复杂度里完成,如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        int k = A.size() - 1;
        vector<int> result(A.size(), 0);
        for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
            if (A[i] * A[i] < A[j] * A[j])  {
                result[k--] = A[j] * A[j];
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }
};

总结

  本题不难,能找到潜在规律,但是一开始走错方向了,双指针从两边到中间是正解,而我一开始想的是先找其中 0 元素,再从 0 到两边,这样还得讨论是否有 0、是否全正或全负、0 是否在两段等特殊情况,这就把问题想复杂了。有时候做算法就是这么神奇,明明已经找到了正确的规律,思路却偏偏会跑偏。

209.长度最小的子数组

题目链接:209. 长度最小的子数组

思路

这题的建议是用滑动窗口的思想,之前好像学习过,于是先理一下自己的思路:

1.滑动窗口,首先设立左右窗口边界,初始窗口大小为 0,左右边界在数组最左边;

2.若当前窗口内数的总和小于target,则右边界向右扩张,直至大于等于target;若当前窗口内数的总和大于等于target,则左窗口向右进行收缩,直至等于target,每次收缩前将当前窗口大小与最小窗口大小比较,实时更新窗口最小值。

3.当右窗口到最右端,做窗口不再收缩时,查看窗口最小值,即为所求结果。

代码实现

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int size = nums.size();

        int left = 0, right = 0;   // 设窗口边界,采取左闭右开原则
        int sum = 0;  // 记录窗口内数的综合
        int minmum = size; // 窗口大小最小值
        while(1){
            while(sum >= target){   // 数和足够,左边界向右收缩
                int len = right - left;
                //minmum = (minmum < len ? minmum : len);
                minmum = min(minmum, len);
                sum -= nums[left++];
            }
            if(sum < target){  // 数和不足,向右扩张
                if(right < size)    sum += nums[right++];
                else{
                    if(left == 0)   return 0;
                    else    break;
                }
            }
        }
        return minmum;
    }
};

看了下代码随想录的解析,基本思想是大致相同的,我就不贴了。

总结

  这一题是看到了建议用滑动窗口,所以就直奔这个思路去了,因为之前学过,所以很轻松就能想到解法,不知道如果没有看到建议,能不能想到这个滑动窗口的方法,这还需在后面多做练习积累经验。

59.螺旋矩阵II 

题目链接:59.螺旋矩阵II

思路

这一题拿到我什么建议也没有看。读过题后的思路是设置边界,矩阵元素的遍历顺序很明确,先向右——向右不通就向下——向下不通就向左——向左不通就向上——向上不通再向右,如此反复,我想只要确定好边界,使其向一个方向遍历到边界不通,自动转向,最终实现全部遍历。

问题遇到的第一个难点是二维vector容器的使用语法,初始化、引用等。引用同数组,初始化可如下方式:

vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组

但是在算法实现过程中,由于我想着四个方向的趋势一样,且大小有关,想放在一个循环里实现,就多了很多复杂的情况,我修改多次始终有误,遂放弃,查看解析发现原来是用四个循环单独考虑,则情况就简单了很多,虽然看起来好像用了4个循环,不过都是相加的关系,时间复杂度上并没有数量级上的差别,且问题显然分析起来简单了很多。

代码实现

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        int loop = n / 2;   // 设置循环圈数
        int mid = n / 2;    // 数组的中间位置
        int start = 0;  // 循环的起始处
        int count = 1;  // 从1到n2的元素
        int offset = 1; //用于控制边界
        while(loop--){
            int i, j;
            for(j = start; j < n - offset; ++j)  res[start][j] = count++;
            for(i = start; i < n - offset; ++i)  res[i][j] = count++;
            for(; j > start; --j)  res[i][j] = count++;
            for(; i > start; --i)  res[i][j] = count++;
            ++start;
            ++offset;
        }
        if(n%2) res[mid][mid] = n * n;

        return res;
    }
};

总结

这一题首先学会了vector的相关语法;

另外,其实思路是没有问题的,只是弄巧成拙,选择了一条以为简省实则复杂的路径,浪费了不少时间。现在明白了,在时间复杂度差不多的时候,先选择思路更简洁明了的方法将题目解出来,甚至是没思路时,先想暴力解法,再想可能的相关算法优化复杂程度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值