数组

数组是将 相同类型 的元素存储于 连续内存空间 的数据结构,其长度不可变;

访问/修改-索引 O(1)

插入元素 O(n)

删除元素 O(n)

合并数组 O(m+n)

数组的特殊性

  1. JS 数组可以动态的改变容量,根据元素的数量来扩容、收缩;

  2. JS 数组可以表现的像栈一样,为数组提供了 pushpop 方法;也可以表现的像队列一样,使用 shiftpush 方法,可以像使用队列一样使用数组;

  3. JS 数组可以使用 for-each 遍历,可以排序,可以倒置;

  4. JS 提供了很多操作数组的方法,比如 Array.concatArray.joinArray.slice

快慢数组

  1. 快数组是一种 线性 的存储方式,新创建的空数组,默认的存储方式是快数组,快数组长度是可变的,可以根据元素的增加和删除来动态调整存储空间大小,内部是通过扩容和收缩机制实现;

    1. 扩容后的新容量 = 旧容量的 1.5 倍 + 16
    2. 需要根据 length + 1 == old_length 进行判断,true 则将空出的空间收缩二分之一,false 则全部收缩掉;
  2. 慢数组是一种 字典 的内存形式,不用开辟大块连续的存储空间,节省了内存,但是由于需要维护这样一个 HashTable 其效率会比快数组低;

前端实战:如何避免触发慢数组(性能优化)

  1. 避免创建稀疏数组:

    // ❌ 错误:创建稀疏数组,触发慢数组
    const badArr = [];
    badArr[1000] = 'hello';
    
    // ✅ 正确:如果需要固定长度数组,先填充默认值(保持密集)
    const goodArr = new Array(1001).fill(undefined);
    goodArr[1000] = 'hello'; // 仍为快数组
    
  2. 避免用 delete 删除数组元素:

    const arr = [1,2,3,4];
    
    // ❌ 错误:delete 留下空洞,可能触发慢数组
    delete arr[1]; // arr 变为 [1,,3,4]
    
    // ✅ 正确:用 splice 彻底删除,保持密集
    arr.splice(1, 1); // arr 变为 [1,3,4](仍为快数组)
    
  3. 大数据数组初始化优化:

    // ❌ 错误:频繁 push 导致快数组多次扩容(虽不触发慢数组,但有性能损耗)
    const bigArr = [];
    for (let i = 0; i < 100000; i++) {
      bigArr.push(i);
    }
    
    // ✅ 正确:提前初始化长度,减少扩容次数
    const bigArr = new Array(100000);
    for (let i = 0; i < 100000; i++) {
      bigArr[i] = i;
    }
    

前端业务中的实际影响

  1. 普通业务场景:日常的列表数据 (长度 < 1000),快 / 慢数组的性能差异几乎感知不到,无需过度关注;

  2. 大数据场景:处理 10万+ 元素的数组 (如数据可视化、大数据导出),慢数组会导致遍历 / 访问性能下降 (约 20%-50%),需避免触发;

  3. 框架层面:React/Vue 等框架的列表渲染底层会规避稀疏数组 (比如 v-for 遍历前先过滤空洞),但自定义大数据处理时仍需注意;

二维矩阵与一维索引互转公式

  1. 行优先遍历 = “先走完一行的所有列,再走下一行”,就像我们读书时 “从左到右、从上到下” 的阅读顺序;

  2. 可视化示例:34 列矩阵 (行索引 i:0/1/2;列索引 j:0/1/2/3)

    行0:[ (0,0), (0,1), (0,2), (0,3) ] → 元素顺序:1, 2, 3, 4
    行1:[ (1,0), (1,1), (1,2), (1,3) ] → 元素顺序:5, 6, 7, 8
    行2:[ (2,0), (2,1), (2,2), (2,3) ] → 元素顺序:9, 10, 11, 12
    
  3. 行优先遍历的一维顺序:1→2→3→4→5→6→7→8→9→10→11→12

