Hashmap/ Hashset- Q39~Q42内容

Q39- code49- 字母异位词分组

实现思路

1 方法1: Map + Sort

易错点:

  • 如果使用 字符编码之和作为key, 可能会有哈希冲突(如 "az" 和 "by")
  • 所以需要 精确使用 字符排序后的字符串作为key

参考文档

01- 方法1参考实现

代码实现

1 方法1: Map + Sort 时间复杂度: O(nmlogm); 空间复杂度(nm)

ts 复制代码
function groupAnagrams(strs: string[]): string[][] {
  const saved = new Map();
  for (let str of strs) {
    // 易错点1:字符编码之和作为key, 可能会有哈希冲突(如 "az" 和 "by")
    // 正确方法:对字符串中的字符进行排序, 排序后的字符串对于所有异位词都是相同的
    const strKey = str.split("").sort().join("");
    const pre = saved.get(strKey) || [];
    saved.set(strKey, [...pre, str]);
  }
  return [...saved.values()];
}

Q40- code350- 两个数组的交集 II

实现思路

1 Map

  • 优化点: 先判断两个数组的长度,将较短的数组作为l1,较长的数组作为l2
  • 易错点1: 由于Map的key是Set,所以需要累增计数,而不能每个key都设置为1

参考文档

01- 方法1参考实现

代码实现

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

ts 复制代码
function intersect(nums1: number[], nums2: number[]): number[] {
  // 优化点: 先判断两个数组的长度,将较短的数组作为l1,较长的数组作为l2
  const [l1, l2] =
    nums1.length <= nums2.length ? [nums1, nums2] : [nums2, nums1];
  // 易错点1: 由于Map的key是Set,所以需要累增计数,而不能每个key都设置为1
  const saved = new Map();
  const res = [];
  l1.map((val) => saved.set(val, (saved.get(val) ?? 0) + 1));
  for (const val of l2) {
    const pre = saved.get(val);
    if (pre) {
      res.push(val);
      saved.set(val, pre - 1);
    }
  }
  return res;
}

Q41- code299- 猜数字游戏

实现思路

1 数字次数统计法

1.1 错误思路

  • S1 创建secret的 (idx: val) 记录对
  • S2 遍历guess 如果(idx, val) 都对,则作为 A长度+1 + 删除该记录 如果val对, idx不对,则作为 B长度+1 + 删除该记录 如果val不对, idx不对,直接略过

错误点: 以上实现会出现 误删除 后一个位置是正确公牛的情况 如 secret = "011" guess = "110"

1.2 正确思路是,

  • 创建 0~9数字的出现次数 统计
  • 先处理完所有的公牛情况,再单独处理奶牛
  • 奶牛的个数,应该是secret 和 guess 对应0~9数字 的出现的最小次数

参考文档

01- 方法1参考实现

代码实现

1 方法1: 数字次数统计法 时间复杂度: O(n); 空间复杂度 O(1)

ts 复制代码
function getHint(secret: string, guess: string): string {
  let bulls = 0;
  // 长度为10的数组,记录secret中每个数字(0-9)的出现次数(排除公牛后)
  const secretCount = new Array(10).fill(0);
  // 长度为10的数组,记录guess中每个数字(0-9)的出现次数(排除公牛后)
  const guessCount = new Array(10).fill(0);

  // 题目保证了secret.length == guess.length
  for (let i = 0; i < secret.length; i++) {
    if (secret[i] === guess[i]) {
      // 如果 位置和值 都相等:说明是公牛,bulls++
      bulls++;
    } else {
      // 将这两个数字分别计入统计数组(排除公牛后的统计)
      // 等价于 secretCount[+secret[i]] = secretCount[+secret[i]] + 1
      secretCount[+secret[i]]++;
      guessCount[+guess[i]]++;
    }
  }

  // 计算奶牛数量:遍历secretCount,
  // count:当前数字在secret中的出现次数
  // i:当前数字(0-9)
  // guessCount[i]:当前数字在guess中的出现次数

  // 对于每个数字,取该数字在secret和guess中的出现次数的最小值
  // 累加得到奶牛数量
  const cows = secretCount.reduce(
    (sum, count, i) => sum + Math.min(count, guessCount[i]),
    0
  );
  // 返回结果格式:公牛数量 + "A" + 奶牛数量 + "B"
  return `${bulls}A${cows}B`;
}

Q42- code348- 设计井字棋

实现思路

1 方法1:增量更新 + 计数追踪

  • 我们只关心:每行/列/对角线被同一玩家占据了多少个位置
  • 只存储"获胜条件"的状态,而不是整个棋盘状态
  • 这种"增量更新"的思想 在很多算法题中都很常见,比如滑动窗口、前缀和等,都是避免重复计算,利用已知信息快速得到新结果

1.2 具体分析过程

S1 理解题目核心

  • 首先明确题目要求:每次落子后,判断是否有玩家获胜。
  • 获胜条件是:同一行、同一列 或 同一对角线 都被 同一玩家占据

S2 从简单情况开始思考:暴力解法

  • 每次落子后,检查所有行、列、对角线
  • 时间复杂度:O(n²),因为要遍历整个棋盘

S3.1 寻找优化思路:观察获胜模式

  • 获胜需要:某一行全部相同,或某一列全部相同,或某条对角线全部相同
  • 关键点:不需要检查整个棋盘,只需要检查当前落子影响的行、列、对角线

S3.2 计数思维

  • 对于每一行:记录每个玩家在该行的棋子数量
  • 对于每一列:记录每个玩家在该列的棋子数量
  • 对于对角线:记录每个玩家在对角线的棋子数量
  • 当某个玩家的计数达到n时,就获胜了

S4 边界情况考虑

  • 对角线:只有落子在主对角线或副对角线时才需要更新
  • 玩家标识:可以用1和2,或者1和-1来区分
  • 平局情况:当棋盘满了但没有获胜者

参考文档

01- 方法1参考实现

代码实现

1 方法1: 增量更新 + 计数追踪 时间复杂度: O(1); 空间复杂度 O(n)

ts 复制代码
class TicTacToe {
  private n: number;
  private rows: number[];
  private cols: number[];
  private diagonals: number[];
  constructor(n: number) {
    // 创建 n行 * n列 + 2条对角线的 记录
    this.n = n;
    this.rows = new Array(n).fill(0);
    this.cols = new Array(n).fill(0);
    // diagonals[0]主对角线,diagonals[1]副对角线
    this.diagonals = [0, 0];
  }

  move(row: number, col: number, player: number): number {
    // 使用 1 和 -1 来区分玩家,简化计算
    const val = player === 1 ? 1 : -1;
    const n = this.n;
    // 更新行计数 和 列计数
    this.rows[row] += val;
    this.cols[col] += val;
    // 更新主对角线:其特点是 row === col
    // 如:(0,0) (1,1) (2,2)...
    if (row === col) this.diagonals[0] += val;
    // 更新副对角线:其特点是 row + col === n - 1
    // 如:(0,2) (1,1) (2,0)...
    if (row + col === n - 1) this.diagonals[1] += val;
    // 判断落下了 该行/该列/该对角线后,此时是否有绝对值 达到了n
    if (
      Math.abs(this.rows[row]) === n ||
      Math.abs(this.cols[col]) === n ||
      Math.abs(this.diagonals[0]) === n ||
      Math.abs(this.diagonals[1]) === n
    ) {
      return player;
    }
    // 否则说明落完这一子后,当前还没有获胜者
    return 0;
  }
}
相关推荐
Tiandaren4 小时前
Selenium 4 教程:自动化 WebDriver 管理与 Cookie 提取 || 用于解决chromedriver版本不匹配问题
selenium·测试工具·算法·自动化
岁忧5 小时前
(LeetCode 面试经典 150 题 ) 11. 盛最多水的容器 (贪心+双指针)
java·c++·算法·leetcode·面试·go
chao_7895 小时前
二分查找篇——搜索旋转排序数组【LeetCode】两次二分查找
开发语言·数据结构·python·算法·leetcode
一斤代码5 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子5 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年5 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
秋说7 小时前
【PTA数据结构 | C语言版】一元多项式求导
c语言·数据结构·算法
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构