Q36-code128- 最长连续序列
实现思路
1 方法1: Set
1.1 理解题目要求:找最长连续序列
- 最长:需要找出 所有可能的连续序列中 最长的那个
- 连续:序列中的元素是连续的,即相邻元素的差为1
- 无序:数组是无序的
1.2 从简单情况入手:有序数组
- 最长连续序列就是:从头开始,依次往后找,直到找到 不连续的元素为止
1.3 无序情况:思考连续序列的特点
- 序列的起点应该是 连续序列中最小的那个数
- 即 当且仅当它的 前一个数不在数组中,才可能是起点
- 如果我们能找到 所有起点,就能找到 所有可能的连续序列
- 实现方法:使用Set,可以快速查找当前元素的 前一个数 是否存在
1.4 优化:避免重复计算和剪枝
- 避免重复计算:如果一个数已经在某个序列中,就不用再以它为起点
- 剪枝:如果当前找到的序列长度 已经超过剩余未处理的数的个数,就可以提前结束
参考文档
代码实现
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 核心思路:使用第一行和第一列作为标记数组
-
- 🏷️ 记录原始状态:第一行 && 第一列 是否原本就有0
-
- 🔍 扫描内部:发现0就在第一行 && 第一列做标记
-
- 🎯 置零内部:根据标记 置0 内部元素
-
- 🏁 处理边界:根据步骤1的原本记录 处理 第一行 && 第一列 是否置0
2 逻辑易错点:
-
1.1 在遍历过程中直接修改矩阵,会影响后续的判断
-
1.2 新置零的元素会被误认为是原本的零元素,导致错误的二次置零
-
2.1 必须先记录第一行第一列的原始状态,否则会影响后续的判断
-
2.2 必须最后处理第一行和第一列,否则会影响标记
2 方法2:m+n 标识法
1 核心思路:
-
- 使用两个独立的布尔数组分别记录需要置零的行和列
-
- 第一次遍历:标记所有包含0的行和列
-
- 第二次遍历:根据标记进行批量置零
参考文档
代码实现
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)
- 删除时,把最后一个人拷贝到 待删除的座位上 + 删除旧位置,名单同步改一下
参考文档
代码实现
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];
}
}