navigation

labuladong 的算法网站

labuladong 的刷题全家桶 由学习网站、《算法秘籍》《刷题笔记》两本 PDF、Chrome/vscode/Jetbrain 三个不同平台的刷题插件组成,致力于为大家提供最丝滑的刷题体验。

本网站是 labuladong 的算法学习网站:

由于我的算法网站有时会遭到恶意攻击,所以本站同时开放两个镜像站点,内容都是一样的,你可以自行选择访问速度较快的站点开始学习。

GitHub Pages Gitee Pages
https://labuladong.github.io/algo/ https://labuladong.gitee.io/algo/

其他的配套工具我会在第一章逐一详细介绍:

手机端可以关注我的公众号查看所有文章,另外《 labuladong 的算法小抄》纸质书已经出版:

本站的实用功能

1️⃣ 我亲自制作了一整套算法学习的工具,致力于让你轻松地、沉浸式地学习算法知识,除了本网站外,还包括两本 PDF 教材Chrome/ vscode/ JetBrains 插件,你可以安装自己喜欢的平台插件刷题学习。

本网站目前可以手把手带你解决 300 多道算法问题,而且在不断更新,全部都是基于力扣的题目,涵盖了所有题型和技巧,我已经把在每篇文章的开头加上了该文章可以解决的题目链接,可以看完文章立即去拿下对应题目。

我还把本站讲解的所有题目整理成了题目列表,建议安装 Chrome 刷题插件 后访问:

力扣版: https://leetcode.cn/problem-list/59jEaTgw/

LeetCode 版: https://leetcode.com/list/9zwo3ww5

4️⃣ 我开发了很多贴心实用的小功能辅助大家学习算法

比如有些较为复杂的代码块中包含小灯泡图标,鼠标移至小灯泡图标上就会弹出图片辅助理解,比如 双指针技巧秒杀七道链表题目 中这段解法的代码:

class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast, slow;
        fast = slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;/**<extend down -200><img src="/algo/images/双指针/3.jpeg"> */
        }
        // 上面的代码类似 hasCycle 函数
        if (fast == null || fast.next == null) {
            // fast 遇到空指针说明没有环
            return null;
        }

        // 重新指向头结点
        slow = head;/**<extend up -100><img src="/algo/images/双指针/2.jpeg"> */
        // 快慢指针同步前进,相交点就是环起点
        while (slow != fast) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}
// 注意:cpp 代码由 chatGPT🤖 根据我的 java 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码还未经过力扣测试,仅供参考,如有疑惑,可以参照我写的 java 代码对比查看。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *fast, *slow;
        fast = slow = head;
        while (fast != nullptr && fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) break;/**<extend down -200><img src="/algo/images/双指针/3.jpeg"> */
        }
        // 上面的代码类似 hasCycle 函数
        if (fast == nullptr || fast->next == nullptr) {
            // fast 遇到空指针说明没有环
            return nullptr;
        }

        // 重新指向头结点
        slow = head;/**<extend up -100><img src="/algo/images/双指针/2.jpeg"> */
        // 快慢指针同步前进,相交点就是环起点
        while (slow != fast) {
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};
# 注意:python 代码由 chatGPT🤖 根据我的 java 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
# 本代码还未经过力扣测试,仅供参考,如有疑惑,可以参照我写的 java 代码对比查看。

class Solution:
    def detectCycle(self, head: ListNode):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        fast, slow = head, head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                break # <extend down -200><img src="/algo/images/双指针/3.jpeg"> #
        
        # 上面的代码类似 hasCycle 函数
        if not fast or not fast.next:
            # fast 遇到空指针说明没有环
            return None
        
        # 重新指向头结点
        slow = head # <extend up -100><img src="/algo/images/双指针/2.jpeg"> #
        # 快慢指针同步前进,相交点就是环起点
        while slow != fast:
            fast = fast.next
            slow = slow.next
        return slow
// 注意:go 代码由 chatGPT🤖 根据我的 java 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码还未经过力扣测试,仅供参考,如有疑惑,可以参照我写的 java 代码对比查看。

func detectCycle(head *ListNode) *ListNode {
    fast, slow := head, head
    for fast != nil && fast.Next != nil {
        fast = fast.Next.Next
        slow = slow.Next
        if fast == slow {
            break/**<extend down -200><img src="/algo/images/双指针/3.jpeg"> */
        }
    }
    if fast == nil || fast.Next == nil {
        return nil
    }
    slow = head/**<extend up -100><img src="/algo/images/双指针/2.jpeg"> */
    for slow != fast {
        fast = fast.Next
        slow = slow.Next
    }
    return slow
}
// 注意:javascript 代码由 chatGPT🤖 根据我的 java 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码还未经过力扣测试,仅供参考,如有疑惑,可以参照我写的 java 代码对比查看。

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    let fast, slow;
    fast = slow = head;
    while (fast !== null && fast.next !== null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) break;/**<extend down -200><img src="/algo/images/双指针/3.jpeg"> */
    }
    // 上面的代码类似 hasCycle 函数
    if (fast === null || fast.next === null) {
        // fast 遇到空指针说明没有环
        return null;
    }

    // 重新指向头结点
    slow = head;/**<extend up -100><img src="/algo/images/双指针/2.jpeg"> */
    // 快慢指针同步前进,相交点就是环起点
    while (slow !== fast) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
};

甚至,我集成了一套算法执行过程的可视化工具,你可以直接在网页上看到算法的执行过程,辅助理解算法逻辑:


🥳 代码可视化动画 🥳

还有,我在每篇文章末尾添加了该文章相关的其他文章及其他算法题,你点击链接就可以跳转过去:

而且结合我的 Chrome 刷题插件,点击相关题目可以直接显示 Chrome 插件中的思路解析,无缝衔接。

这体验够不够丝滑?更牛逼的是,最新版的 PDF 教材、Chrome 插件、vscode 插件、JetBrains 插件与本站同步,全都拥有上述实用功能

还有一个贴心小功能,侧边栏中学完的文章会显示 ✅ 标记,未学完文章会显示 🕒 标记,全部学完的章节会显示 🚩 标记。如果有一天你把本站的所有文章刷完,那么侧边栏将会是一列小红旗 🚩,满满的成就感有没有~

4️⃣ 我的网站和最新版配套刷题插件已支持所有主流编程语言

老读者都知道,以前我的算法教程主要使用 Java 语言。但是现在,我的网站已经支持了 Java/C++/Python/Golang/JavaScript 等所有常用的编程语言(代码块上有 tab 可以切换),能尽可能照顾到更多读者的需求。目前只有最终解法或者比较重要的代码片段才会提供多语言的支持,一些伪码片段或者逻辑比较简单的代码片段一眼就能看懂,就没什么必要提供多语言版本了。

需要注意的是,带有 🟢 标记的是我写的代码(大多是 Java 语言),带有 🤖 标记的其他语言解法代码是利用 chatGPT 生成的,旨在帮助不同背景的读者理解算法逻辑。这些代码经过我手工调试后准确度已经非常高了,不过不排除还会有出错的情况。如遇到错误请大家多多包涵并在 这里 反馈或修复。

为了做到循序渐进,我的文章中的解法代码可能是一部分一部分给出的,所以为了清晰简洁,有些代码片段我会适度变通。比如力扣一般是让你把解法代码写在 Solution 类的一个类方法里,而我经常会省略这个类,直接把解法逻辑写在一个函数中。那么像 Python 的解法,正常来说类方法的第一个参数必须是 self,而我写成普通函数的话就会省略掉这个参数。

所以有时候需要你在我给出的代码上稍作修改,改为符合力扣的提交格式,才能通过力扣的测试用例。当然,我的系列刷题插件中给出的都是完整解法,可以一键复制粘贴(如果你想的话)。

5️⃣ 我在每篇文章下都开放了评论区,欢迎大家友好讨论。点赞是一种态度,如果有的评论帮助到了你,请不要吝惜你的点赞。如果你想评论,请注意遵守基本礼仪,禁止嘲讽、阴阳怪气、刷屏、发学习无关的内容。我会定期查看并回复评论,发现违规者直接拉黑,希望大家共同维护良好的学习氛围。

很多读者留言说我快把那些算法培训班干失业了,这话我可不敢当,大家能从我这学到东西我就很高兴,不差钱的买个 我的课程 捧个钱场,缺钱的把我推荐给身边的朋友捧个人场,都是对我的支持,真心谢谢大家!

关于我

简单介绍一下自己,我应该是 2018 年就开始写算法题解,顺便开通了自己的公众号,诞生了 labuladong 这个昵称,之后就一直在算法领域持续输出,2020 年在 GitHub 开源了算法仓库 fucking-algorithm,没想到火遍了全网,现在这个仓库已经有 100k star 了。

由于这个仓库的火爆,有很多出版社找来寻求出版,我在去年年底出版了纸质书《labuladong 的算法小抄》,销量一直非常稳定,为了感谢公众号读者一直以来的支持,我把公众号的大部分文章放到了网站上,方便大家学习。

众所周知现在技术岗面试中算法题是必考项目,我知道很多人对算法并不是真爱,所以被算法搞得很头痛。

