刷力扣简单题:从哈希分组到雇员分组,建立算法直觉
今天照例打开力扣准备刷今天的每日一题。长期关注“勤劳的小蜜蜂系列”的朋友应该知道我这个系列的定位一直很明确不追难题、不炫技每天老老实实刷几道力扣简单题把基础打得结结实实。有人可能会觉得简单题有什么好刷的但如果你正处于准备面试的阶段或者在数据结构和算法上总觉得“会做但说不清”那这篇文章应该能给你一些不一样的启发。今天这一篇我除了记录今天刷的几道题、拆解思路之外还想聊聊一个最近搜索热度很高的题目方向——“雇员按共同特征分组”这类题。很多人在搜“力扣1875将雇员相同的分组”说明一旦题目从纯数组操作跳到一个带业务背景的“分组”场景不少人是会卡住的。这恰恰是我一直强调的简单题从来不只是简单题它练的是你脑子里的“算法直觉”这种直觉能直接迁移到真实的业务开发里。下面我把今天的刷题过程、思路拆解、以及一些坚持刷题的心得一次性整理出来。1. 为什么我坚持刷力扣简单题这事比看上去有价值得多1.1 简单题不是“水题”而是稳定性的地基有相当一部分人会有一个误区觉得刷简单题没什么技术含量要刷就刷中等题、困难题。我早期也有过这个阶段上来就死磕困难题结果一道题能卡两三个小时最后看题解都费劲挫败感特别强。后来我调整了策略每天固定先刷一两道简单题把状态热起来再考虑要不要碰难题。坚持一段时间后发现简单题给我带来的收益被严重低估了。拿“合并两个有序链表”这种经典题来说看着简单但你真能在面试的紧张状态下一次性写出没有边界漏洞的代码吗递归写法想清楚终止条件了吗迭代写法记得用虚拟头节点吗这些细节就是简单题的价值——它们在训练你的“肌肉记忆”让你在真正写代码的时候不用分心去想基础语法和常见套路。另一个角度是知识体系的查漏补缺。大部分简单题考察的都不是偏门算法而是数组、字符串、链表、哈希表、栈、队列这些最核心的数据结构基础操作。这些内容恰恰是复杂题目的“原子操作”。我开始系统刷简单题之后才发现自己对StringBuilder的适用场景、哈希表遍历时的删除规则、链表反转的指针顺序这些细节其实并没有完全吃透。1.2 面试中简单题的真实权重我自己参与过几次技术面试的旁听也和其他做面试官的朋友聊过。一个很真实的观察是面试手撕代码环节真正出困难题的公司其实很少大多数面试官更愿意用中等题来考察而中等题的下限往往就是简单题的上限。比如“有效的括号”这道题算简单题但它依然是很多公司面试题库里的常客。再比如“买卖股票的最佳时机”简单到不能再简单但面试官把它包装成一个业务场景——“给你一个数组判断哪天买哪天卖收益最大”照样能筛掉一批人。原因很简单一道看似简单的题能考察你对数据结构的选择、边界条件的把握、时间复杂度的优化意识这些才是工程能力的体现。所以我一直劝准备面试的朋友别把简单题当热身要当主菜来吃。你把简单题刷出条件反射了中等题里拿到核心思路的概率会大幅提升。1.3 “小蜜蜂系列”存在的意义对抗遗忘说点更贴近这个系列主题的事情。我叫这个系列“勤劳的小蜜蜂”核心不在于“勤劳”而在于“每天”。算法能力的衰减速度比你想象中快很多。我有个阶段连续两周没刷题再回头做一道普通的数组题手生了不说连最常用的双指针套路都要想半天。每天固定刷简单题本质上是一种非常低成本的“手感维护”。它不需要你腾出大块时间不需要你死磕到深夜只要每天花二三十分钟让大脑保持“数据结构在线”的状态这个收益长期来看是非常可观的。我自己的切身体会是坚持这个系列之后我面对陌生题目时的第一反应不再是“这题我肯定做不出来”而是“这题的考点应该落在哪个区间里”——这种直觉的提升靠的就是每天一小步的积累。2. 今天的刷题清单与核心解题思路拆解今天的清单我选了四道题覆盖了四个最常见的考察方向字符串匹配、哈希计数、链表操作、二分查找。每一道题我都按“我的第一思路 → 优化 → 易错点”这个顺序写出来方便你参考。2.1 字符串与栈括号匹配类题目的“状态机”思维题目方向给定一个只包含括号字符的字符串判断括号是否有效闭合。这类题的经典解法是用栈遇到左括号就入栈遇到右括号就看栈顶是否匹配。我最初写的时候犯过一个很蠢的错误只判断了左右括号数量是否相等没考虑顺序问题比如“)(”这种字符串数量上是一比一但显然是无效的。栈这种结构之所以适合这道题就是因为它天然带着“最近的左括号先被匹配”的语义——这和真实世界的嵌套结构完全一致。一个小技巧是入栈的时候不要存左括号本身而是存对应的右括号。这样遇到右括号时直接和栈顶元素比较是否相等就行不用写一堆switch-case。代码看起来会清爽很多def isValid(s: str) - bool: stack [] mapping {): (, ]: [, }: {} for ch in s: if ch in mapping: if not stack or stack[-1] ! mapping[ch]: return False stack.pop() else: stack.append(ch) return not stack这道题给我最大的提醒是别把简单题想当然。我至少见过三种错误写法——忘了判空栈、忘了括号顺序、忘了最后栈可能不为空。面试里这些全是扣分点。2.2 哈希表与计数“两数之和”到变体题的思路跃迁题目方向给定一个整数数组和一个目标值找出数组中两个数之和等于目标值的下标。“两数之和”大概是力扣上最出名的一道题了。很多人背答案都能写出来但未必理解为什么需要用哈希表。本质上我们是在把“查找”这件事从O(n)降到O(1)。遍历数组时每看到一个数num就检查target-num在不在哈希表里——在的话答案直接出来了不在的话把num和它的下标存进哈希表留给后面的数来匹配。这类哈希计数的思路迁移性极强。今天我看一道变体题要求返回的不是下标而是“是否存在这样两个数”那更简单用set就够了。如果题目改成“三个数之和”先排序再固定一个数剩下两个数用双指针往中间夹又是另一个经典套路。我的经验是遇到“给你一个数组问你能不能凑出某个条件”的题第一反应先想哈希表第二反应想双指针。这两个招法能覆盖相当大比例的简单和中等问题。2.3 链表操作虚拟头节点和双指针的固定套路题目方向删除链表中的某个节点或者返回链表倒数第k个节点按具体题目变形。链表题是很多初学者的噩梦因为指针操作太容易绕晕了。我自己总结出了两个固定套路基本能应对大部分简单题第一虚拟头节点。凡是要删除节点、或者在头部插入节点我都先建一个dummy节点指向真正的head。这样就不用单独讨论“删除的是头节点”这种边界情况最后直接返回dummy.next即可。核心逻辑统一了错误率能降一半。第二快慢指针。找倒数第k个节点让快指针先走k步然后快慢指针一起走快指针到结尾时慢指针正好停在目标位置。这个套路熟练之后像“环形链表检测”这些题也能一眼看穿本质——一个走两步一个走一步追上就是有环。写代码的时候我习惯在纸上先把指针的每一步画出来特别是涉及节点互换的场景。不要觉得画图浪费时间链表题靠脑子硬想十有八九在指针重连那一步会出错。2.4 二分查找不是“在数组里找数字”那么简单题目方向在有序数组中查找目标值或查找第一个满足条件的元素位置。二分查找的模板我见得多了基本写法没问题但有一个小坑很容易踩循环条件写left right还是left right取决于你的搜索区间是开还是闭。我自己的习惯是统一用左闭右闭区间循环条件写left right每次更新left mid 1或者right mid - 1。这样逻辑最容易自洽。另一个实用技巧是不用死记模板只要抓住“每次循环必须把搜索区间缩小一半”这个核心就能自己推导出各个边界怎么写。还有计算mid的时候用mid left (right - left) // 2比(left right) // 2更稳妥能避免极端情况下的整数溢出问题。虽然力扣的测试用例一般不会让你溢出但养成这个习惯在生产代码里是有意义的。今天刷的这几道题难度都不高但每一道都有值得记录的细节。我刷完之后会趁热打铁写一篇简短题解把思路、代码、易错点存到自己的笔记里这样下次复习的时候效率会高很多。3. 从热词“雇员按相同特征分组”看简单题的迁移价值3.1 分组统计的本质从“数数”到“建索引”最近“力扣1875将雇员相同的分组”这个搜索热度不低。光从题名看这道题的核心应该就是把具有相同特征的雇员分到同一组——本质上是分组统计问题。这种题在力扣里太常见了。最简单的一档是“给你一个数组统计每个元素出现的次数”。解法就是哈希表计数遍历一遍count[num] count.get(num, 0) 1。稍微变形成“按字符串长度分组”、“按首字母分组”思路是一样的——选定一个“分组键”然后把数据塞进以这个键为索引的桶里。很多人在解这类题的时候会把注意力放在“怎么把代码写出来”上但我觉得更重要的是理解“为什么要用哈希表做分组”。哈希表的key天然就是一个“分组标识”value就是这一组的聚合结果。所以哈希表 分组 聚合这五个字能贯穿从简单题到中等题的一大片题目。3.2 SQL分组与哈希分桶的思维同构我特别想多说一句“雇员按相同特征分组”这个场景和SQL里的GROUP BY在逻辑上几乎一模一样。GROUP BY department_id就是把同一部门的雇员凑到一组然后可以COUNT(*)、AVG(salary)——这是后端开发、数据分析、报表开发里每天都在用的操作。为什么很多人在LeetCode上看到“雇员分组”会卡壳我觉得是因为他们把“写代码”和“业务思维”分成两件事了。实际上数据结构的训练就是在练业务思维的底层能力。比如哈希表分桶 按用户的某个标签做人群圈选双指针 在有序数据里高效找配对栈 函数调用栈、嵌套结构解析、浏览器的前进后退队列 任务排队、消息队列、广度优先搜索我平时给团队新人做分享时经常说一句话算法不是面试完就扔的东西它就是你对数据做操作的思维模型。你今天刷了一道“统计股票价格最大涨幅”的题明天工作中同事让你“统计每个商品类目的月销量变化”你会发现这俩是一回事。3.3 简单题里的工程启示拿“雇员按共同特征分组”延伸开去真实业务中类似的需求比比皆是。举个例子一个电商后台要给运营同学做一张“用户订单聚合表”需要按“用户ID 月份”分组统计每个组内的订单数和GMV。如果你熟悉哈希分组你会立刻想到两层结构外层先按用户ID分桶内层再按月份分桶。如果数据量大你会自然会想到“map-reduce”的思想——先分组再聚合这本质上就是你在力扣简单题里练过一百遍的东西。再比如日志分析场景里要按“接口路径 状态码”统计请求量。你脑子里浮现的应该是遍历日志拼一个复合key然后计数。这和“两数之和”里用哈希表存target-num有什么本质区别没有。都是“用一个可比较的key快速定位到对应的桶”。所以我强烈建议当你刷到哈希表相关的简单题时刻意多想一步——“这个分组逻辑放到真实场景里会对标什么需求”想多了你的迁移能力会比单纯刷题强好几倍。3.4 这类题的通用解题框架为了方便你直接套用我把这类“分组统计”简单题的通用框架整理出来第一步确定分组键。把所有数据按照哪个维度归为一组可能是元素本身可能是元素的某个属性也可能是多个属性的组合。组合键在编程时可以直接用一个元组Python的tuple当key非常方便。第二步确定聚合方式。分组之后要做什么计数、求和、取最大最小、收集列表这决定了value的类型——计数用int聚合列表用list求最值可以用一个初始值不断比较。第三步处理边界情况。空数组怎么办key不存在怎么办比如collections.defaultdict(int)和defaultdict(list)就是解决“key不存在”这个痛点的最好工具用上之后可以省掉大量if key not in dict的判断。第四步考虑遍历顺序是否需要保留。如果要求输出顺序和第一次出现顺序一致就用普通的dictPython 3.7默认保序如果要求按key排序就最后统一sorted一次如果不要求顺序普通dict或Counter都可以。这套框架不仅能解“雇员分组”这类题对“字符出现次数排序”、“按频率统计单词”这类热门简单题同样适用。把框架内化成自己的比背十道题的代码有用得多。4. 把“小蜜蜂”式坚持落地的刷题日常节奏、记录与防遗忘4.1 每天怎么安排刷题时间说到坚持很多人第一个问题就是“我也知道应该坚持但就是坚持不下来。”我的答案很简单把启动成本降到最低。我现在的固定动作是每天上午打开电脑第一步不是看邮件而是打开力扣刷一道简单题。不需要规划今天要刷几道、要刷哪几道就是“打开网站做今天的每日一题”。这个动作的启动成本低到几乎没有但积累下来非常可观。如果当天状态好我就额外再刷一两道同类题加深今天的主题如果状态一般做完一道就收工绝不硬撑。有人可能觉得这不够“努力”但长期主义恰恰是一场马拉松不是百米冲刺。因为我见过太多人第一天立flag刷十道第二天刷五道第三天打开网站看了一眼关掉了第四天就把这事儿忘了。持续的小步前进远比间歇性的猛冲有效。中午吃完饭我也会花五分钟时间在手机上看一眼题解区的高赞评论学别人的简洁写法。晚上睡前如果当天刷的题里有值得记录的我会把思路和代码存进笔记。这样一整天的碎片时间都被利用起来了又不会觉得负担重。4.2 错题本怎么做才有效我见过不少人刷题刷完一道AC了就过了。下次遇到同类型题目还是要想半天。问题出在哪缺乏沉淀。我的习惯是给每道题打个标签考点、难度、错误次数、易错点。标签可以自己定比如“链表-虚拟头节点”“哈希-计数”“双指针-有序数组”。这样做的目的是把题目从“一道一道”的散点状态归纳成“一类一类”的网状结构。等到复习的时候我不会去重刷所有题而是按标签挑代表性的题目各做一遍。举个小例子我把“两数之和”“三数之和”“四数之和”打上同一个标签“双指针/哈希-数对求和”复习的时候一起看立刻能看出这类题的演进路径——两数之和的哈希解法是怎么变形成三数之和的排序双指针又怎么扩展到四数之和。理解了这个关联比单独背三道题的答案有用得多。另外错题本上一定要写“当初我为什么错”。我发现很多错误其实可以归为几类边界条件没考虑数组为空、只有一个元素、循环条件写错还是、数据溢出或类型不匹配。把这些错误分类之后你会发现自己有一个固定的“臭毛病清单”比如我就特别容易在“数组下标越界”上翻车——知道这一点之后我每次写循环前都会刻意检查边界这个问题后来真的很少再犯。4.3 一个可复制的复习节奏表最后一个实用建议是复习。很多刷题人最大的痛点就是“刷了忘忘了刷刷了再忘”。我的应对方案是给自己定一个简单的复习节奏。刷完一道有价值的题之后当天不复习第1天回顾一下思路第3天不看书自己重新写一遍第7天做一道同类的新题验证迁移能力第14天再翻一次错题本确认没有遗漏。这五个时间点已经把“短期记忆”转成“长期记忆”的关键节点都覆盖住了。如果你觉得时间太紧至少也要保证“第3天重写一遍”和“第7天做同类题”这两步前面提到的那套“算法直觉”主要就是靠这两个动作长出来的。我自己回头去看很多以为自己已经掌握的题就是在第3天重写的时候暴露出了理解上的漏洞。复习的时候还有一个原则尽量不直接看原来的代码而是先口述思路写得出思路再动手写代码。这样能逼你把“看懂答案”升级成“独立推导”。如果发现卡住了就回错题本看错误原因用红笔或者在你的笔记App里用高亮标一遍然后隔天再来一次。4.4 把刷题变成一件“不痛苦”的事情最后聊一点心态层面的东西。我为什么把这个系列叫“勤劳的小蜜蜂”因为蜜蜂采蜜的时候不觉得自己在“坚持”它就是每天飞出去一朵花一朵花地采然后把蜜带回来。刷力扣简单题对我来说也是这么一件事。我不是在完成什么苦难重重的任务而是在每天和这些基础数据结构打打交道维护自己的手感顺便从每道题里抠一点点新东西出来——有时候是一个API的新用法有时候是一个想通了的边界条件有时候是一段比我写得更优雅的代码。这种心态很重要。一旦你觉得刷题是在“受苦”你就很难真正长期做下去一旦你把它当成“每天收一点新东西”它反而会变成一种惯性。我自己坚持这个系列这么久最大的体会就是不要高估某一次猛刷几个小时的收益也不要低估每天认真刷一道题的复利。一年下来三百多道题覆盖常见的考点和套路加上反复复习和错题整理应付面试和日常开发里的算法问题是完全够用的。如果你还没找到自己的节奏我建议你今天就可以开始打开力扣找一道最简单的题别管时间认真把它做透然后把思路写下来。明天再做一道。后天遇到同类型的试着不看答案独立写一遍。一个月后你再回来看大概率会感谢那个从今天开始“每天一点点”的自己。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →