Leetcode一刷-数组篇【补卡】-Day2/60

207 阅读5分钟

数组基础回顾

  1. 数组是内存空间连续的一组相同类型数据的集合,因此不能直接进行删改,只能进行覆盖操作。
  2. 在做数组题目的时候,注意变量循环定义不变量原则,在变量范围处理时,需要关注是否满足[left,right][left,right)的初始定义。

今日遇到的一些做题技巧总结

  1. 除了「移除元素」中涉及的快慢指针外,对于更新值总在左右两侧的问题,可以考虑使用双指针的方法,ij分别指向数组首尾,如 977.有序数组的平方;
  2. 对于仅在原始数组之上计算某类子串长度问题,则可以考虑使用滑动窗口,滑动窗口变化的触发条件在于是否满足指定值target,通常是for内嵌套while的语句,如 209. 长度最小的子数组;
  3. 关于规律性矩阵类问题,需要遵循变量循环定义不变量原则,模拟矩阵生成过程,如 59. 螺旋矩阵II。
  4. c++中?条件运算符的使用:Exp1 ? Exp2 : Exp3; 5.对于一个不一定存在的解,可以用一个同类型的占位符,最后如果存在解则返回解,否则返回0,如无限大INT32_MAX

参考www.runoob.com/cplusplus/c…

数组问题总结

  1. 在c++中需要注意array和vector的区别,vector的底层是array,vector的本质其实是容器
  2. 数组作为面试中必然会出现的一道题,常见的有四个高效的解法
    • 二分法:在循环中坚持对区间的定义,需要会手撕。
    • 双指针法(快慢指针法):通过一个快指针和一个慢指针在一个for循环下完成两个for循环的工作。
      • 数组中的元素为什么不能删除的原因:
        • 数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束后,回收内存栈空间)
        • C++中的vector和array区别一定要弄清楚,vector的底层实现是array,封装后使用更友好。
      • 快慢指针法在数组和链表操作中非常常见。
    • 滑动窗口:需要理解究竟是怎么移动窗口起始位置的。
    • 模拟行为:其实真正解决题目的代码都是简洁的,或者有原则的。

Leetcode相关题目及解法要义

此处埋下一个坑,之后补:需要补一下时间复杂度和空间复杂度分析的知识点。

977. 有序数组的平方

题目链接:leetcode.cn/problems/sq…

先上一个c++的暴力解法:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        for (int i = 0; i < A.size(); i++) {
            A[i] *= A[i];
        }
        sort(A.begin(), A.end()); // 快速排序
        return A;
    }
};

考虑到本就有序的数组,其平方最大值一定在首尾处,而不是在中间位置,所以采用双指针的思想,c++代码如下:

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]; //c++中的 k--,是先赋值之后再减,类似于=k,k--
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }
};

python实现的代码如下:

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        i,j,k = 0,n - 1,n - 1
        ans = [-1] * n
        while i <= j:
            lm = nums[i] ** 2
            rm = nums[j] ** 2
            if lm > rm:
                ans[k] = lm
                i += 1
            else:
                ans[k] = rm
                j -= 1
            k -= 1
        return ans

209. 长度最小的子数组

题目链接:leetcode.cn/problems/mi…

先上一个c++的暴力解法:

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& 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 >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

寻找子串问题的滑动窗口,关键在于如何动态的调整子串的起始和终止位置,采用for+while的嵌套写法,for控制窗口的尾部;while只负责控制temp_sum-头部边界元素,实现窗口的滑动。

class Solution {
public:
    int minSubArrayLen(int s, 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,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

python实现的代码如下:

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        # 定义一个无限大的数
        res = float("inf")
        Sum = 0
        index = 0
        for i in range(len(nums)):
            Sum += nums[i]
            while Sum >= s:
                res = min(res, i-index+1)
                Sum -= nums[index]
                index += 1
        return 0 if res==float("inf") else res

59. 螺旋矩阵II

题目链接:leetcode.cn/problems/sp…

此题类似于数学归纳法问题,代码中有很多细节。 解题思路是模拟顺时针画矩阵的过程,相当于循环中嵌套四个循环,所以一定要遵循变量循环定义不变量原则:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

一些关键的变量:每次起始x,起始y

c++代码实现:

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),注意索引是从0开始的
        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 += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

python代码实现:

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        nums = [[0] * n for _ in range(n)]
        startx, starty = 0, 0               # 起始点
        loop, mid = n // 2, n // 2          # 迭代次数、n为奇数时,矩阵的中心点
        count = 1                           # 计数

        for offset in range(1, loop + 1) :      # 每循环一层偏移量加1,偏移量从1开始
            for i in range(starty, n - offset) :    # 从左至右,左闭右开
                nums[startx][i] = count
                count += 1
            for i in range(startx, n - offset) :    # 从上至下
                nums[i][n - offset] = count
                count += 1
            for i in range(n - offset, starty, -1) : # 从右至左
                nums[n - offset][i] = count
                count += 1
            for i in range(n - offset, startx, -1) : # 从下至上
                nums[i][starty] = count
                count += 1                
            startx += 1         # 更新起始点
            starty += 1

        if n % 2 != 0 :			# n为奇数时,填充中心点
            nums[mid][mid] = count 
        return nums