我也是这样过来的,但靠着自己的努力和一些技巧,在毕业秋招斩获了 13 个 offer,你随便说一个大厂,我都拿到了 offer。说这些不是凡尔赛,说实话刷个题面个试真的不算啥,我身边的大佬也比较多,比找工作难的事情多着呢。我想表达的是,其实刷题也好,面试也好,都是有技巧的,我希望把我的技巧和经验分享给大家。如果你有刷算法题的需求觉得算法难,面试笔试难,那么我可以提供一些捷径,避免你走太多弯路。

而且,我奉行的原则是「少即是多」,致力于提高「知识密度」而不是文章数量

举个简单的例子,你可以看到很多算法相关的博客/仓库,动辄有四五百篇文章,每篇文章其实就是一道题的解法代码 + 三言两语的注释甩在那里,你也许能看懂,但很难做到举一反三,真正会做那一类题目。而我会严格限制文章数量的增长,把精力放在提高单篇文章质量上面,我称之为「知识密度」。

随着我自己不断刷题和思考,如果觉得某篇历史文章的知识密度不够高,那么我会毫不犹豫地下架历史文章,重新构思和发布一篇更干,更通用的新文章。所以你可以看到我的 PDF 以及视频课程都带有版本号,就是因为我在不断更新其中的内容,降低理解成本,以便大家能够最容易的学会我想表达的东西。

另外,我自己一直在刷题并输出自己的思考和总结,致力于打造一套完整的算法学习体系

经常有读者跟我反馈,看了我的核心算法框架系列后大呼精妙,瞬间觉得自己已经掌握算法了,但自己做题又有些不知所措了。我认为真正想学会某个技能(比如刷算法题),不可能靠那么一两句话、一两篇文章就能搞定,而应该系统化、体系化地进行训练。

培养大家学习算法的信心和框架思维是我的初心,但话又说回来,你肯定不可能只靠开头那几篇文章就能在题海中大杀四方,而应该在把几篇核心框架思维的文章作为基础,接着按照我的内容安排去阅读相关的文章,这样才能慢慢感悟我想表达的思考方式,真正掌握框架思维,甚至感受到算法之美。

总结来说,不要图快,而要沉下心来,仔细思考,日拱一卒,功不唐捐。

学习算法可能遇到的问题

1、题太多,不知道从哪里开始刷

现在 LeetCode 已经 2000 多道题目了,难道我要全部刷完,才算搞定算法了吗?

我应该怎么刷题?按题号一道道刷?按分类刷?听说动态规划相关的问题经常出现,干脆直接开始刷动态规划问题?

我理解大家这种急切的心情,但是在刷最热门的类型之前,有一些必要的前置知识,如果你不掌握的话,很可能一脸懵逼;但如果按照一定的章法掌握之后,就能找到抓手,成功赋能,轻易地打出一套算法组合拳(手动滑稽)。

2、没有人指导,刷一道题就仅仅是刷了一道题而已,很难举一反三

这是很多朋友都存在的问题,尤其是在刚开始刷题的时候,很难在做完一道题后总结出一些通用的技巧,在遇到新的问题时运用出来,所以很多读者调侃自己「一看都会,一做就废」。

不需要气馁,在刚刚刷题的过程中,积累的算法技巧有限,难以举一反三很正常。但如果有人能够给一些思路上的引导,会让你学习算法的效率事半功倍。

3、被有意无意地灌输错误的思想,以为算法很高大上,非要啃完算法导论才算是入门,所以从心理上就畏惧算法

我对这个问题有切身体会,刚学算法那会儿,我会去搜索过各种资料、经验贴之类的,想快速入门。

现在回头在看,就觉得很有意思,我是说很多写经验贴的作者心态很有意思。

举个例子,很多「大佬」,你问他怎么入门算法,他告诉你看《算法导论》,然后又甩给你一堆英文课程,还强调一定要看英文的哦,中文的翻译不好。

这就好比,你是一个胖子,去问人家怎么减肥,人家告诉你,每次做 100 个俯卧撑,100 个引体向上,一天三次,肯定有效,你看我就是这样练出来的!

呵呵,我要是真能做到这些,还用得着来问你么?不过你仔细琢磨琢磨,给出这种回答是基于一种什么心理?

实际上他根本不在乎你的诉求,他只是想告诉你:我做过这些,我吃过这些苦,我牛逼吧,我厉害吧,你羡慕吧,你做不到吧~

不过话说回来,这种小九九人皆有之,我也不例外,我也时不时想证明自己牛,想证明自己和别人不一样,我以前做分享,也会多少有点故弄玄虚的倾向。

但后来我发现,真正帮助别人解决问题,是更能得到对方的认可的,这也是我的公众号能够很快成长的原因。

所以大家可以放心,我的这份教程是「老少皆宜」,非常接地气的,最能解决学习算法这个问题的。

