经验复盘-力扣刷题给我带来了什么?

1,801 阅读5分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-经验复盘,点击查看 征文活动详情

起因

虽然在去年跳槽前有刷过一段时间的力扣,但是一直都没有将自己的刷题的过程记录下来。直到2021年5月,掘金举办了一个Java 刷题打卡的活动,我才开始将自己的第一篇刷题文章发布在掘金👇

image.png

后面掘金又举办了2021年6月更文挑战活动,活动大奖要求有 30 篇文章,最终这次活动中我输出了 23 篇力扣刷题的文章。

至今为止,我在掘金上已发表了 124 个力扣题解,有兴趣的话可以关注我的专栏:力扣刷题

image.png

我是怎么做的?

1. 代码存储

为了更方便的调试代码以及存储自己写的测试用例,所以我在Github上建了一个私人库用于存储代码。这样做的好处就是:随时随地获取你的历史代码、而且可以保留自己写的测试用例。我非常推荐你也使用仓库来存储自己的解题代码!

image.png

2.模仿和总结

刚开始刷题的时候我只会简单的暴力求解,所以在提交第一题的时候击败率连 10% 都不到,后来通过看官方题解发现了可以使用 HashMap 用空间换取时间(因 HashMap 中链表节点超过8时会转为红黑树)。

所以在刚开始很长一段时间我都是 题目看完 -> 思考15分钟,没有思路 -> 看官方题解,但经过了一段时间后我发现自己的提升很小。就像是一种脑子会了,手没会的感觉,下次碰到类似的题目还是不会做。

后来经过不断的摸索,我刷题的步骤大概是这样的:

  1. 看题目,列出题目中的重点
  2. 想清楚解题的关键和思路(碰到链表或二叉树相关的题目,我一般都会在草稿纸上画一下)
  3. 实在不会的可以看一下官方题解,但是不抄官方的代码
  4. 运行调试:写好代码后,将力扣的测试用例写在main方法中测试(像一些二叉树的题目就需要根据官方的二叉树数组自己转为 TreeNode 了)
  5. 提交代码,然后再在本地调试不通过的测试用例(一般来说需要调试二十分钟左右,不要着急!)
  6. 直到通过所有的测试用例

经过这段时间的刷题,我也有了一些小感悟:

  • 算法的本质就是利用空间换区时间
  • 有时候看到数据结构就能猜到用什么算法:例如碰到 二叉树 就会想到 递归、碰到 截取字符串 会想到 滑动窗口、碰到大问题可以分解的情况会想到 动态规划
  • 使用 HashMap 替代数组或链表,可有效降低时间复杂度
  • 遍历链表时一般都需要临时指针指向头结点
  • ......

我收获了什么?

虽然刷题碰到的一些算法在实际业务中无法很好落地,但是刷题能够对数据结构有更深刻的理解调试能力的提升(先将步骤分解,然后寻找非预期的输入或输出)、锻炼思维能力、以及对代码执行的效率更敏感了(减少时间复杂度)。

实际项目如何运用?

下面我举得这个例子就可以很好的体现:递归算法的运用和提高执行效率。

在常见的业务系统中,不可避免的会需要动态菜单和权限的功能。动态菜单本质上是一个 ,而某个菜单路由下面的权限就像是树中的 叶子节点。但是数据库并不能够存储树这样的结构,那怎么办呢?

常规的树形结构的路由菜单如下所示:

image.png

设计的数据库表如下所示:

parentId:表示父节点的Id  
menuType:当类型为 C 标识为路由下的权限,即二叉树中的叶子节点

image.png

实际业务需要将查找出来的列表转为树形结构返回给前端,这怎么实现呢?大致的思路如下所示:

    /**
     * list转为树结构
     */
    private List<MenuBO> list2Tree(List<MenuBO> list, Integer pId) {
        List<MenuBO> tree = new ArrayList<>();
        Iterator<MenuBO> it = list.iterator();
        while (it.hasNext()) {
            MenuBO m = it.next();
            if (m.getParentId() == pId) {
                tree.add(m);
                // 已添加的元素删除掉
                it.remove();
            }
        }
        // 寻找子元素
        tree.forEach(n -> n.setChildren(list2Tree(list, n.getId())));
        return tree;
    }

这段代码的注释比较清楚,就是找到原列表中所有 pid 相同的元素放入新列表,并将新列表设为孩子节点,直到所有节点都遍历完成。又因为一行记录只会成为树中的一个节点,故每个元素只需遍历一次。所以再将元素放入到新列表后,就在原列表中将此元素移除。算法复杂度从 O(N*logN) 变为了 0(N),大大提升了执行效率。

总结

去年的这个时候我是零基础开始刷题的,刚开始写一个中等难度的题目需要一两个小时,再加上写完题目后还要输出一篇文章,一般都要搞到凌晨左右。但现在我简单题已经可以重拳出击了,中等难度的题目解题的时间更少了,至于困难题还是需要看题解。

从我一年多刷题的经历来看,我总结出来两句话:

  1. 万事开头难
  2. 实践才是检验真理的唯一标准!

所以行动起来吧!就现在!

最后我想感谢一下稀土掘金!来这里不仅能刷沸点段子,还能写文章拿奖品。不得不说,掘金的活动实在是太多了(PS:奖品也很多)!