公式 1:二维索引 → 一维索引((i,j) → k)

  1. 公式:一维索引k = 行索引i × 矩阵列数n + 列索引j

  2. 核心逻辑,把二维矩阵想象成 “排队”:

    1. 行索引 i = 你前面有多少 “完整的队伍(行)”
    2. 列索引 j = 你在自己队伍里的位置
    3. 一维索引 k = 前面所有队伍的人数 + 你在自己队伍里的位置
  3. 可视化对应表:

    二维索引 (i,j) 元素 计算 (i×n+j) 一维索引 k
    (0,0) 1 0×4+0=0 0
    (0,1) 2 0×4+1=1 1
    (0,2) 3 0×4+2=2 2
    (0,3) 4 0×4+3=3 3
    (1,0) 5 1×4+0=4 4
    (1,1) 6 1×4+1=5 5
    (1,2) 7 1×4+2=6 6
    (1,3) 8 1×4+3=7 7
    (2,0) 9 2×4+0=8 8

公式 2:一维索引 → 二维索引(k → (i,j))

  1. 这是 公式 1 的逆运算,核心是 “除法求行、取模求列”

  2. 公式:

    1. 行索引 i = Math.floor(一维索引 k / 矩阵列数 n)
    2. 列索引 j = 一维索引 k % 矩阵列数 n
  3. 核心逻辑,把一维数组想象成 “拆分成若干行”:

    1. 除法 Math.floor(k/n) = 能拆出多少个 “完整的行” (即行索引)
    2. 取模 k % n = 拆分完完整行后,剩下的元素数 (即列索引)
  4. 可视化对应表 (反向推导)

    一维索引 k 元素 计算行 (i=Math.floor (k/n)) 计算列 (j=k% n) 二维索引 (i,j)
    6 7 Math.floor(6/4)=1 6%4=2 (1,2)
    7 8 Math.floor(7/4)=1 7%4=3 (1,3)
    8 9 Math.floor(8/4)=2 8%4=0 (2,0)
    9 10 Math.floor(9/4)=2 9%4=1 (2,1)

力扣题单

重塑矩阵

  1. 实现方式一:

    1. 核心思路:将二维矩阵 “扁平化” 为一维数组,再按新的行列数重新分割为二维数组;
    2. 步骤拆解:
      • 计算原矩阵的总行数 rows、总列数 cols,以及总元素数 total = rows * cols
      • 校验 total 是否等于 r*c:若不等,直接返回原矩阵;
      • 若相等,先将原矩阵扁平化为一维数组 (按行遍历)
      • rc 列的规则,将一维数组重新分割为二维数组;
    3. 复杂度:
      • 时间复杂度:O(rc)
      • 空间复杂度:O(mn),这里的空间复杂度不包含返回的重塑矩阵需要的空间;
    4. 实现代码:
    function matrixReshape(nums: number[][], r: number, c: number): number[][] {
        // 1. 获取原矩阵的行数和列数(处理空矩阵边界情况)
        const rows = nums.length;
        if (rows === 0) return nums; // 空矩阵直接返回
        const cols = nums[0].length;
        const total = rows * cols;
    
        // 2. 校验元素总数是否匹配,不匹配则返回原矩阵
        if (total !== r * c) {
            return nums;
        }
    
        // 3. 扁平化原矩阵为一维数组(行优先遍历)
        const flatNums: number[] = [];
        for (let i = 0; i < rows; i++) {
            for (let j = 0; j < cols; j++) {
                flatNums.push(nums[i][j]);
            }
        }
    
        // 4. 重塑为 r 行 c 列的新矩阵
        const result: number[][] = [];
        let index = 0; // 指向flatNums的指针
        for (let i = 0; i < r; i++) {
            const row: number[] = [];
            for (let j = 0; j < c; j++) {
                row.push(flatNums[index]);
                index++;
            }
            result.push(row);
        }
    
        return result;
    }
    
  2. 实现方式二:

    1. 公式 2:一维索引 → 二维索引(k → (i,j))
      • 行索引 i = Math.floor(一维索引 k / 矩阵列数 n)
      • 列索引 j = 一维索引 k % 矩阵列数 n
    2. 复杂度:
      • 时间复杂度:O(rc)
      • 空间复杂度:O(1)
    3. 实现代码:
    function matrixReshape(nums: number[][], r: number, c: number): number[][] {
        // 获取原矩阵行数,处理空矩阵边界
        const row = nums.length;
        if (row === 0) return nums;
        // 获取原矩阵列数(非空矩阵至少有一行)
        const col = nums[0].length;
    
        // 校验元素总数是否匹配,不匹配则返回原矩阵
        if (row * col !== r * c) return nums;
    
        // 初始化 r 行 c 列的结果矩阵,避免数组引用共享问题
        const ans: number[][] = new Array(r).fill(0).map(() => new Array(c).fill(0));
    
        // 遍历所有元素的一维索引,通过数学公式映射到原矩阵和新矩阵的二维位置
        for (let i = 0; i < row * col; ++i) {
            ans[Math.floor(i / c)][i % c] = nums[Math.floor(i / col)][i % col];
        }
    
        return ans;
    }
    

矩阵置零

  1. 实现方式一:暴力法 (额外空间记录零的行 / 列)

    1. 思路:
      • 遍历矩阵,用两个集合分别记录包含 0 的行号和列号,用 Set 避免重复记录同一行 / 列 (比如一行有多个 0 只需记录一次)
      • 再次遍历矩阵,若当前位置的行 / 列在记录集合中,则置为 0
      • 优点:逻辑简单易懂,适合新手;缺点:需要额外空间存储行 / 列信息;
    2. 复杂度:
      • 时间复杂度:O(mn) (两次遍历矩阵)
      • 空间复杂度:O(m+n) (最坏情况所有行 / 列都有 0)
    3. 实现代码:
    function setZeroes(matrix: number[][]): void {
        const rows = matrix.length;
        const cols = matrix[0].length;
        // 用集合存储需要置零的行和列索引
        const zeroRows = new Set<number>();
        const zeroCols = new Set<number>();
    
        // 第一步:遍历矩阵,记录所有 0 的行和列
        for (let i = 0; i < rows; i++) {
            for (let j = 0; j < cols; j++) {
                if (matrix[i][j] === 0) {
                    zeroRows.add(i);
                    zeroCols.add(j);
                }
            }
        }
    
        // 第二步:根据记录的行和列置零
        for (let i = 0; i < rows; i++) {
            for (let j = 0; j < cols; j++) {
                if (zeroRows.has(i) || zeroCols.has(j)) {
                    matrix[i][j] = 0;
                }
            }
        }
    }
    
  2. 实现方式二:原地优化 (利用矩阵首行 / 首列存储标记)

    1. 思路:
      • 利用矩阵首行和首列代替 【解法 1】 中的集合,存储需要置零的行 / 列标记;
      • 先单独记录首行 / 首列本身是否有 0 (避免覆盖原始 0 的信息)
      • 遍历矩阵 (从第二行第二列开始),若当前元素为 0,则将首行对应列、首列对应行置为 0
      • 根据首行 / 首列的标记,置零对应行 / 列;
      • 最后根据初始记录,决定是否置零首行 / 首列;
    2. 复杂度:
      • 时间复杂度:仍为 O(mn) (多次遍历但无嵌套层级增加)
      • 空间复杂度:降至 O(1) (仅用两个布尔变量),符合 “原地修改” 的最优要求;
    3. 实现代码:
    function setZeroes(matrix: number[][]): void {
        const rows = matrix.length;
        const cols = matrix[0].length;
        // 标记首行/首列是否本身有0
        let firstRowHasZero = false;
        let firstColHasZero = false;
    
        // 第一步:检查首行是否有0
        for (let j = 0; j < cols; j++) {
            if (matrix[0][j] === 0) {
                firstRowHasZero = true;
                break;
            }
        }
    
        // 第二步:检查首列是否有0
        for (let i = 0; i < rows; i++) {
            if (matrix[i][0] === 0) {
                firstColHasZero = true;
                break;
            }
        }
    
        // 第三步:遍历矩阵(排除首行首列),用首行首列标记需要置零的行/列
        for (let i = 1; i < rows; i++) {
            for (let j = 1; j < cols; j++) {
                if (matrix[i][j] === 0) {
                    matrix[i][0] = 0; // 标记第i行需要置零
                    matrix[0][j] = 0; // 标记第j列需要置零
                }
            }
        }
    
        // 第四步:根据首列标记,置零对应行
        for (let i = 1; i < rows; i++) {
            if (matrix[i][0] === 0) {
                for (let j = 1; j < cols; j++) {
                    matrix[i][j] = 0;
                }
            }
        }
    
        // 第五步:根据首行标记,置零对应列
        for (let j = 1; j < cols; j++) {
            if (matrix[0][j] === 0) {
                for (let i = 1; i < rows; i++) {
                    matrix[i][j] = 0;
                }
            }
        }
    
        // 第六步:处理首行
        if (firstRowHasZero) {
            for (let j = 0; j < cols; j++) {
                matrix[0][j] = 0;
            }
        }
    
        // 第七步:处理首列
        if (firstColHasZero) {
            for (let i = 0; i < rows; i++) {
                matrix[i][0] = 0;
            }
        }
    }
    

