数组:leetcode 977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II

leetcode 977.有序数组的平方

leetcode 209.长度最小的子数组

leetcode 59.螺旋矩阵II

代码随想录算法公开课

leetcode 977.有序数组的平方

代码实现

暴力排序

class Solution {
public:
    vector sortedSquares(vector& nums) {
        for(int i = 0; i < nums.size(); i++){
            nums[i] *= nums[i];
        }
        sort(nums.begin(), nums.end()); // sort快排
        return nums;
    }
};
  • 时间复杂度O(n*logn) ---最优情况

  • 空间复杂度O(n) ---最差情况

细节处理

为什么选择使用sort()?

我们经常会遇到各种排序问题,如果不使用一些排序方法时我们就需要手撕排序代码,这样会浪费大量的时间,同时我们还要根据需要去选择相应的排序方法,不懂排序算法的基本原理可以看这里。

目前主要的排序方法有:冒泡排序、插入排序、选择排序、快速排序、希尔排序、桶排序、归并排序、堆排序等。

sort()是C++里对数组的元素进行排序的函数,该函数在C++中包含于algorithm库中。如果我们使用sort()方法就可以只需要一条语句就可以实现排序,这样可以节省时间。sort()函数是类似于快速排序的方法,时间复杂度为n*log2(n),执行效率较高,C++ sort()函数详解可以看这里。

快速排序(Quicksort)是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

其实STL中的sort()并非只是普通的快速排序,不仅对普通的快排进行优化,还结合了插入排序和堆排序。根据不同的数量级别以及不同情况,能自动选用合适的排序方法。

快速排序的时间复杂度和空间复杂度问题

每种排序方法都会有最优的时间复杂度以及最差的时间复杂度。当快速排序每次都取出最大或者最小的那个元素时,此时快速排序就和冒泡排序等同,得到最差的时间复杂度O(n^2)、空间复杂度O(n)。

在最优情况下,即每一次取到的元素都刚好平分整个数组时,此时时间复杂度为:

T(n) = T(n/2) + f(n)

其中T(n)为有n个元素时的时间复杂度,T(n/2)为由n/2个元素时的时间复杂度,f(n)为平分n个元素时所花的时间。

下面用迭代法计算最优情况下快速排序的时间复杂度

// 迭代法
T[n] = 2*T[n/2] + n  // 第一次递归
T[n/2] = 2*T[n/2^2] + n/2
T[n] = 2*(2*T[n/2^2] + n/2) + n
     = 2^2*T[n/2^2] + 2n // 第二次递归
// 同理得第m次递归
T[n] = 2^m*T[n/2^m] + m*n // 第m次递归

当公式最后得到T[1]时,就说明迭代完成,此时有:

T[n/2^m] = T[1]

则:m = log2(n),由此得:

T[n] = 2^m*T[n/2^m] + m*n = n + n*log2(n)

故得到最优情况下快速排序的时间复杂度O(nlogn)。同理易得最优情况下快速排序的空间复杂度O(logn)。

各排序算法的时间空间复杂度详解看这里

双指针法

class Solution {
public:
    vector sortedSquares(vector& nums) {
        int k = nums.size() - 1;
        vector 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)

  • 空间复杂度O(n)

细节处理

为什么想到用双指针法?

在数组上,对于使用两个for语句循环的嵌套或者其他时间复杂度较高的算法时,可以考虑双指针思路来减少时间复杂度,提高算法性能。以上sort()函数的时间复杂度O(nlogn),使用双指针法可以将时间复杂度降到O(n)。

由于初始数组A是从小到大排列且可能包含负数,所以平方后的数组的最大值一定出现在原数组的第一位和最后一位,由此可以在原数组的第一位和最后一位分别放一个指针i和j,进而比较两者平方的大小,再对指针进行前移或者后移操作,并把平方结果由后往前放在result数组中。

leetcode 209.长度最小的子数组

代码实现

暴力解法

class Solution {
public:
    int minSubArrayLen(int target, vector& nums) {
        int result = 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 >= target){
                    subLength = j - i + 1;
                    result = result < subLength ? result : subLength;
                    break; 
                }
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};
  • 时间复杂度O(n^2)

  • 空间复杂度O(1)

滑动窗口法

class Solution {
public:
    int minSubArrayLen(int target, vector& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑窗内元素之和的大小
        int subLength = 0; // 滑窗大小
        int i = 0; // 滑窗起始位置
        for(int j = 0; j < nums.size(); j++){ // 滑窗的尾部
            sum += nums[j];
            while(sum >= target){
                subLength = j - i + 1;
                result = result < subLength ? result : subLength;
                sum -= nums[i++];
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};
  • 时间复杂度O(n)

  • 空间复杂度O(1)

细节处理

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。滑窗其实是双指针法的一种。

在暴力解法中用了两个for循环完成题目,那么思考如何只使用一个for循环?在一个for循环中,应该表示的是滑动窗口的终止位置,否则表示滑窗的起始位置的话就与暴力解法遍历无异。

滑动窗口中的元素表示其和大于等于target的连续元素,其关键在于不断扩大滑窗的终止位置,找寻符合要求的子数组并记录其长度,再在已有的子数组上固定终止位置,不断右移起始位置。可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

并不是for里放一个while时间复杂度就是O(n^2),主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

leetcode 59.螺旋矩阵II

代码实现

class Solution {
public:
    vector> generateMatrix(int n) {
        vector> result(n, vector(n, 0));
        int startx, starty = 0; // 每圈的起始位置
        int loop = n / 2; // 每个圈循环几次
        int offset = 1; // 控制循环不变量为区间左闭右开,右侧需要收缩一位offset
        int count = 1; // 元素的值
        int mid = n / 2; // 矩阵中间的位置,用于给n为奇数的情况额外赋值
        while(loop--){
            int i;
            int j;
            for(j = starty; j < n - offset; j++){
                result[startx][j] = count++;
            }
            for(i = startx; i < n - offset; i++){
                result[i][j] = count++;
            }
            for(j = n - offset; j > starty; j--){
                result[i][j] = count++;
            }
            for(i = n - offset; i > startx; i--){
                result[i][j] = count++;
            }
            startx++;
            starty++;
            offset++;
        }
        if(n % 2 != 0){
            result[mid][mid] = count++;
        }
        return result;
    }
};
  • 时间复杂度O(n^2)

  • 空间复杂度O(n^2)

细节处理

一定要坚持循环不变量的原则,螺旋矩阵一圈下来要画四条边,这里将每条边区间左闭右开作为循环不变量。除此之外还要考虑到n为奇数时矩阵中心会剩下一个元素的情况。

你可能感兴趣的:(leetcode,矩阵,c++,排序算法,数据结构)