都说算法难,到底难在哪里

1、技巧较多,难以整活儿

各种算法技巧确实比较繁杂,初学者很容易碰到没见过的技巧,碰到时就大呼牛逼,觉得自己菜。比如说判断一个单链表是否成环的算法,一般人确实很难想到。

这也是为什么我说一开始刷算法题不要死磕,因为技巧储备还不够。

比如现在出一道数组的题目,你的脑海里能浮现出什么算法技巧?最起码应该想到 二分查找快慢指针左右指针滑动窗口前缀和数组差分数组

这些技巧就好比工具库里各种型号的工具,你得有的选,才能运用出来不是吗?那么对于初学者,工具库里面就没什么东西,拿什么死磕?用锯子去挖石头,这不扯淡呢吗?

那么如何解决技巧储备不足的问题呢?这就是我想解决的问题,我把所有常用的算法技巧都集合起来,给你整理出一份「算法小抄」,就可以帮你在做题的过程中有一个方向性的指导。

下次遇到数组相关的题目时,你把我总结的数组相关的技巧都拿出来,一个个试,形成一套固定的做题方法,那遇到新的算法题,不就来者不惧了吗?

2、对于递归的理解不透彻

递归思维是经典的计算机思维,如果掌握不好,很多时候你去看别人的题解代码可能都看不懂,这就很打击人的自信,觉得自己好菜。

不用气馁,递归确实不好理解,因为它是「计算机思维」嘛,你是个人,又不是个计算机,当然不容易理解了。

要掌握递归,关键要跳出细节,培养框架思维,尝试从整体上理解算法的过程。

以我的刷题经验,初学者最好从「树」相关的问题开始刷题,为此我特意写了 手把手刷二叉树(纲领篇),帮你从二叉树的视角理解各种复杂的算法。我的 二叉树递归专题课 会手把手带你通过二叉树这一种数据结构,洞悉动态规划/回溯算法等高级算法的本质,帮助大家培养框架思维。

别不信,到后面你会发现递归代码反而是最简单,最容易理解的。

你能在这里学到什么

我的读者可以大致分两类:一类对算法完全没有兴趣,属于面向笔试学习算法的读者;另一类是对算法感兴趣,能够享受纯粹求知乐趣的读者。我个人属于后者,自然希望后者多一些,但毕竟人各有志,两种读者没有对错之分。而且人总是会变的,说不定学着学着就改变想法了呢?这也未可知。

但我的文章能够同时满足这两者的需求:

首先,我会结合自己的刷题经验,有所取舍,忽略性价比不高的算法技巧,抽象出常用算法的框架,帮助大家高效掌握算法,搞定面试笔试。另外,我也会清晰地描述自己的解题过程,阐述算法的底层原理,帮助大家培养框架思维以及举一反三的能力,力求让大家真正爱上算法,每天不刷两道题都难受的那种,已经有不少读者在评论区说刷题跟打游戏似的,上起瘾了。所以,不管你是刚刚入门算法的小白,还是已经对算法有所小成,希望进阶,我相信这些内容都会对你有帮助。

另外,经常有读者问,我的教程应该按照什么顺序学习

最好的情况当然是按照我编排的顺序阅读所有文章,已经有几十个读者私下跟我说靠我的公众号零基础入门算法,最后进到了谷歌,他们都是把我的历史文章看完了,有的部分还看了好几遍。当然,对于基础已经比较好的读者,可以选自己感兴趣的模块针对性学习。

我只强调两点:

1、各种算法像是精美的拼图,而数据结构是组成这些拼图的模块,所以学习算法之前你必须对常用的数据结构有基本的认识,否则你看本站的内容会比较吃力。这里自卖自夸一下,推荐我的 数据结构精品课,以视频课为主,手把手带你实现常用的数据结构及相关算法,旨在帮助算法基础较为薄弱的读者深入理解常用数据结构的底层原理,在算法学习中少走弯路。

2、常用数据结构相关的算法,主要是数组、链表相关的算法,比如 双指针算法滑动窗口算法 等,不需要什么前置知识,技巧虽然精妙,但你理解起来也不会很困难。但稍微进阶一些的算法技巧,主要是递归相关的算法,比如 回溯算法动态规划,或者高级数据结构相关算法 Dijkstra 算法字典树算法 等,你不要上来就学它们,否则很容易劝退。这些递归算法需要二叉树算法作为铺垫,你应该先学习我的二叉树专题文章,再去学习这些高级算法,就能融会贯通了

最后,公众号后台回复「进群」可加入算法群,回复「全家桶」下载 PDF 和配套插件:

共同维护高质量学习环境,评论礼仪见这里,违者直接拉黑不解释