合并两个有序数组

  1. 思路:双指针 (从后到前,最优解),利用 nums1 末尾的预留空间 (0 的位置),从两个数组的末尾有效元素开始比较,将较大的元素直接放入 nums1 的末尾,无需额外空间;

    • 初始化指针:l 指向 nums1 有效元素末尾 (m-1)r2 指向 nums2 末尾 (n-1)r 指向 nums1 最终末尾 (m+n-1)
    • 比较 nums1[l]nums2[r2],将较大的放入 nums1[r],并移动对应指针;
    • nums2 还有剩余元素 (r >= 0),将剩余元素全部放入 nums1 (nums1 剩余位置无需处理,因为本身就是有效元素)
  2. 复杂度:

    • 时间复杂度:O(m+n)
    • 空间复杂度:O(1) (无额外空间)
  3. 实现代码:

    function merge3(nums1: number[], m: number, nums2: number[], n: number): void {
      // 初始化指针:left→nums1 有效末尾,right2→nums2 末尾,right→nums1 最终末尾
      let left = m - 1,
        right2 = n - 1,
        right = m + n - 1;
    
      // 步骤1:从后向前比较,填充较大元素
      while (left >= 0 && right2 >= 0) {
        if (nums1[left] >= nums2[right2]) {
          nums1[right--] = nums1[left--];
        } else {
          nums1[right--] = nums2[right2--];
        }
      }
    
      // 步骤2:处理 nums2 剩余元素(nums1剩余无需处理,本身就是有序的)
      while (right2 >= 0) {
        nums1[right--] = nums2[right2--];
      }
    }
    

