Q36~Q38内容

78 阅读6分钟

Q36-code128- 最长连续序列

实现思路

1 方法1: Set

1.1 理解题目要求:找最长连续序列

  • 最长:需要找出 所有可能的连续序列中 最长的那个
  • 连续:序列中的元素是连续的,即相邻元素的差为1
  • 无序:数组是无序的

1.2 从简单情况入手:有序数组

  • 最长连续序列就是:从头开始,依次往后找,直到找到 不连续的元素为止

1.3 无序情况:思考连续序列的特点

  • 序列的起点应该是 连续序列中最小的那个数
  • 即 当且仅当它的 前一个数不在数组中,才可能是起点
  • 如果我们能找到 所有起点,就能找到 所有可能的连续序列
  • 实现方法:使用Set,可以快速查找当前元素的 前一个数 是否存在

1.4 优化:避免重复计算和剪枝

  • 避免重复计算:如果一个数已经在某个序列中,就不用再以它为起点
  • 剪枝:如果当前找到的序列长度 已经超过剩余未处理的数的个数,就可以提前结束

参考文档

01- 方法1参考实现

代码实现

1 方法1: Set 时间复杂度: O(n); 空间复杂度(n)

function longestConsecutive(nums: number[]): number {
  let numSet = new Set(nums);
  let res = 0;
  // S1 遍历所有可能的起点
  for (let x of numSet) {
    // S2 如果当前元素的 前一个数 存在,
    // 说明它必然不可能是 序列的起点,直接跳过
    if (numSet.has(x - 1)) continue;

    // S3 找到当前序列的终点,以获取一个可能的返回值
    let y = x + 1;
    while (numSet.has(y)) y++;

    // S4 更新res,获取其最大值
    // 循环结束后,y-1 是最后一个在Set里的数
    // 从 x 到 y-1 一共 y-x 个数
    res = Math.max(res, y - x);

    // S5 如果当前序列长度 已经是可能的最大值,就可以提前结束
    if (res === nums.length) break;
  }
  return res;
}

Q37-code73- 矩阵置零

实现思路

1 方法1: 首行列标识法

1 核心思路:使用第一行和第一列作为标记数组

    1. 🏷️ 记录原始状态:第一行 && 第一列 是否原本就有0
    1. 🔍 扫描内部:发现0就在第一行 && 第一列做标记
    1. 🎯 置零内部:根据标记 置0 内部元素
    1. 🏁 处理边界:根据步骤1的原本记录 处理 第一行 && 第一列 是否置0

2 逻辑易错点:

  • 1.1 在遍历过程中直接修改矩阵,会影响后续的判断

  • 1.2 新置零的元素会被误认为是原本的零元素,导致错误的二次置零

  • 2.1 必须先记录第一行第一列的原始状态,否则会影响后续的判断

  • 2.2 必须最后处理第一行和第一列,否则会影响标记


2 方法2:m+n 标识法

1 核心思路:

    1. 使用两个独立的布尔数组分别记录需要置零的行和列
    1. 第一次遍历:标记所有包含0的行和列
    1. 第二次遍历:根据标记进行批量置零

参考文档

01- 方法1参考实现

代码实现

1 方法1: 首行列标识法 时间复杂度: O(m * n); 空间复杂度(1)

function setZeroes(matrix: number[][]): void {
  const [m, n] = [matrix.length, matrix[0].length];
  // S1 标记第一行 && 第一列 是否有0标识
  const [r0, c0] = [matrix[0].includes(0), matrix.some((row) => row[0] === 0)];
  // S2 从第1行第1列开始,遍历元素,遇到0则在对应的 首行 && 首列上 写入0标记
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      // matrix[i][0] = 0:在第1列第i行 写入0标记
      // matrix[0][j] = 0:在第1行第j列 写入0标记
      if (matrix[i][j] === 0) matrix[i][0] = matrix[0][j] = 0;
    }
  }

  // S3 从第1行第1列开始,只要元素对应的 首行 && 首列上 是0标记,则整行整列都写入0
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      // 如果该元素所属的 第一行 || 第一列 有0标记,则将当前元素置0
      // matrix[i][0] === 0:检查第一列的标记
      // matrix[0][j] === 0:检查第一行的标记
      if (!matrix[i][0] || !matrix[0][j]) matrix[i][j] = 0;
    }
  }
  // S4 根据一开始获得的 第一行 && 第一列 0标识,处理第一行 && 第一列 的0值写入
  if (r0) matrix[0].fill(0);
  if (c0) matrix.map((row) => (row[0] = 0));
}

2 方法2:m+n 标识法 时间复杂度: O(m * n); 空间复杂度(m + n)

function setZeroes(matrix: number[][]): void {
  // S1: 创建两个布尔数组,分别记录需要置零的行和列
  const [m, n] = [matrix.length, matrix[0].length];
  const [rows, cols] = [Array(m).fill(false), Array(n).fill(false)];

  // S2: 遍历矩阵,发现0就标记对应的行和列
  matrix.map((row, i) =>
    row.map((val, j) => {
      if (val === 0) [rows[i], cols[j]] = [true, true];
    })
  );

  // S3 批量置零:优先处理整行,再处理列
  // 根据行标记,整行fill(0)置零
  // 根据列标记,整列逐行置零
  rows.map((needZero, i) => {
    if (needZero) matrix[i].fill(0);
  });
  cols.map((needZero, j) => {
    if (needZero) matrix.map((row) => (row[j] = 0));
  });
};

Q38-code380- O(1) 插入、删除 和 获取随机元素

实现思路

1 方法1: 数组 + Map

1 实现思路

1.1 为什么不能只用 Set 或 Map

  • Set/Map:虽然插入、删除、查找都是 O(1),但无法 O(1) 随机访问元素(没有下标)
  • 数组:可以 O(1) 随机访问,但删除/查找不是 O(1)

1.2 为什么要“数组+哈希表”双结构

  • 数组:支持 O(1) 随机访问(getRandom)。
  • 哈希表:支持 O(1) 查找和删除(insert/remove)

2.1 删除时:用数组最后一个元素覆盖要删的位置,保证删除也是 O(1)

  • 插入:数组末尾加,哈希表记下标。
  • 删除:用最后一个元素覆盖要删的元素,然后pop,哈希表同步更新

比喻理解:

  • 数组像一排座位,随便抽一个人很快(getRandom)
  • 哈希表像名单,查某个人在第几号座位很快(insert/remove)
  • 删除时,把最后一个人拷贝到 待删除的座位上 + 删除旧位置,名单同步改一下

参考文档

01- 方法1参考实现

代码实现

1 方法1: 数组 + Map 时间复杂度: O(1); 空间复杂度(n)

class RandomizedSet {
  saved: Map<number, number>;
  arr: Array<number>;
  constructor() {
    this.saved = new Map();
    this.arr = [];
  }

  insert(val: number): boolean {
    if (this.saved.has(val)) return false;
    this.saved.set(val, this.arr.length);
    this.arr.push(val);
    return true;
  }

  remove(val: number): boolean {
    const idx = this.saved.get(val);
    if (idx == null) return false;
    const last = this.arr.at(-1);
    this.arr[idx] = last;
    // 易错点1:remove位置替换为last后,对应的Map的<k, v> 也要更新
    this.saved.set(last, idx);

    // 弹出冗余的last && 删除旧的val记录
    this.arr.pop();
    this.saved.delete(val);
    return true;
  }

  getRandom(): number {
    const rdx = Math.floor(Math.random() * this.arr.length);
    return this.arr[rdx];
  }
}