算法day2 | LeetCode 977. 有序数组的平方,LeetCode 209. 长度最小的子数组,LeetCode 59. 螺旋矩阵||

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;
    }
};

总结

本题主要就是模拟整个过程,每条边界都采用相同的原则,要不自己就先乱掉了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值