矩阵对角线元素的和

  1. 实现方式一:基础遍历

    1. 思路:
      • 获取矩阵边长 n (正方形矩阵行 / 列数相等)
      • 遍历每一行 i,分别取主对角线元素 mat[i][i] 和副对角线元素 mat[i][n-1-i] (主对角线:行号 = 列号,副对角线:行号 + 列号 = n-1)
      • i === n-1-i (奇数阶矩阵中心元素),则只加一次,否则两个元素都加;
      • 累加所有符合条件的元素得到结果;
    2. 复杂度:
      • 时间复杂度:O(n) (仅遍历 n 行)
      • 空间复杂度:O(1) (仅用常量变量)
    3. 实现代码:
    function diagonalSum(mat: number[][]): number {
        const n = mat.length; // 矩阵边长
        let sum = 0;
    
        for (let i = 0; i < n; i++) {
            const main = mat[i][i]; // 主对角线元素
            const anti = mat[i][n - 1 - i]; // 副对角线元素
    
            if (i === n - 1 - i) {
                sum += main; // 中心元素只加一次
            } else {
                sum += main + anti;
            }
        }
    
        return sum;
    }
    
  2. 实现方式二:无判断累加 (简化逻辑)

    1. 思路:
      • 先累加所有主对角线 + 副对角线元素;
      • 若矩阵阶数为奇数,减去重复计算的中心元素 (因为中心元素被加了两次)
    2. 复杂度:
      • 时间复杂度:O(n) (仅遍历 n 行)
      • 空间复杂度:O(1) (仅用常量变量)
    3. 实现代码:
    function diagonalSum(mat: number[][]): number {
        const n = mat.length;
        let sum = 0;
    
        // 累加主、副对角线所有元素(可能重复)
        for (let i = 0; i < n; i++) {
            sum += mat[i][i]; // 主对角线
            sum += mat[i][n - 1 - i]; // 副对角线
        }
    
        // 奇数阶矩阵:减去重复的中心元素
        if (n % 2 === 1) {
            const mid = Math.floor(n / 2);
            sum -= mat[mid][mid];
        }
    
        return sum;
    }
    
  3. 实现方式三:累加对角线元素后将其置 0 (避免重复计算 / 标记已处理)

    1. 思路:
      • 遍历主对角线,累加元素值后将该位置置 0
      • 遍历副对角线,累加元素值 (此时中心元素已被置 0,无需额外判断)
      • 最终累加和即为结果 (中心元素仅在主对角线遍历中被加一次)
    2. 复杂度:
      • 时间复杂度:O(n) (仅遍历 n 行)
      • 空间复杂度:O(1) (仅用常量变量)
    3. 实现代码:
    function diagonalSum(mat: number[][]): number {
        const n = mat.length;
        let sum = 0;
    
        // 主对角线累加并置0
        for (let i = 0; i < n; i++) {
            sum += mat[i][i];
            mat[i][i] = 0;
            sum += mat[i][n - 1 - i];
        }
    
        return sum;
    }
    
  4. 实现方式四:双指针 (优化遍历,仅遍历对角线元素)

    1. 思路:
      • 用两个指针 left (主对角线列号)right (副对角线列号)
      • 遍历每一行,left0 递增到 n-1rightn-1 递减到 0
      • 累加当前行的 mat[i][left]mat[i][right],若 left === right 则只加一次;
    2. 复杂度:
      • 时间复杂度:O(n) (仅遍历 n 行)
      • 空间复杂度:O(1) (仅用常量变量)
    3. 实现代码:
    function diagonalSum(mat: number[][]): number {
        const n = mat.length;
        let sum = 0;
        let left = 0;
        let right = n - 1;
    
        for (let i = 0; i < n; i++) {
            if (left === right) {
                sum += mat[i][left];
            } else {
                sum += mat[i][left] + mat[i][right];
            }
            left++;
            right--;
        }
    
        return sum;
    }
    

整数反转

  1. 方式一:数学取余法 (高效,符合题目 “不存储 64 位整数” 要求)

    1. 思路:
      • 通过取余 (%10) 提取数字的最后一位,通过整除 (Math.trunc(x / 10)) 去掉最后一位;
      • 逐位构建反转后的数字 (result = result * 10 + 余数)
      • 关键:在每一步构建时检查是否超出范围 (避免溢出),而不是最后检查 (符合 “不存储 64 位整数” 的限制)
        • 正数:若 result > MAX/10,则 result*10 必然超过 MAX;若 result === MAX/10,则最后一位不能超过 7 (MAX=2147483647)
        • 负数:若 result < MIN/10,则 result*10 必然低于 MIN;若 result === MIN/10,则最后一位不能低于 -8 (MIN=-2147483648)
    2. 复杂度:
      • 时间复杂度:O(n),循环次数 = 数字位数;
      • 空间复杂度:O(1)
    3. 实现代码:
    function reverse(x: number): number {
        const MAX = 2 ** 31 - 1; // 2147483647
        const MIN = - (2 ** 31); // -2147483648
        let result = 0;
    
        while (x !== 0) {
            // 提取最后一位数字(处理负数:-123 % 10 = -3,符合预期)
            const digit = x % 10;
            // 去掉最后一位(Math.trunc 处理负数更准确:Math.trunc(-123/10) = -12)
            x = Math.trunc(x / 10);
    
            // 提前检查溢出:在乘以10加余数前判断是否超出范围
            // 正数溢出:result > MAX/10 或 (result === MAX/10 且 digit > 7)(MAX最后一位是7)
            if (result > Math.trunc(MAX / 10) || (result === Math.trunc(MAX / 10) && digit > 7)) return 0;
            // 负数溢出:result < Math.trunc(MIN/10) 或 (result === Math.trunc(MIN/10) 且 digit < -8)
            if (result < Math.trunc(MIN / 10) || (result === Math.trunc(MIN / 10) && digit < -8)) return 0;
    
            // 构建反转数字
            result = result * 10 + digit;
        }
    
        return result;
    }
    
  2. 方式二:递归法 (拓展思路,不推荐生产使用)

    1. 思路:
      • 通过递归逐位提取最后一位,构建反转数字,同时在递归过程中检查溢出;
      • 每一步递归提取最后一位,更新反转结果,并检查溢出;
      • 递归深度取决于数字位数 (最多 10 位,无栈溢出风险),但性能略低于迭代的数学法,仅作思路拓展;
    2. 复杂度:
      • 时间复杂度:O(n),递归次数 = 数字位数;
      • 空间复杂度:O(1)
    3. 实现代码:
    function reverse(x: number): number {
        const MAX = 2 ** 31 - 1;
        const MIN = - (2 ** 31);
    
        // 递归辅助函数:参数为剩余数字、当前反转结果
        const helper = (remaining: number, res: number): number => {
            if (remaining === 0) return res;
    
            const digit = remaining % 10;
            const newRemaining = Math.trunc(remaining / 10);
    
            // 溢出检查(同数学法)
            if (res > Math.trunc(MAX / 10) || (res === Math.trunc(MAX / 10) && digit > 7)) return 0;
            if (res < Math.trunc(MIN / 10) || (res === Math.trunc(MIN / 10) && digit < -8)) return 0;
    
            return helper(newRemaining, res * 10 + digit);
        };
    
        return helper(x, 0);
    }
    

杨辉三角

  1. 方式一:基础迭代法

    1. 思路:
      • 初始化结果数组,逐行构建;
      • 每行先初始化全 1 的数组 (满足首尾为 1 的特性)
      • 遍历每行的中间元素,中间的每个元素等于上一行中相邻两个元素之和 (即 row[i] = prevRow[i-1] + prevRow[i])
    2. 复杂度:
      • 时间复杂度:O(n²) (n 为行数)
      • 空间复杂度:O(n²) (n 为行数)
    3. 实现代码:
    function generate(numRows: number): number[][] {
        // 边界处理:行数为 0 时返回空数组
        if (numRows === 0) return [];
    
        // 初始化结果数组,第一行固定为 [1]
        const result: number[][] = [[1]];
    
        // 从第 2 行开始构建(i 代表当前行号,从 1 开始,对应第 2 行)
        for (let i = 1; i < numRows; i++) {
            // 初始化当前行为全 1 的数组,长度为 i+1(第 i+1 行有 i+1 个元素)
            const currentRow: number[] = new Array(i + 1).fill(1);
            // 获取上一行数据
            const prevRow = result[i - 1];
    
            // 计算中间元素(首尾已经是 1,无需处理)
            // 遍历范围:从第 1 个元素到倒数第 2 个元素
            for (let j = 1; j < currentRow.length - 1; j++) {
                currentRow[j] = prevRow[j - 1] + prevRow[j];
            }
    
            // 将当前行加入结果
            result.push(currentRow);
        }
    
        return result;
    };
    
  2. 方式二:递归法 (分治思路)

    1. 思路:
      • 递归核心:生成第 n 行的前提是先生成第 n-1 行;
      • 终止条件:
        • n = 1 时,返回 [[1]]
        • 递归获取前 n-1 行的结果,再基于最后一行计算第 n 行;
    2. 复杂度:
      • 时间复杂度:总操作次数:1 + Σ(i 从 1 到 n-1)i = 1 + n(n-1)/2,最终时间复杂度为 O(n²)
      • 空间复杂度:O(n²)
    3. 实现代码:
    function generate(numRows: number): number[][] {
        // 终止条件 1:行数为 0
        if (numRows === 0) return [];
        // 终止条件 2:行数为 1
        if (numRows === 1) return [[1]];
    
        // 递归获取前 numRows - 1 行的结果
        const prevResult = generate(numRows - 1);
        // 获取上一行数据
        const prevRow = prevResult[prevResult.length - 1];
        // 构建当前行
        const currentRow: number[] = [1];
    
        // 计算中间元素
        for (let j = 1; j < prevRow.length; j++) {
            currentRow.push(prevRow[j - 1] + prevRow[j]);
        }
        currentRow.push(1);
    
        // 将当前行加入结果并返回
        return [...prevResult, currentRow];
    };
    

字母异位词分组

  1. 方式一:排序键值法

    1. 思路:
      • 对每个字符串排序,异位词排序后结果相同 (如 eat 和 tea 排序后都是 aet)
      • 用排序后的字符串作为 Map 的键,值为对应异位词数组,遍历完成后返回 Map 的值即可;
    2. 复杂度:
      • 时间复杂度:O(n∗klogk)n 是字符串数组长度,k 是单个字符串的最大长度,每个字符串排序的时间是 O(klogk),遍历 n 个字符串总时间为 O(n∗klogk)
      • 空间复杂度:O(n∗k)Map 存储所有字符串,总空间为所有字符串的字符数之和;
    3. 实现代码:
    function groupAnagrams(strs: string[]): string[][] {
        // 定义Map,键为排序后的字符串,值为异位词数组
        const map = new Map<string, string[]>();
    
        for (const str of strs) {
            // 1. 拆分字符串为字符数组 → 排序 → 重新拼接成键
            const key = str.split('').sort().join('');
            // 2. 若键不存在则初始化空数组,存在则直接追加当前字符串
            if (!map.has(key)) {
                map.set(key, []);
            }
            map.get(key)!.push(str);
        }
    
        // 3. 将Map的值转换为数组返回
        return Array.from(map.values());
    }
    
  2. 方式二:计数键值法(优化排序耗时)

    1. 思路:
      • 异位词的字符计数完全相同 (如 eat 和 tea 都是 a:1, e:1, t:1)
      • 用字符计数的字符串 (如 a1e1t1) 作为 Map 键,避免排序的 O(klogk) 耗时;
    2. 复杂度:
      • 时间复杂度:O(n∗k),遍历每个字符串的每个字符 (O(n∗k)),计数和生成键的操作是 O(26) (常数),无排序耗时;
      • 空间复杂度:O(n∗k)Map 存储所有字符串,计数数组是常数空间 (26 长度),不影响整体复杂度;
    3. 实现代码:
    function groupAnagramsByCount(strs: string[]): string[][] {
        const map = new Map<string, string[]>();
    
        for (const str of strs) {
            // 1. 初始化26个字母的计数数组(对应a-z)
            const count = new Array(26).fill(0);
            // 2. 统计每个字符出现次数
            for (const char of str) {
                const index = char.charCodeAt(0) - 'a'.charCodeAt(0);
                count[index]++;
            }
            // 3. 生成计数键(如a1e1t1)
            const key = count.map((num, i) => num > 0 ? `${String.fromCharCode(97 + i)}${num}` : '').join('');
            // 4. 存入Map
            if (!map.has(key)) {
                map.set(key, []);
            }
            map.get(key)!.push(str);
        }
    
        return Array.from(map.values());
    }
    

螺旋矩阵

  1. 思路:

  2. 复杂度:

    • 时间复杂度:
    • 空间复杂度:
  3. 实现代码:

螺旋矩阵 II

{% lc https://leetcode.cn/problems/spiral-matrix-ii/ 59.螺旋矩阵II %}
  1. 思路:

  2. 复杂度:

    • 时间复杂度:
    • 空间复杂度:
  3. 实现代码:

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 数组
    1. 1.1. 访问/修改-索引 O(1)
    2. 1.2. 插入元素 O(n)
    3. 1.3. 删除元素 O(n)
    4. 1.4. 合并数组 O(m+n)
  2. 2. 数组的特殊性
  3. 3. 快慢数组
  4. 4. 前端实战:如何避免触发慢数组(性能优化)
  5. 5. 前端业务中的实际影响
  6. 6. 二维矩阵与一维索引互转公式
    1. 6.1. 公式 1:二维索引 → 一维索引((i,j) → k)
    2. 6.2. 公式 2:一维索引 → 二维索引(k → (i,j))
  7. 7. 力扣题单
    1. 7.1. 重塑矩阵
    2. 7.2. 矩阵置零
    3. 7.3. 合并两个有序数组
    4. 7.4. 矩阵对角线元素的和
    5. 7.5. 整数反转
    6. 7.6. 杨辉三角
    7. 7.7. 字母异位词分组
    8. 7.8. 螺旋矩阵
    9. 7.9. 螺旋矩阵 II