From fb1992138bf504f94cda7f11c4e4fbffd128e1fe Mon Sep 17 00:00:00 2001 From: robot Date: Sun, 10 Mar 2024 21:13:15 +0800 Subject: [PATCH] Site updated: 2024-03-10 21:13:13 --- 2009/01/01/oschina/index.html | 4 +- 2010/03/04/ad/index.html | 4 +- 2019/06/13/laoshushidu/index.html | 4 +- 2019/09/04/bloom-filter/index.html | 4 +- 2019/09/05/daily-featured-2019-09/index.html | 4 +- 2019/09/18/algorthimn-fe-1/index.html | 4 +- 2019/09/19/algorthimn-fe-2/index.html | 4 +- 2019/09/20/algorthimn-fe-3/index.html | 4 +- 2019/09/21/algorthimn-fe-4/index.html | 4 +- 2019/10/03/draft/index.html | 4 +- 2019/10/10/daily-featured-2019-10/index.html | 4 +- 2019/11/04/daily-featured-2019-11/index.html | 4 +- 2019/12/02/daily-featured-2019-12/index.html | 4 +- 2019/12/11/event-loop/index.html | 4 +- 2020/01/02/daily-featured-2020-01/index.html | 4 +- 2020/01/03/basic-data-structure/index.html | 6 +- .../index.html | 8 +- .../index.html | 6 +- .../1310.xor-queries-of-a-subarray/index.html | 4 +- .../index.html | 4 +- .../index.html | 6 +- 2020/01/20/ppt-data/index.html | 4 +- 2020/01/20/reverseList/index.html | 6 +- .../index.html" | 4 +- 2020/01/29/50.powx-n/index.html | 6 +- 2020/01/31/335.self-crossing/index.html | 6 +- 2020/02/02/bigpipe-and-micro-fe/index.html | 4 +- .../02/02/how-am-I-conque-leetcode/index.html | 6 +- .../index.html" | 4 +- .../index.html | 4 +- .../index.html" | 4 +- .../index.html | 6 +- 2020/02/20/rotate-list/index.html | 6 +- 2020/02/20/to-author/index.html | 4 +- 2020/02/20/union-find/index.html | 4 +- .../22/web-components-enterprize/index.html | 4 +- 2020/03/02/daily-featured-2020-03/index.html | 4 +- .../index.html | 4 +- 2020/03/04/85.maximal-rectangle/index.html | 4 +- 2020/03/16/slide-window/index.html | 4 +- 2020/03/20/serialize/index.html | 4 +- 2020/03/20/thanksGaving-2/index.html | 4 +- 2020/03/23/91-algo/index.html | 4 +- 2020/03/24/bit/index.html | 4 +- 2020/03/25/91-algo2/index.html | 4 +- 2020/04/06/82-jinzhiying/index.html | 4 +- 2020/04/06/movie-eager-game/index.html | 4 +- 2020/04/12/canvas-filter/index.html | 4 +- 2020/04/13/interview-log-tqp/index.html | 4 +- 2020/04/13/leetcode-greedy/index.html | 4 +- 2020/04/14/thanksGiving3/index.html | 4 +- 2020/05/01/how-am-I-learn-fe/index.html | 4 +- .../index.html | 4 +- 2020/05/05/why-no-magic-string/index.html | 4 +- 2020/05/16/343.integer-break/index.html | 6 +- .../26/91algo-basic-05.two-pointer/index.html | 4 +- 2020/05/30/91algo-05-30/index.html | 4 +- 2020/05/31/101.symmetric-tree/index.html | 6 +- 2020/06/01/312.burst-balloons/index.html | 6 +- 2020/06/02/hacker-drawer/index.html | 4 +- 2020/06/03/over-fancy01/index.html | 4 +- .../874.walking-robot-simulation/index.html | 4 +- 2020/06/04/algo-chrome-extension/index.html | 4 +- 2020/06/08/887.super-egg-drop/index.html | 4 +- 2020/06/11/91algo-4-basic/index.html | 10 +- 2020/06/13/immutable-js/index.html | 4 +- 2020/06/13/leetcode-1186/index.html | 10 +- 2020/06/13/leetcode-island/index.html | 20 +- 2020/06/13/leetcode-median/index.html | 18 +- 2020/06/13/leetcode-pp/index.html | 16 +- 2020/06/16/ts-generics/index.html | 4 +- 2020/06/20/611.triangle/index.html | 14 +- 2020/06/20/LIS/index.html | 16 +- 2020/06/20/LSS/index.html | 18 +- 2020/06/20/interview-log-haoweilai/index.html | 10 +- 2020/06/20/mac-setup-for-fe/index.html | 4 +- 2020/06/20/rfc-monitor/index.html | 4 +- .../index.html" | 4 +- 2020/07/01/LCS/index.html | 6 +- .../index.html | 6 +- 2020/07/21/balanced-tree/index.html | 6 +- 2020/08/03/mother-01/index.html | 4 +- 2020/08/04/ts-internal/index.html | 4 +- 2020/08/11/webkit-intro/index.html | 4 +- 2020/08/13/leetcode-interview-ts/index.html | 4 +- 2020/08/15/ts-type-system/index.html | 4 +- 2020/08/16/leetcode-cheat/index.html | 4 +- 2020/08/20/tech-conf/index.html | 4 +- 2020/08/21/ts-type/index.html | 4 +- 2020/08/24/ts-config/index.html | 4 +- 2020/08/27/91algo-dp-lecture/index.html | 6 +- .../887.super-egg-drop-extension/index.html | 8 +- 2020/09/06/byte-dance-algo-ex-2017/index.html | 18 +- 2020/09/06/byte-dance-algo-ex/index.html | 18 +- 2020/09/27/atMostK/index.html | 4 +- 2020/09/27/ts-exercises/index.html | 8 +- 2020/10/02/error-catch/index.html | 4 +- 2020/10/07/mock-interview/index.html | 4 +- 2020/10/13/ts-exercises-2/index.html | 4 +- 2020/10/19/91-algo-2/index.html | 4 +- 2020/11/02/fe-test-best-practice/index.html | 4 +- 2020/11/03/monotone-stack/index.html | 4 +- 2020/11/08/linked-list/index.html | 4 +- 2020/11/13/iterm-window-hotkey/index.html | 4 +- 2020/11/15/browser-event/index.html | 4 +- 2020/11/23/tree/index.html | 4 +- 2020/11/29/shuati-silu/index.html | 4 +- 2020/12/02/91-tougao-tianxing-01/index.html | 4 +- 2020/12/04/91-tougao-xiaoyang-01/index.html | 4 +- 2020/12/12/shuati-silu2/index.html | 4 +- 2020/12/13/leetcode-ebook-1/index.html | 4 +- 2020/12/16/leetcode-app-1/index.html | 4 +- 2020/12/21/shuati-silu3/index.html | 4 +- 2020/12/26/heap/index.html | 4 +- 2021/01/12/reservoid-sampling/index.html | 4 +- 2021/01/19/91-algo-3/index.html | 4 +- 2021/01/19/heap-2/index.html | 4 +- 2021/02/14/deploy-button/index.html | 4 +- 2021/02/20/2d-pre/index.html | 6 +- 2021/02/20/youtube-extesion/index.html | 4 +- .../index.html" | 4 +- 2021/02/24/gcd/index.html | 6 +- 2021/02/27/github-blog-auto/index.html | 6 +- 2021/03/01/91meeting-season-3-1/index.html | 4 +- 2021/03/04/school-01/index.html | 4 +- 2021/03/08/binary-search-1/index.html | 4 +- 2021/03/11/school-02/index.html | 4 +- 2021/03/16/leetcode-cheat-update-1/index.html | 4 +- 2021/03/23/binary-search-2/index.html | 4 +- 2021/03/28/school-03/index.html | 4 +- 2021/04/03/interview-road/index.html | 4 +- 2021/04/06/out-of-science/index.html | 4 +- 2021/04/20/dp/index.html | 4 +- 2021/04/28/ebook-2.0/index.html | 4 +- 2021/05/02/91algo-4/index.html | 4 +- 2021/05/10/91-student-1/index.html | 4 +- 2021/05/16/github-reader/index.html | 4 +- 2021/05/18/dp-bottom-up/index.html | 4 +- 2021/05/24/interview-fe-bi/index.html | 4 +- 2021/06/02/search/index.html | 4 +- 2021/06/18/quit-esign/index.html | 4 +- 2021/06/23/moyu-1/index.html | 4 +- 2021/07/03/mono-vite/index.html | 4 +- 2021/07/05/ttw/index.html | 4 +- .../07/27/91algo-interview-cabbage/index.html | 4 +- 2021/07/28/vscode-brower-debug/index.html | 4 +- 2021/07/29/91algo-interview-yixiao/index.html | 4 +- 2021/08/06/ydk-leetcode/index.html | 4 +- 2021/08/17/ydk-leetcode-2/index.html | 4 +- 2021/08/21/91algo-5/index.html | 4 +- 2021/09/02/vscode-dev-codespaces/index.html | 4 +- 2021/09/02/zaozaoliao/index.html | 4 +- 2021/09/04/leetcode-solution-book/index.html | 4 +- 2021/09/04/wfh/index.html | 4 +- .../09/24/91algo-interview-kongshi/index.html | 4 +- 2021/09/26/algo-vis/index.html | 4 +- 2021/10/05/copilot/index.html | 4 +- 2021/10/10/programming-idioms/index.html | 4 +- 2021/10/16/algo-fakers/index.html | 4 +- 2021/10/24/new-book/index.html | 4 +- 2021/11/09/grapth/index.html | 4 +- 2021/11/10/chrome-recorder/index.html | 4 +- 2021/11/17/cses/index.html | 4 +- 2021/11/20/leetcode-book.intro/index.html | 4 +- 2021/11/28/qiuzhao2021/index.html | 4 +- 2021/12/03/91algo-6/index.html | 4 +- 2021/12/13/tencent-2021/index.html | 4 +- 2021/12/16/segment-tree/index.html | 4 +- .../22/leetcode-cheat-hide-cases/index.html | 4 +- 2022/01/04/non-tech-skill/index.html | 4 +- 2022/01/27/backward/index.html | 4 +- 2022/02/06/cors-extension/index.html | 4 +- 2022/02/11/git-bisect-bug/index.html | 4 +- 2022/02/20/wfh2/index.html | 4 +- 2022/03/04/zhandui/index.html | 4 +- 2022/03/12/91algo-7/index.html | 4 +- 2022/03/21/interviewer/index.html | 4 +- 2022/04/06/git-merge-base/index.html | 4 +- 2022/04/26/books-2022/index.html | 4 +- 2022/05/15/daily-featured-2022-04/index.html | 4 +- 2022/05/21/google-interview-tao/index.html | 4 +- 2022/07/09/91algo-8/index.html | 4 +- 2022/10/15/91algo-9/index.html | 4 +- 2023/01/01/goutou-2/index.html | 4 +- 2023/01/02/how-leetcode/index.html | 4 +- 2023/01/18/2022/index.html | 4 +- 2023/01/19/leetcode-rating/index.html | 4 +- 2023/02/01/91algo-10/index.html | 4 +- 2023/03/27/migrate-to-webpack5_swc/index.html | 4 +- 2023/06/04/91algo-11/index.html | 4 +- 2023/08/08/remove-useless-css/index.html | 4 +- 2023/08/27/how-to-make-automator/index.html | 4 +- 2023/11/02/91algo-12/index.html | 4 +- 2024/01/28/error-boundaries/index.html | 13 +- 2024/03/07/kuma/index.html | 32 +- 2024/{02/15 => 03/10}/make-money/index.html | 66 +- 404.html | 4 +- about/index.html | 4 +- archives/2009/01/index.html | 4 +- archives/2009/index.html | 4 +- archives/2010/03/index.html | 4 +- archives/2010/index.html | 4 +- archives/2019/06/index.html | 4 +- archives/2019/09/index.html | 4 +- archives/2019/10/index.html | 4 +- archives/2019/11/index.html | 4 +- archives/2019/12/index.html | 4 +- archives/2019/index.html | 4 +- archives/2019/page/2/index.html | 4 +- archives/2020/01/index.html | 18 +- archives/2020/01/page/2/index.html | 4 +- archives/2020/02/index.html | 10 +- archives/2020/03/index.html | 4 +- archives/2020/04/index.html | 4 +- archives/2020/05/index.html | 8 +- archives/2020/06/index.html | 62 +- archives/2020/06/page/2/index.html | 56 +- archives/2020/07/index.html | 8 +- archives/2020/08/index.html | 6 +- archives/2020/08/page/2/index.html | 4 +- archives/2020/09/index.html | 38 +- archives/2020/10/index.html | 4 +- archives/2020/11/index.html | 4 +- archives/2020/12/index.html | 4 +- archives/2020/index.html | 4 +- archives/2020/page/10/index.html | 14 +- archives/2020/page/2/index.html | 4 +- archives/2020/page/3/index.html | 40 +- archives/2020/page/4/index.html | 66 +- archives/2020/page/5/index.html | 54 +- archives/2020/page/6/index.html | 10 +- archives/2020/page/7/index.html | 4 +- archives/2020/page/8/index.html | 6 +- archives/2020/page/9/index.html | 12 +- archives/2021/01/index.html | 4 +- archives/2021/02/index.html | 8 +- archives/2021/03/index.html | 4 +- archives/2021/04/index.html | 4 +- archives/2021/05/index.html | 4 +- archives/2021/06/index.html | 4 +- archives/2021/07/index.html | 4 +- archives/2021/08/index.html | 4 +- archives/2021/09/index.html | 4 +- archives/2021/10/index.html | 4 +- archives/2021/11/index.html | 4 +- archives/2021/12/index.html | 4 +- archives/2021/index.html | 4 +- archives/2021/page/2/index.html | 4 +- archives/2021/page/3/index.html | 4 +- archives/2021/page/4/index.html | 4 +- archives/2021/page/5/index.html | 8 +- archives/2021/page/6/index.html | 4 +- archives/2022/01/index.html | 4 +- archives/2022/02/index.html | 4 +- archives/2022/03/index.html | 4 +- archives/2022/04/index.html | 4 +- archives/2022/05/index.html | 4 +- archives/2022/07/index.html | 4 +- archives/2022/10/index.html | 4 +- archives/2022/index.html | 4 +- archives/2022/page/2/index.html | 4 +- archives/2023/01/index.html | 4 +- archives/2023/02/index.html | 4 +- archives/2023/03/index.html | 4 +- archives/2023/06/index.html | 4 +- archives/2023/08/index.html | 4 +- archives/2023/11/index.html | 4 +- archives/2023/index.html | 4 +- archives/2024/01/index.html | 4 +- archives/2024/03/index.html | 131 +- archives/2024/index.html | 58 +- archives/index.html | 40 +- archives/page/10/index.html | 40 +- archives/page/11/index.html | 40 +- archives/page/12/index.html | 40 +- archives/page/13/index.html | 40 +- archives/page/14/index.html | 40 +- archives/page/15/index.html | 40 +- archives/page/16/index.html | 40 +- archives/page/17/index.html | 40 +- archives/page/18/index.html | 40 +- archives/page/19/index.html | 40 +- archives/page/2/index.html | 40 +- archives/page/20/index.html | 40 +- archives/page/3/index.html | 40 +- archives/page/4/index.html | 40 +- archives/page/5/index.html | 40 +- archives/page/6/index.html | 40 +- archives/page/7/index.html | 40 +- archives/page/8/index.html | 40 +- archives/page/9/index.html | 40 +- atom.xml | 44 +- .../index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 4 +- categories/CD/index.html | 4 +- categories/Easy/index.html | 6 +- categories/Github/index.html | 4 +- categories/Hard/index.html | 6 +- .../index.html" | 4 +- categories/LeetCode/index.html | 4 +- categories/LeetCode/page/2/index.html | 60 +- categories/LeetCode/page/3/index.html | 8 +- .../index.html" | 4 +- .../index.html" | 4 +- categories/Medium/index.html | 10 +- categories/React/index.html | 4 +- categories/TypeScript/index.html | 4 +- categories/devtool/index.html | 4 +- categories/index.html | 11 +- categories/vite/index.html | 4 +- .../index.html" | 4 +- .../\344\270\255\347\255\211/index.html" | 4 +- "categories/\344\271\246/index.html" | 4 +- .../\347\256\227\346\263\225/index.html" | 4 +- .../\344\271\246\345\215\225/index.html" | 4 +- .../\344\271\246\346\221\230/index.html" | 4 +- .../\344\272\214\345\210\206/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../TypeScript/index.html" | 4 +- .../\346\263\233\345\236\213/index.html" | 4 +- .../css-in-js/index.html" | 4 +- .../eslint/index.html" | 4 +- .../\345\211\215\347\253\257/index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 4 +- .../web-component/index.html" | 4 +- .../webkit/index.html" | 4 +- .../\346\265\213\350\257\225/index.html" | 4 +- .../index.html" | 4 +- .../\347\256\227\346\263\225/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- "categories/\345\233\276/index.html" | 4 +- "categories/\345\240\206/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 6 +- .../Chrome/index.html" | 4 +- .../VSCODE/index.html" | 4 +- .../\345\267\245\345\205\267/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../D2/index.html" | 4 +- .../Google-IO/index.html" | 4 +- .../JSConf/index.html" | 4 +- .../QCon/index.html" | 4 +- .../React-Conf/index.html" | 4 +- .../index.html" | 4 +- .../\346\217\222\344\273\266/index.html" | 4 +- .../index.html" | 4 +- .../\347\256\227\346\263\225/index.html" | 4 +- .../\346\220\234\347\264\242/index.html" | 4 +- .../\346\225\260\345\255\246/index.html" | 8 +- .../\347\256\227\346\263\225/index.html" | 8 +- .../hashtable/index.html" | 16 +- .../index.html" | 50 +- .../page/2/index.html" | 20 +- .../page/3/index.html" | 8 +- .../index.html" | 4 +- .../\345\233\276/index.html" | 4 +- .../index.html" | 8 +- .../index.html" | 4 +- .../\346\225\260\347\273\204/index.html" | 52 +- .../\347\256\227\346\263\225/index.html" | 4 +- .../\351\223\276\350\241\250/index.html" | 8 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../\346\227\245\350\256\260/index.html" | 4 +- .../\346\212\200\346\234\257/index.html" | 4 +- .../\346\230\245\346\213\233/index.html" | 4 +- "categories/\346\240\210/index.html" | 4 +- "categories/\346\240\221/index.html" | 4 +- .../\346\240\241\346\213\233/index.html" | 4 +- .../index.html" | 4 +- .../2019-09/index.html" | 4 +- .../2019-10/index.html" | 4 +- .../2019-11/index.html" | 4 +- .../2019-12/index.html" | 4 +- .../2020-01/index.html" | 4 +- .../2020-03/index.html" | 4 +- .../2022-04/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../webkit/index.html" | 4 +- .../\344\272\213\344\273\266/index.html" | 4 +- .../\347\220\206\350\264\242/index.html" | 1398 +++++++++++++++++ .../\347\224\265\345\275\261/index.html" | 4 +- .../index.html" | 4 +- .../\347\231\276\345\272\246/index.html" | 4 +- .../\347\256\227\346\263\225/BFS/index.html" | 12 +- .../\347\256\227\346\263\225/DFS/index.html" | 6 +- .../\347\256\227\346\263\225/index.html" | 40 +- .../page/2/index.html" | 74 +- .../page/3/index.html" | 22 +- .../index.html" | 8 +- .../index.html" | 6 +- .../\345\211\215\347\253\257/index.html" | 4 +- .../\351\235\242\350\257\225/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 18 +- .../index.html" | 8 +- .../\345\233\236\346\226\207/index.html" | 6 +- .../\345\233\236\346\272\257/index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 4 +- .../index.html" | 6 +- .../\346\225\260\345\255\246/index.html" | 12 +- .../\346\246\202\347\216\207/index.html" | 10 +- .../\346\257\215\351\242\230/index.html" | 4 +- .../index.html" | 38 +- .../index.html" | 4 +- .../index.html" | 6 +- .../\351\200\222\345\275\222/index.html" | 10 +- .../index.html" | 6 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 6 +- .../index.html" | 4 +- .../index.html" | 4 +- .../\350\231\276\347\232\256/index.html" | 4 +- .../index.html" | 4 +- .../\350\260\267\346\255\214/index.html" | 4 +- .../index.html" | 4 +- .../\351\235\242\350\257\225/index.html" | 4 +- .../\350\264\252\345\251\252/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../\351\223\276\350\241\250/index.html" | 4 +- .../\351\235\242\347\273\217/index.html" | 4 +- .../\351\235\242\350\257\225/index.html" | 38 +- .../index.html" | 38 +- content.json | 2 +- index.html | 62 +- mylist/index.html | 4 +- page/10/index.html | 4 +- page/11/index.html | 40 +- page/12/index.html | 39 +- page/13/index.html | 58 +- page/14/index.html | 31 +- page/15/index.html | 6 +- page/16/index.html | 4 +- page/17/index.html | 10 +- page/18/index.html | 18 +- page/19/index.html | 4 +- page/2/index.html | 4 +- page/20/index.html | 4 +- page/3/index.html | 4 +- page/4/index.html | 4 +- page/5/index.html | 4 +- page/6/index.html | 4 +- page/7/index.html | 4 +- page/8/index.html | 8 +- page/9/index.html | 4 +- search.xml | 220 +-- tags/2022/index.html | 4 +- .../index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 4 +- tags/AI/index.html | 4 +- tags/AST/index.html | 4 +- tags/BFS/index.html | 8 +- tags/BigPipe/index.html | 4 +- tags/CD/index.html | 4 +- tags/CSS/index.html | 4 +- tags/Canvas/index.html | 4 +- tags/Chrome/index.html | 4 +- tags/D2/index.html | 4 +- tags/DFS/index.html | 4 +- tags/Easy/index.html | 6 +- tags/Floyd-Warshall/index.html | 4 +- tags/GitHub/index.html | 4 +- tags/Google-IO/index.html | 4 +- tags/Hard/index.html | 6 +- tags/JSConf/index.html | 4 +- tags/LeetCode/index.html | 4 +- tags/LeetCode/page/2/index.html | 4 +- tags/LeetCode/page/3/index.html | 58 +- tags/LeetCode/page/4/index.html | 8 +- .../index.html" | 26 +- tags/Mac/index.html | 4 +- tags/Medium/index.html | 10 +- tags/PPT/index.html | 4 +- tags/QCon/index.html | 4 +- tags/RFC/index.html | 4 +- tags/React/index.html | 4 +- tags/TypeScript/index.html | 4 +- tags/VSCODE/index.html | 4 +- tags/css-in-js/index.html | 4 +- tags/eslint/index.html | 4 +- tags/immutable/index.html | 4 +- tags/immutablejs/index.html | 4 +- tags/index.html | 13 +- "tags/k-\351\227\256\351\242\230/index.html" | 4 +- tags/swc/index.html | 4 +- tags/vite/index.html | 4 +- tags/vue/index.html | 4 +- tags/web-component/index.html | 4 +- tags/webkit/index.html | 4 +- tags/webpack/index.html | 4 +- .../index.html" | 4 +- .../index.html" | 8 +- "tags/\344\270\255\347\255\211/index.html" | 4 +- "tags/\344\271\246/index.html" | 4 +- "tags/\344\271\246\345\215\225/index.html" | 4 +- "tags/\344\271\246\346\221\230/index.html" | 4 +- "tags/\344\272\213\344\273\266/index.html" | 4 +- .../index.html" | 4 +- "tags/\344\272\214\345\210\206/index.html" | 4 +- .../index.html" | 8 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- "tags/\345\210\267\351\242\230/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- "tags/\345\211\215\347\253\257/index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 4 +- .../index.html" | 12 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- "tags/\345\233\236\346\272\257/index.html" | 4 +- "tags/\345\233\260\351\232\276/index.html" | 4 +- "tags/\345\233\276/index.html" | 4 +- .../index.html" | 4 +- "tags/\345\240\206/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 6 +- .../index.html" | 40 +- .../index.html" | 6 +- "tags/\345\267\245\345\205\267/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 6 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- "tags/\346\212\200\350\203\275/index.html" | 4 +- .../index.html" | 4 +- "tags/\346\217\222\344\273\266/index.html" | 4 +- "tags/\346\220\234\347\264\242/index.html" | 4 +- "tags/\346\225\210\347\216\207/index.html" | 4 +- "tags/\346\225\260\345\255\246/index.html" | 22 +- .../index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 42 +- .../page/4/index.html" | 66 +- .../page/5/index.html" | 10 +- .../page/6/index.html" | 18 +- .../page/7/index.html" | 10 +- .../page/8/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- "tags/\346\225\260\347\273\204/index.html" | 8 +- "tags/\346\227\245\350\256\260/index.html" | 4 +- "tags/\346\230\245\346\213\233/index.html" | 4 +- .../index.html" | 8 +- .../index.html" | 4 +- .../index.html" | 4 +- "tags/\346\240\210/index.html" | 4 +- "tags/\346\240\221/index.html" | 4 +- "tags/\346\240\241\346\213\233/index.html" | 4 +- "tags/\346\246\202\347\216\207/index.html" | 10 +- "tags/\346\250\241\345\235\227/index.html" | 4 +- .../index.html" | 4 +- "tags/\346\257\215\351\242\230/index.html" | 4 +- .../index.html" | 4 +- "tags/\346\262\237\351\200\232/index.html" | 4 +- "tags/\346\263\233\345\236\213/index.html" | 4 +- "tags/\346\265\213\350\257\225/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 38 +- "tags/\346\273\244\351\225\234/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../\347\220\206\350\264\242/index.html" | 51 +- .../index.html" | 4 +- "tags/\347\224\265\345\275\261/index.html" | 4 +- "tags/\347\231\276\345\272\246/index.html" | 4 +- "tags/\347\233\221\346\216\247/index.html" | 4 +- "tags/\347\247\213\346\213\233/index.html" | 4 +- "tags/\347\256\227\346\263\225/index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 8 +- .../page/4/index.html" | 48 +- .../page/5/index.html" | 60 +- .../page/6/index.html" | 10 +- .../page/7/index.html" | 20 +- .../page/8/index.html" | 4 +- .../index.html" | 4 +- .../page/2/index.html" | 4 +- .../page/3/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 6 +- "tags/\350\215\211\347\250\277/index.html" | 4 +- .../index.html" | 4 +- "tags/\350\231\276\347\232\256/index.html" | 4 +- "tags/\350\243\205\346\234\272/index.html" | 4 +- .../index.html" | 4 +- "tags/\350\260\267\346\255\214/index.html" | 4 +- "tags/\350\264\252\345\251\252/index.html" | 4 +- "tags/\350\264\252\345\277\203/index.html" | 4 +- .../index.html" | 4 +- .../index.html" | 4 +- "tags/\351\200\222\345\275\222/index.html" | 10 +- "tags/\351\223\276\350\241\250/index.html" | 8 +- .../index.html" | 4 +- "tags/\351\235\242\347\273\217/index.html" | 4 +- "tags/\351\235\242\350\257\225/index.html" | 38 +- 644 files changed, 4985 insertions(+), 2094 deletions(-) rename 2024/{02/15 => 03/10}/make-money/index.html (91%) create mode 100644 "categories/\347\220\206\350\264\242/index.html" rename archives/2024/02/index.html => "tags/\347\220\206\350\264\242/index.html" (94%) diff --git a/2009/01/01/oschina/index.html b/2009/01/01/oschina/index.html index 3b38014a0c..d903c02354 100644 --- a/2009/01/01/oschina/index.html +++ b/2009/01/01/oschina/index.html @@ -934,6 +934,8 @@

 评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1082,7 +1084,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2010/03/04/ad/index.html b/2010/03/04/ad/index.html index 5b0d439f32..cf541c48d5 100644 --- a/2010/03/04/ad/index.html +++ b/2010/03/04/ad/index.html @@ -991,6 +991,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1139,7 +1141,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/06/13/laoshushidu/index.html b/2019/06/13/laoshushidu/index.html index 146935ddcc..c8d7366b35 100644 --- a/2019/06/13/laoshushidu/index.html +++ b/2019/06/13/laoshushidu/index.html @@ -1000,6 +1000,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1148,7 +1150,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/09/04/bloom-filter/index.html b/2019/09/04/bloom-filter/index.html index 9c1c25d40f..c865272496 100644 --- a/2019/09/04/bloom-filter/index.html +++ b/2019/09/04/bloom-filter/index.html @@ -1011,6 +1011,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1159,7 +1161,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/09/05/daily-featured-2019-09/index.html b/2019/09/05/daily-featured-2019-09/index.html index 9ee463cf9d..fd6646f536 100644 --- a/2019/09/05/daily-featured-2019-09/index.html +++ b/2019/09/05/daily-featured-2019-09/index.html @@ -1161,6 +1161,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1309,7 +1311,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/09/18/algorthimn-fe-1/index.html b/2019/09/18/algorthimn-fe-1/index.html index 8bf40744f6..770f5ea681 100644 --- a/2019/09/18/algorthimn-fe-1/index.html +++ b/2019/09/18/algorthimn-fe-1/index.html @@ -1098,6 +1098,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1246,7 +1248,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/09/19/algorthimn-fe-2/index.html b/2019/09/19/algorthimn-fe-2/index.html index 8b64c0bf1c..36041ca0e3 100644 --- a/2019/09/19/algorthimn-fe-2/index.html +++ b/2019/09/19/algorthimn-fe-2/index.html @@ -1086,6 +1086,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1234,7 +1236,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/09/20/algorthimn-fe-3/index.html b/2019/09/20/algorthimn-fe-3/index.html index a1f6e0df7d..5eb68fcc86 100644 --- a/2019/09/20/algorthimn-fe-3/index.html +++ b/2019/09/20/algorthimn-fe-3/index.html @@ -1080,6 +1080,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1228,7 +1230,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/09/21/algorthimn-fe-4/index.html b/2019/09/21/algorthimn-fe-4/index.html index fde64f6f04..8275a7914d 100644 --- a/2019/09/21/algorthimn-fe-4/index.html +++ b/2019/09/21/algorthimn-fe-4/index.html @@ -1060,6 +1060,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1208,7 +1210,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/10/03/draft/index.html b/2019/10/03/draft/index.html index 5d7139dd05..9cb18eac8e 100644 --- a/2019/10/03/draft/index.html +++ b/2019/10/03/draft/index.html @@ -1138,6 +1138,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1286,7 +1288,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/10/10/daily-featured-2019-10/index.html b/2019/10/10/daily-featured-2019-10/index.html index e15219fddd..75aca71353 100644 --- a/2019/10/10/daily-featured-2019-10/index.html +++ b/2019/10/10/daily-featured-2019-10/index.html @@ -1131,6 +1131,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1279,7 +1281,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/11/04/daily-featured-2019-11/index.html b/2019/11/04/daily-featured-2019-11/index.html index 51a6ed553c..bbe187ad9e 100644 --- a/2019/11/04/daily-featured-2019-11/index.html +++ b/2019/11/04/daily-featured-2019-11/index.html @@ -1144,6 +1144,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1292,7 +1294,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/12/02/daily-featured-2019-12/index.html b/2019/12/02/daily-featured-2019-12/index.html index 9d72f77453..29ec32f732 100644 --- a/2019/12/02/daily-featured-2019-12/index.html +++ b/2019/12/02/daily-featured-2019-12/index.html @@ -1097,6 +1097,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1245,7 +1247,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2019/12/11/event-loop/index.html b/2019/12/11/event-loop/index.html index 22c76500a5..8b447fcbc6 100644 --- a/2019/12/11/event-loop/index.html +++ b/2019/12/11/event-loop/index.html @@ -1109,6 +1109,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1257,7 +1259,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/02/daily-featured-2020-01/index.html b/2020/01/02/daily-featured-2020-01/index.html index f3d05a96a0..df6d59655f 100644 --- a/2020/01/02/daily-featured-2020-01/index.html +++ b/2020/01/02/daily-featured-2020-01/index.html @@ -1041,6 +1041,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1189,7 +1191,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/03/basic-data-structure/index.html b/2020/01/03/basic-data-structure/index.html index 88630d74b3..ca1a3003c9 100644 --- a/2020/01/03/basic-data-structure/index.html +++ b/2020/01/03/basic-data-structure/index.html @@ -728,7 +728,7 @@

    - 数学 数据结构 算法 概率 递归 动态规划 + 数据结构 算法 数学 概率 递归 动态规划
    @@ -1212,6 +1212,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1360,7 +1362,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/04/1227.airplane-seat-assignment-probability/index.html b/2020/01/04/1227.airplane-seat-assignment-probability/index.html index 82a056e22b..5cf4ef29c7 100644 --- a/2020/01/04/1227.airplane-seat-assignment-probability/index.html +++ b/2020/01/04/1227.airplane-seat-assignment-probability/index.html @@ -334,7 +334,7 @@

    @@ -472,7 +472,7 @@

    数学

    + @@ -1041,6 +1041,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1189,7 +1191,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/05/1262.greatest-sum-divisible-by-three/index.html b/2020/01/05/1262.greatest-sum-divisible-by-three/index.html index 425967ad6e..121945f9f4 100644 --- a/2020/01/05/1262.greatest-sum-divisible-by-three/index.html +++ b/2020/01/05/1262.greatest-sum-divisible-by-three/index.html @@ -566,7 +566,7 @@

    - 数学 数据结构 算法 概率 递归 动态规划 + 数据结构 算法 数学 概率 递归 动态规划
    @@ -1030,6 +1030,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1178,7 +1180,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/09/1310.xor-queries-of-a-subarray/index.html b/2020/01/09/1310.xor-queries-of-a-subarray/index.html index 18dee8d0c9..cbd0587149 100644 --- a/2020/01/09/1310.xor-queries-of-a-subarray/index.html +++ b/2020/01/09/1310.xor-queries-of-a-subarray/index.html @@ -1004,6 +1004,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1152,7 +1154,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/index.html b/2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/index.html index 6286414d5d..a97617e326 100644 --- a/2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/index.html +++ b/2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/index.html @@ -1003,6 +1003,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1151,7 +1153,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/14/1332.remove-palindromic-subsequences/index.html b/2020/01/14/1332.remove-palindromic-subsequences/index.html index afc1a21daf..f6a96c2962 100644 --- a/2020/01/14/1332.remove-palindromic-subsequences/index.html +++ b/2020/01/14/1332.remove-palindromic-subsequences/index.html @@ -334,7 +334,7 @@

    @@ -999,6 +999,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1147,7 +1149,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/20/ppt-data/index.html b/2020/01/20/ppt-data/index.html index 6901452385..450a056ad6 100644 --- a/2020/01/20/ppt-data/index.html +++ b/2020/01/20/ppt-data/index.html @@ -1075,6 +1075,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1223,7 +1225,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/20/reverseList/index.html b/2020/01/20/reverseList/index.html index 554c6be352..3d12479b10 100644 --- a/2020/01/20/reverseList/index.html +++ b/2020/01/20/reverseList/index.html @@ -334,7 +334,7 @@

    @@ -1052,6 +1052,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1200,7 +1202,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/2020/01/20/\351\235\242\350\257\225\351\242\23046. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262/index.html" "b/2020/01/20/\351\235\242\350\257\225\351\242\23046. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262/index.html" index 17923e97df..0d26e0b338 100644 --- "a/2020/01/20/\351\235\242\350\257\225\351\242\23046. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262/index.html" +++ "b/2020/01/20/\351\235\242\350\257\225\351\242\23046. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262/index.html" @@ -1022,6 +1022,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1170,7 +1172,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/29/50.powx-n/index.html b/2020/01/29/50.powx-n/index.html index 6a3e0f8766..8ddcfb1bc0 100644 --- a/2020/01/29/50.powx-n/index.html +++ b/2020/01/29/50.powx-n/index.html @@ -334,7 +334,7 @@

    @@ -1049,6 +1049,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1197,7 +1199,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/01/31/335.self-crossing/index.html b/2020/01/31/335.self-crossing/index.html index b5daae1815..3093709b1f 100644 --- a/2020/01/31/335.self-crossing/index.html +++ b/2020/01/31/335.self-crossing/index.html @@ -334,7 +334,7 @@

    @@ -1020,6 +1020,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1168,7 +1170,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/02/bigpipe-and-micro-fe/index.html b/2020/02/02/bigpipe-and-micro-fe/index.html index 4f84171b30..0ca6c2934c 100644 --- a/2020/02/02/bigpipe-and-micro-fe/index.html +++ b/2020/02/02/bigpipe-and-micro-fe/index.html @@ -1013,6 +1013,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1161,7 +1163,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/02/how-am-I-conque-leetcode/index.html b/2020/02/02/how-am-I-conque-leetcode/index.html index 9a1b25556f..0b98c58e76 100644 --- a/2020/02/02/how-am-I-conque-leetcode/index.html +++ b/2020/02/02/how-am-I-conque-leetcode/index.html @@ -334,7 +334,7 @@

    @@ -1035,6 +1035,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1183,7 +1185,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/2020/02/03/leetcode-\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250\347\263\273\345\210\227/index.html" "b/2020/02/03/leetcode-\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250\347\263\273\345\210\227/index.html" index f72e09410d..15d401935a 100644 --- "a/2020/02/03/leetcode-\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250\347\263\273\345\210\227/index.html" +++ "b/2020/02/03/leetcode-\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250\347\263\273\345\210\227/index.html" @@ -1111,6 +1111,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1259,7 +1261,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/index.html b/2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/index.html index ab28d7c84d..2651624aeb 100644 --- a/2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/index.html +++ b/2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/index.html @@ -999,6 +999,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1147,7 +1149,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/2020/02/08/\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221\344\270\223\351\242\230/index.html" "b/2020/02/08/\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221\344\270\223\351\242\230/index.html" index 251b343b1f..1aa1bdc4aa 100644 --- "a/2020/02/08/\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221\344\270\223\351\242\230/index.html" +++ "b/2020/02/08/\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221\344\270\223\351\242\230/index.html" @@ -1094,6 +1094,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1242,7 +1244,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/index.html b/2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/index.html index 17d62dee4a..fa637e3d4a 100644 --- a/2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/index.html +++ b/2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/index.html @@ -334,7 +334,7 @@

    @@ -1042,6 +1042,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1190,7 +1192,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/20/rotate-list/index.html b/2020/02/20/rotate-list/index.html index 3f8cc8ae69..ba3184bec1 100644 --- a/2020/02/20/rotate-list/index.html +++ b/2020/02/20/rotate-list/index.html @@ -334,7 +334,7 @@

    @@ -1066,6 +1066,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1214,7 +1216,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/20/to-author/index.html b/2020/02/20/to-author/index.html index dc2e89ad96..8ec0ed2517 100644 --- a/2020/02/20/to-author/index.html +++ b/2020/02/20/to-author/index.html @@ -1018,6 +1018,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1166,7 +1168,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/20/union-find/index.html b/2020/02/20/union-find/index.html index d28bc2c7e6..4580a35248 100644 --- a/2020/02/20/union-find/index.html +++ b/2020/02/20/union-find/index.html @@ -1004,6 +1004,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1152,7 +1154,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/02/22/web-components-enterprize/index.html b/2020/02/22/web-components-enterprize/index.html index 37ff1318ce..0a026a9b47 100644 --- a/2020/02/22/web-components-enterprize/index.html +++ b/2020/02/22/web-components-enterprize/index.html @@ -1030,6 +1030,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1178,7 +1180,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/02/daily-featured-2020-03/index.html b/2020/03/02/daily-featured-2020-03/index.html index ccef94d3c7..55a900b031 100644 --- a/2020/03/02/daily-featured-2020-03/index.html +++ b/2020/03/02/daily-featured-2020-03/index.html @@ -1061,6 +1061,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1209,7 +1211,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/04/84.largest-rectangle-in-histogram/index.html b/2020/03/04/84.largest-rectangle-in-histogram/index.html index c9d7173df1..6649d21322 100644 --- a/2020/03/04/84.largest-rectangle-in-histogram/index.html +++ b/2020/03/04/84.largest-rectangle-in-histogram/index.html @@ -1027,6 +1027,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1175,7 +1177,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/04/85.maximal-rectangle/index.html b/2020/03/04/85.maximal-rectangle/index.html index 4a3f413778..9d9b0f462a 100644 --- a/2020/03/04/85.maximal-rectangle/index.html +++ b/2020/03/04/85.maximal-rectangle/index.html @@ -1001,6 +1001,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1149,7 +1151,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/16/slide-window/index.html b/2020/03/16/slide-window/index.html index 484d88652d..aa39fbe787 100644 --- a/2020/03/16/slide-window/index.html +++ b/2020/03/16/slide-window/index.html @@ -1033,6 +1033,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1181,7 +1183,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/20/serialize/index.html b/2020/03/20/serialize/index.html index e1dc1884cc..b0623eb983 100644 --- a/2020/03/20/serialize/index.html +++ b/2020/03/20/serialize/index.html @@ -1100,6 +1100,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1248,7 +1250,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/20/thanksGaving-2/index.html b/2020/03/20/thanksGaving-2/index.html index 17a9638190..45c1f858b9 100644 --- a/2020/03/20/thanksGaving-2/index.html +++ b/2020/03/20/thanksGaving-2/index.html @@ -1017,6 +1017,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1165,7 +1167,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/23/91-algo/index.html b/2020/03/23/91-algo/index.html index f477d3da09..2a5fbea9ac 100644 --- a/2020/03/23/91-algo/index.html +++ b/2020/03/23/91-algo/index.html @@ -1052,6 +1052,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1200,7 +1202,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/24/bit/index.html b/2020/03/24/bit/index.html index cb499dff6d..ad5dbdf1af 100644 --- a/2020/03/24/bit/index.html +++ b/2020/03/24/bit/index.html @@ -1044,6 +1044,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1192,7 +1194,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/03/25/91-algo2/index.html b/2020/03/25/91-algo2/index.html index 7938a34bcf..ad17c16ede 100644 --- a/2020/03/25/91-algo2/index.html +++ b/2020/03/25/91-algo2/index.html @@ -1031,6 +1031,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1179,7 +1181,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/04/06/82-jinzhiying/index.html b/2020/04/06/82-jinzhiying/index.html index 82681f6b00..3ac5c1902b 100644 --- a/2020/04/06/82-jinzhiying/index.html +++ b/2020/04/06/82-jinzhiying/index.html @@ -991,6 +991,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1139,7 +1141,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/04/06/movie-eager-game/index.html b/2020/04/06/movie-eager-game/index.html index 5eec9091e9..5091ba0dd9 100644 --- a/2020/04/06/movie-eager-game/index.html +++ b/2020/04/06/movie-eager-game/index.html @@ -987,6 +987,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1135,7 +1137,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/04/12/canvas-filter/index.html b/2020/04/12/canvas-filter/index.html index 6fd06d75b7..9870dfb6b1 100644 --- a/2020/04/12/canvas-filter/index.html +++ b/2020/04/12/canvas-filter/index.html @@ -1019,6 +1019,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1167,7 +1169,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/04/13/interview-log-tqp/index.html b/2020/04/13/interview-log-tqp/index.html index 7f4c746b2c..38d2762d73 100644 --- a/2020/04/13/interview-log-tqp/index.html +++ b/2020/04/13/interview-log-tqp/index.html @@ -1217,6 +1217,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1365,7 +1367,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/04/13/leetcode-greedy/index.html b/2020/04/13/leetcode-greedy/index.html index 8f0fe4ab63..e24d67ef04 100644 --- a/2020/04/13/leetcode-greedy/index.html +++ b/2020/04/13/leetcode-greedy/index.html @@ -1080,6 +1080,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1228,7 +1230,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/04/14/thanksGiving3/index.html b/2020/04/14/thanksGiving3/index.html index 19bf375d52..46c4e4e9ba 100644 --- a/2020/04/14/thanksGiving3/index.html +++ b/2020/04/14/thanksGiving3/index.html @@ -1002,6 +1002,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1150,7 +1152,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/05/01/how-am-I-learn-fe/index.html b/2020/05/01/how-am-I-learn-fe/index.html index 6daf6b07f8..c5dbfd56e8 100644 --- a/2020/05/01/how-am-I-learn-fe/index.html +++ b/2020/05/01/how-am-I-learn-fe/index.html @@ -1025,6 +1025,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1173,7 +1175,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/index.html b/2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/index.html index f868beefd9..d6919729ad 100644 --- a/2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/index.html +++ b/2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/index.html @@ -1031,6 +1031,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1179,7 +1181,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/05/05/why-no-magic-string/index.html b/2020/05/05/why-no-magic-string/index.html index af5b765d52..07f3ad13df 100644 --- a/2020/05/05/why-no-magic-string/index.html +++ b/2020/05/05/why-no-magic-string/index.html @@ -1047,6 +1047,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1195,7 +1197,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/05/16/343.integer-break/index.html b/2020/05/16/343.integer-break/index.html index 31db290b42..f9b78044e2 100644 --- a/2020/05/16/343.integer-break/index.html +++ b/2020/05/16/343.integer-break/index.html @@ -334,7 +334,7 @@

    @@ -1049,6 +1049,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1197,7 +1199,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/05/26/91algo-basic-05.two-pointer/index.html b/2020/05/26/91algo-basic-05.two-pointer/index.html index b1b5a089ec..93560df300 100644 --- a/2020/05/26/91algo-basic-05.two-pointer/index.html +++ b/2020/05/26/91algo-basic-05.two-pointer/index.html @@ -1091,6 +1091,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1239,7 +1241,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/05/30/91algo-05-30/index.html b/2020/05/30/91algo-05-30/index.html index 3526a02baf..d6898bc911 100644 --- a/2020/05/30/91algo-05-30/index.html +++ b/2020/05/30/91algo-05-30/index.html @@ -1031,6 +1031,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1179,7 +1181,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/05/31/101.symmetric-tree/index.html b/2020/05/31/101.symmetric-tree/index.html index 8aa9d2d871..e378059589 100644 --- a/2020/05/31/101.symmetric-tree/index.html +++ b/2020/05/31/101.symmetric-tree/index.html @@ -334,7 +334,7 @@

    @@ -1004,6 +1004,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1152,7 +1154,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/01/312.burst-balloons/index.html b/2020/06/01/312.burst-balloons/index.html index ab26ec1ad6..374af21c54 100644 --- a/2020/06/01/312.burst-balloons/index.html +++ b/2020/06/01/312.burst-balloons/index.html @@ -334,7 +334,7 @@

    @@ -1024,6 +1024,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1172,7 +1174,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/02/hacker-drawer/index.html b/2020/06/02/hacker-drawer/index.html index 5f3160e40f..a0300abcec 100644 --- a/2020/06/02/hacker-drawer/index.html +++ b/2020/06/02/hacker-drawer/index.html @@ -984,6 +984,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1132,7 +1134,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/03/over-fancy01/index.html b/2020/06/03/over-fancy01/index.html index 8db636d46b..7e0651035e 100644 --- a/2020/06/03/over-fancy01/index.html +++ b/2020/06/03/over-fancy01/index.html @@ -1030,6 +1030,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1178,7 +1180,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/04/874.walking-robot-simulation/index.html b/2020/06/04/874.walking-robot-simulation/index.html index 0e2e8d3cd9..5811778aec 100644 --- a/2020/06/04/874.walking-robot-simulation/index.html +++ b/2020/06/04/874.walking-robot-simulation/index.html @@ -1010,6 +1010,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1158,7 +1160,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/04/algo-chrome-extension/index.html b/2020/06/04/algo-chrome-extension/index.html index 0a2cd020b4..b96cdd4ad0 100644 --- a/2020/06/04/algo-chrome-extension/index.html +++ b/2020/06/04/algo-chrome-extension/index.html @@ -1025,6 +1025,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1173,7 +1175,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/08/887.super-egg-drop/index.html b/2020/06/08/887.super-egg-drop/index.html index fad4436607..e23efb805c 100644 --- a/2020/06/08/887.super-egg-drop/index.html +++ b/2020/06/08/887.super-egg-drop/index.html @@ -1085,6 +1085,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1233,7 +1235,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/11/91algo-4-basic/index.html b/2020/06/11/91algo-4-basic/index.html index 235910a400..71d3ce6795 100644 --- a/2020/06/11/91algo-4-basic/index.html +++ b/2020/06/11/91algo-4-basic/index.html @@ -519,16 +519,16 @@

     上一页

    -

    - 数据结构 算法 算法提高班 力扣加加 + 数据结构 算法 数学 中位数 二分法
    @@ -1012,6 +1012,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1160,7 +1162,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/13/immutable-js/index.html b/2020/06/13/immutable-js/index.html index df721e2201..854837351e 100644 --- a/2020/06/13/immutable-js/index.html +++ b/2020/06/13/immutable-js/index.html @@ -1070,6 +1070,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1218,7 +1220,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/13/leetcode-1186/index.html b/2020/06/13/leetcode-1186/index.html index 83e31d88f4..f4f98dd54b 100644 --- a/2020/06/13/leetcode-1186/index.html +++ b/2020/06/13/leetcode-1186/index.html @@ -531,16 +531,16 @@
    @@ -1004,6 +1004,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1152,7 +1154,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/13/leetcode-island/index.html b/2020/06/13/leetcode-island/index.html index 604905eb4d..94e254a053 100644 --- a/2020/06/13/leetcode-island/index.html +++ b/2020/06/13/leetcode-island/index.html @@ -332,9 +332,9 @@

    @@ -535,16 +535,16 @@

    优化

     上一页

    -

    - 数学 数据结构 算法 中位数 二分法 + 数据结构 算法 算法提高班 力扣加加
    @@ -555,16 +555,16 @@
    @@ -1028,6 +1028,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1176,7 +1178,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/13/leetcode-median/index.html b/2020/06/13/leetcode-median/index.html index e34605d7f2..ac555b93ab 100644 --- a/2020/06/13/leetcode-median/index.html +++ b/2020/06/13/leetcode-median/index.html @@ -501,7 +501,7 @@

    代 - + @@ -577,16 +577,16 @@

     上一页

    -

    - LeetCode + 数据结构 算法 LeetCode日记
    @@ -597,16 +597,16 @@

    @@ -1070,6 +1070,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1218,7 +1220,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/13/leetcode-pp/index.html b/2020/06/13/leetcode-pp/index.html index c08d02bc7f..249d79a626 100644 --- a/2020/06/13/leetcode-pp/index.html +++ b/2020/06/13/leetcode-pp/index.html @@ -528,16 +528,16 @@

    官网

     上一页

    -

    - 数据结构 算法 LeetCode日记 + LeetCode
    @@ -548,16 +548,16 @@
    @@ -1021,6 +1021,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1169,7 +1171,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/16/ts-generics/index.html b/2020/06/16/ts-generics/index.html index 9f54469ce6..abca76a988 100644 --- a/2020/06/16/ts-generics/index.html +++ b/2020/06/16/ts-generics/index.html @@ -1257,6 +1257,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1405,7 +1407,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/20/611.triangle/index.html b/2020/06/20/611.triangle/index.html index 419e55191b..7376f7b0e5 100644 --- a/2020/06/20/611.triangle/index.html +++ b/2020/06/20/611.triangle/index.html @@ -332,9 +332,9 @@

    @@ -566,16 +566,16 @@

    @@ -1039,6 +1039,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1187,7 +1189,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/20/LIS/index.html b/2020/06/20/LIS/index.html index ce18ff74a0..befc6a37db 100644 --- a/2020/06/20/LIS/index.html +++ b/2020/06/20/LIS/index.html @@ -640,16 +640,16 @@

    More

     上一页

    -

    - 数据结构 算法 LeetCode 数组 + 数据结构 算法 LeetCode日记 Medium
    @@ -660,16 +660,16 @@
    @@ -1133,6 +1133,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1281,7 +1283,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/20/LSS/index.html b/2020/06/20/LSS/index.html index a8d6a1b388..94e990f93d 100644 --- a/2020/06/20/LSS/index.html +++ b/2020/06/20/LSS/index.html @@ -334,7 +334,7 @@

    @@ -568,16 +568,16 @@

    总结

     上一页

    -

    - 数据结构 算法 LeetCode日记 Medium + 动态规划 LeetCode 最长上升子序列
    @@ -588,16 +588,16 @@
    @@ -1061,6 +1061,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1209,7 +1211,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/20/interview-log-haoweilai/index.html b/2020/06/20/interview-log-haoweilai/index.html index 80b179abec..5a86ccf910 100644 --- a/2020/06/20/interview-log-haoweilai/index.html +++ b/2020/06/20/interview-log-haoweilai/index.html @@ -634,16 +634,16 @@

     上一页

    -

    - 动态规划 LeetCode 最长上升子序列 + 数据结构 算法 LeetCode 数组
    @@ -1127,6 +1127,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1275,7 +1277,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/20/mac-setup-for-fe/index.html b/2020/06/20/mac-setup-for-fe/index.html index 48073c8157..3777afd60b 100644 --- a/2020/06/20/mac-setup-for-fe/index.html +++ b/2020/06/20/mac-setup-for-fe/index.html @@ -992,6 +992,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1140,7 +1142,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/06/20/rfc-monitor/index.html b/2020/06/20/rfc-monitor/index.html index 15b6658b94..bc8520e41a 100644 --- a/2020/06/20/rfc-monitor/index.html +++ b/2020/06/20/rfc-monitor/index.html @@ -1073,6 +1073,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1221,7 +1223,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/2020/06/20/\345\210\267\351\242\230\346\226\260\346\211\213/index.html" "b/2020/06/20/\345\210\267\351\242\230\346\226\260\346\211\213/index.html" index 5019354c98..3778fcd0ff 100644 --- "a/2020/06/20/\345\210\267\351\242\230\346\226\260\346\211\213/index.html" +++ "b/2020/06/20/\345\210\267\351\242\230\346\226\260\346\211\213/index.html" @@ -1081,6 +1081,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1229,7 +1231,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/07/01/LCS/index.html b/2020/07/01/LCS/index.html index 4667f007c5..e7304bea4a 100644 --- a/2020/07/01/LCS/index.html +++ b/2020/07/01/LCS/index.html @@ -618,7 +618,7 @@

    - 数学 数据结构 算法 BFS + 数据结构 算法 数学 BFS
    @@ -1102,6 +1102,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1250,7 +1252,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/07/04/1091.shortest-path-in-binary-matrix/index.html b/2020/07/04/1091.shortest-path-in-binary-matrix/index.html index e55e8d1c18..ed60d81eec 100644 --- a/2020/07/04/1091.shortest-path-in-binary-matrix/index.html +++ b/2020/07/04/1091.shortest-path-in-binary-matrix/index.html @@ -464,7 +464,7 @@

    数学

    + @@ -1030,6 +1030,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1178,7 +1180,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/07/21/balanced-tree/index.html b/2020/07/21/balanced-tree/index.html index 0ea9c4f4f6..1b151ea9c4 100644 --- a/2020/07/21/balanced-tree/index.html +++ b/2020/07/21/balanced-tree/index.html @@ -607,7 +607,7 @@

    - 数学 数据结构 算法 BFS + 数据结构 算法 数学 BFS
    @@ -1071,6 +1071,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1219,7 +1221,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/03/mother-01/index.html b/2020/08/03/mother-01/index.html index 279d2e3acf..d5a8157136 100644 --- a/2020/08/03/mother-01/index.html +++ b/2020/08/03/mother-01/index.html @@ -1165,6 +1165,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1313,7 +1315,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/04/ts-internal/index.html b/2020/08/04/ts-internal/index.html index 73346ff82d..b1a7e03753 100644 --- a/2020/08/04/ts-internal/index.html +++ b/2020/08/04/ts-internal/index.html @@ -1154,6 +1154,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1302,7 +1304,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/11/webkit-intro/index.html b/2020/08/11/webkit-intro/index.html index 93a898f7cc..c869d2706e 100644 --- a/2020/08/11/webkit-intro/index.html +++ b/2020/08/11/webkit-intro/index.html @@ -1061,6 +1061,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1209,7 +1211,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/13/leetcode-interview-ts/index.html b/2020/08/13/leetcode-interview-ts/index.html index 7cee9c5499..71e282c70d 100644 --- a/2020/08/13/leetcode-interview-ts/index.html +++ b/2020/08/13/leetcode-interview-ts/index.html @@ -1087,6 +1087,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1235,7 +1237,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/15/ts-type-system/index.html b/2020/08/15/ts-type-system/index.html index 9b8df37a07..6895d80c10 100644 --- a/2020/08/15/ts-type-system/index.html +++ b/2020/08/15/ts-type-system/index.html @@ -1207,6 +1207,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1355,7 +1357,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/16/leetcode-cheat/index.html b/2020/08/16/leetcode-cheat/index.html index 3fe645ec4c..76fc91bcc6 100644 --- a/2020/08/16/leetcode-cheat/index.html +++ b/2020/08/16/leetcode-cheat/index.html @@ -1066,6 +1066,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1214,7 +1216,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/20/tech-conf/index.html b/2020/08/20/tech-conf/index.html index 04c247677a..ffbac9f3c3 100644 --- a/2020/08/20/tech-conf/index.html +++ b/2020/08/20/tech-conf/index.html @@ -993,6 +993,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1141,7 +1143,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/21/ts-type/index.html b/2020/08/21/ts-type/index.html index 1d182bae7d..e977e7796e 100644 --- a/2020/08/21/ts-type/index.html +++ b/2020/08/21/ts-type/index.html @@ -1143,6 +1143,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1291,7 +1293,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/24/ts-config/index.html b/2020/08/24/ts-config/index.html index 55416bd5f4..a4649ca126 100644 --- a/2020/08/24/ts-config/index.html +++ b/2020/08/24/ts-config/index.html @@ -1294,6 +1294,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1442,7 +1444,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/27/91algo-dp-lecture/index.html b/2020/08/27/91algo-dp-lecture/index.html index b360a33f91..86aa714a37 100644 --- a/2020/08/27/91algo-dp-lecture/index.html +++ b/2020/08/27/91algo-dp-lecture/index.html @@ -334,7 +334,7 @@

    @@ -1157,6 +1157,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1305,7 +1307,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/08/30/887.super-egg-drop-extension/index.html b/2020/08/30/887.super-egg-drop-extension/index.html index 28f47fbff3..3fc74884b2 100644 --- a/2020/08/30/887.super-egg-drop-extension/index.html +++ b/2020/08/30/887.super-egg-drop-extension/index.html @@ -562,9 +562,9 @@

     上一页

    -

    @@ -1055,6 +1055,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1203,7 +1205,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/09/06/byte-dance-algo-ex-2017/index.html b/2020/09/06/byte-dance-algo-ex-2017/index.html index d6cffbb702..44a29f8887 100644 --- a/2020/09/06/byte-dance-algo-ex-2017/index.html +++ b/2020/09/06/byte-dance-algo-ex-2017/index.html @@ -334,7 +334,7 @@

    @@ -700,16 +700,16 @@

    扩展

     上一页

    -

    - 数据结构 算法 滑动窗口 面试 字节跳动 + 前端 TypeScript
    @@ -720,16 +720,16 @@
    @@ -1193,6 +1193,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1341,7 +1343,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/09/06/byte-dance-algo-ex/index.html b/2020/09/06/byte-dance-algo-ex/index.html index f0d251e302..34f93be801 100644 --- a/2020/09/06/byte-dance-algo-ex/index.html +++ b/2020/09/06/byte-dance-algo-ex/index.html @@ -334,7 +334,7 @@

    @@ -622,16 +622,16 @@

    总结

     上一页

    -

    - 前端 TypeScript + 数据结构 算法 滑动窗口 面试 字节跳动
    @@ -642,16 +642,16 @@
    @@ -1115,6 +1115,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1263,7 +1265,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/09/27/atMostK/index.html b/2020/09/27/atMostK/index.html index 2fa89e5888..c5543c25d7 100644 --- a/2020/09/27/atMostK/index.html +++ b/2020/09/27/atMostK/index.html @@ -1227,6 +1227,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1375,7 +1377,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2020/09/27/ts-exercises/index.html b/2020/09/27/ts-exercises/index.html index f042ad87c6..b488950311 100644 --- a/2020/09/27/ts-exercises/index.html +++ b/2020/09/27/ts-exercises/index.html @@ -752,9 +752,9 @@
    @@ -1073,6 +1073,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1221,7 +1223,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/02/20/youtube-extesion/index.html b/2021/02/20/youtube-extesion/index.html index 702d22b2b2..c997c37bcc 100644 --- a/2021/02/20/youtube-extesion/index.html +++ b/2021/02/20/youtube-extesion/index.html @@ -1007,6 +1007,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1155,7 +1157,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/2021/02/20/\345\210\240\351\231\244\351\227\256\351\242\230/index.html" "b/2021/02/20/\345\210\240\351\231\244\351\227\256\351\242\230/index.html" index 4f19c8f6a7..6f1adbb1ef 100644 --- "a/2021/02/20/\345\210\240\351\231\244\351\227\256\351\242\230/index.html" +++ "b/2021/02/20/\345\210\240\351\231\244\351\227\256\351\242\230/index.html" @@ -1151,6 +1151,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1299,7 +1301,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/02/24/gcd/index.html b/2021/02/24/gcd/index.html index 7c9c48f679..44982d66f0 100644 --- a/2021/02/24/gcd/index.html +++ b/2021/02/24/gcd/index.html @@ -484,7 +484,7 @@

    总结

    数学

    + @@ -1071,6 +1071,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1219,7 +1221,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/02/27/github-blog-auto/index.html b/2021/02/27/github-blog-auto/index.html index cd2cd621b5..624d118ca8 100644 --- a/2021/02/27/github-blog-auto/index.html +++ b/2021/02/27/github-blog-auto/index.html @@ -548,7 +548,7 @@

    - 数学 数据结构 算法 最大公约数 + 数据结构 算法 数学 最大公约数
    @@ -1012,6 +1012,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1160,7 +1162,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/03/01/91meeting-season-3-1/index.html b/2021/03/01/91meeting-season-3-1/index.html index 23c931840a..e29af528e3 100644 --- a/2021/03/01/91meeting-season-3-1/index.html +++ b/2021/03/01/91meeting-season-3-1/index.html @@ -1063,6 +1063,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1211,7 +1213,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/03/04/school-01/index.html b/2021/03/04/school-01/index.html index f947cfb1f0..84e0a13a08 100644 --- a/2021/03/04/school-01/index.html +++ b/2021/03/04/school-01/index.html @@ -986,6 +986,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1134,7 +1136,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/03/08/binary-search-1/index.html b/2021/03/08/binary-search-1/index.html index 989a849751..c23e2d27f2 100644 --- a/2021/03/08/binary-search-1/index.html +++ b/2021/03/08/binary-search-1/index.html @@ -1120,6 +1120,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1268,7 +1270,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/03/11/school-02/index.html b/2021/03/11/school-02/index.html index b1dfe0a224..164ede1f00 100644 --- a/2021/03/11/school-02/index.html +++ b/2021/03/11/school-02/index.html @@ -1104,6 +1104,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1252,7 +1254,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/03/16/leetcode-cheat-update-1/index.html b/2021/03/16/leetcode-cheat-update-1/index.html index a7ba7b2aa6..13851ab9d5 100644 --- a/2021/03/16/leetcode-cheat-update-1/index.html +++ b/2021/03/16/leetcode-cheat-update-1/index.html @@ -1000,6 +1000,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1148,7 +1150,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/03/23/binary-search-2/index.html b/2021/03/23/binary-search-2/index.html index 920e051ba1..f0eeecb7cd 100644 --- a/2021/03/23/binary-search-2/index.html +++ b/2021/03/23/binary-search-2/index.html @@ -1358,6 +1358,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1506,7 +1508,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/03/28/school-03/index.html b/2021/03/28/school-03/index.html index 94bacba0d9..451529b889 100644 --- a/2021/03/28/school-03/index.html +++ b/2021/03/28/school-03/index.html @@ -1168,6 +1168,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1316,7 +1318,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/04/03/interview-road/index.html b/2021/04/03/interview-road/index.html index 6cd202e5d3..689023f28c 100644 --- a/2021/04/03/interview-road/index.html +++ b/2021/04/03/interview-road/index.html @@ -1034,6 +1034,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1182,7 +1184,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/04/06/out-of-science/index.html b/2021/04/06/out-of-science/index.html index dd87ac6033..95a9dee00b 100644 --- a/2021/04/06/out-of-science/index.html +++ b/2021/04/06/out-of-science/index.html @@ -1117,6 +1117,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1265,7 +1267,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/04/20/dp/index.html b/2021/04/20/dp/index.html index d0ab24fb04..ef8b80a1e9 100644 --- a/2021/04/20/dp/index.html +++ b/2021/04/20/dp/index.html @@ -1413,6 +1413,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1561,7 +1563,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/04/28/ebook-2.0/index.html b/2021/04/28/ebook-2.0/index.html index 4d0960d2c9..dce52ea684 100644 --- a/2021/04/28/ebook-2.0/index.html +++ b/2021/04/28/ebook-2.0/index.html @@ -1019,6 +1019,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1167,7 +1169,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/05/02/91algo-4/index.html b/2021/05/02/91algo-4/index.html index f0bbc938ba..9b781785b3 100644 --- a/2021/05/02/91algo-4/index.html +++ b/2021/05/02/91algo-4/index.html @@ -1187,6 +1187,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1335,7 +1337,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/05/10/91-student-1/index.html b/2021/05/10/91-student-1/index.html index 7987825aa3..f489095c7e 100644 --- a/2021/05/10/91-student-1/index.html +++ b/2021/05/10/91-student-1/index.html @@ -1040,6 +1040,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1188,7 +1190,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/05/16/github-reader/index.html b/2021/05/16/github-reader/index.html index cacf80e3a2..9d924ffc84 100644 --- a/2021/05/16/github-reader/index.html +++ b/2021/05/16/github-reader/index.html @@ -1027,6 +1027,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1175,7 +1177,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/05/18/dp-bottom-up/index.html b/2021/05/18/dp-bottom-up/index.html index 8d49d3d238..8539338837 100644 --- a/2021/05/18/dp-bottom-up/index.html +++ b/2021/05/18/dp-bottom-up/index.html @@ -1146,6 +1146,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1294,7 +1296,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/05/24/interview-fe-bi/index.html b/2021/05/24/interview-fe-bi/index.html index 511ce4fd58..dafbc394a5 100644 --- a/2021/05/24/interview-fe-bi/index.html +++ b/2021/05/24/interview-fe-bi/index.html @@ -1126,6 +1126,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1274,7 +1276,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/06/02/search/index.html b/2021/06/02/search/index.html index 76986eb424..2f3e1b2925 100644 --- a/2021/06/02/search/index.html +++ b/2021/06/02/search/index.html @@ -1307,6 +1307,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1455,7 +1457,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/06/18/quit-esign/index.html b/2021/06/18/quit-esign/index.html index 4029f131e1..f780dadbd9 100644 --- a/2021/06/18/quit-esign/index.html +++ b/2021/06/18/quit-esign/index.html @@ -1017,6 +1017,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1165,7 +1167,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/06/23/moyu-1/index.html b/2021/06/23/moyu-1/index.html index 1ff8ed1610..262741a274 100644 --- a/2021/06/23/moyu-1/index.html +++ b/2021/06/23/moyu-1/index.html @@ -1014,6 +1014,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1162,7 +1164,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/07/03/mono-vite/index.html b/2021/07/03/mono-vite/index.html index 75321c02d3..4abbc8ebb4 100644 --- a/2021/07/03/mono-vite/index.html +++ b/2021/07/03/mono-vite/index.html @@ -1000,6 +1000,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1148,7 +1150,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/07/05/ttw/index.html b/2021/07/05/ttw/index.html index 9ae10af1c2..aa06bbb181 100644 --- a/2021/07/05/ttw/index.html +++ b/2021/07/05/ttw/index.html @@ -1020,6 +1020,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1168,7 +1170,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/07/27/91algo-interview-cabbage/index.html b/2021/07/27/91algo-interview-cabbage/index.html index 33d5b45110..b5953ecf50 100644 --- a/2021/07/27/91algo-interview-cabbage/index.html +++ b/2021/07/27/91algo-interview-cabbage/index.html @@ -1028,6 +1028,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1176,7 +1178,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/07/28/vscode-brower-debug/index.html b/2021/07/28/vscode-brower-debug/index.html index 0bd0ecfafb..6354362b6e 100644 --- a/2021/07/28/vscode-brower-debug/index.html +++ b/2021/07/28/vscode-brower-debug/index.html @@ -1005,6 +1005,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1153,7 +1155,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/07/29/91algo-interview-yixiao/index.html b/2021/07/29/91algo-interview-yixiao/index.html index c137fea58a..ff2d3b85f5 100644 --- a/2021/07/29/91algo-interview-yixiao/index.html +++ b/2021/07/29/91algo-interview-yixiao/index.html @@ -1062,6 +1062,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1210,7 +1212,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/08/06/ydk-leetcode/index.html b/2021/08/06/ydk-leetcode/index.html index f034345bf9..3153a2460d 100644 --- a/2021/08/06/ydk-leetcode/index.html +++ b/2021/08/06/ydk-leetcode/index.html @@ -1031,6 +1031,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1179,7 +1181,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/08/17/ydk-leetcode-2/index.html b/2021/08/17/ydk-leetcode-2/index.html index e187a506eb..bc6b636c6b 100644 --- a/2021/08/17/ydk-leetcode-2/index.html +++ b/2021/08/17/ydk-leetcode-2/index.html @@ -1041,6 +1041,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1189,7 +1191,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/08/21/91algo-5/index.html b/2021/08/21/91algo-5/index.html index eaac34b5aa..f927fdc410 100644 --- a/2021/08/21/91algo-5/index.html +++ b/2021/08/21/91algo-5/index.html @@ -1176,6 +1176,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1324,7 +1326,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/09/02/vscode-dev-codespaces/index.html b/2021/09/02/vscode-dev-codespaces/index.html index 439b4a20ad..efc8386270 100644 --- a/2021/09/02/vscode-dev-codespaces/index.html +++ b/2021/09/02/vscode-dev-codespaces/index.html @@ -990,6 +990,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1138,7 +1140,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/09/02/zaozaoliao/index.html b/2021/09/02/zaozaoliao/index.html index 3b6a64bca7..2d27a09cc2 100644 --- a/2021/09/02/zaozaoliao/index.html +++ b/2021/09/02/zaozaoliao/index.html @@ -1254,6 +1254,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1402,7 +1404,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/09/04/leetcode-solution-book/index.html b/2021/09/04/leetcode-solution-book/index.html index d0685baef2..11602b3b99 100644 --- a/2021/09/04/leetcode-solution-book/index.html +++ b/2021/09/04/leetcode-solution-book/index.html @@ -1071,6 +1071,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1219,7 +1221,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/09/04/wfh/index.html b/2021/09/04/wfh/index.html index 88864663fa..5c5e94e2d8 100644 --- a/2021/09/04/wfh/index.html +++ b/2021/09/04/wfh/index.html @@ -1006,6 +1006,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1154,7 +1156,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/09/24/91algo-interview-kongshi/index.html b/2021/09/24/91algo-interview-kongshi/index.html index fec1671189..251a360656 100644 --- a/2021/09/24/91algo-interview-kongshi/index.html +++ b/2021/09/24/91algo-interview-kongshi/index.html @@ -1023,6 +1023,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1171,7 +1173,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/09/26/algo-vis/index.html b/2021/09/26/algo-vis/index.html index 496383a23b..36ecadd42d 100644 --- a/2021/09/26/algo-vis/index.html +++ b/2021/09/26/algo-vis/index.html @@ -1029,6 +1029,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1177,7 +1179,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/10/05/copilot/index.html b/2021/10/05/copilot/index.html index 7fcd5af53b..7feaab5595 100644 --- a/2021/10/05/copilot/index.html +++ b/2021/10/05/copilot/index.html @@ -1006,6 +1006,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1154,7 +1156,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/10/10/programming-idioms/index.html b/2021/10/10/programming-idioms/index.html index 02b5489b40..dc5c16c99f 100644 --- a/2021/10/10/programming-idioms/index.html +++ b/2021/10/10/programming-idioms/index.html @@ -992,6 +992,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1140,7 +1142,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/10/16/algo-fakers/index.html b/2021/10/16/algo-fakers/index.html index e9b733ad18..cab515a756 100644 --- a/2021/10/16/algo-fakers/index.html +++ b/2021/10/16/algo-fakers/index.html @@ -1041,6 +1041,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1189,7 +1191,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/10/24/new-book/index.html b/2021/10/24/new-book/index.html index 5a3d01e147..7c180dd997 100644 --- a/2021/10/24/new-book/index.html +++ b/2021/10/24/new-book/index.html @@ -1043,6 +1043,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1191,7 +1193,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/11/09/grapth/index.html b/2021/11/09/grapth/index.html index ba04246a75..3330245d36 100644 --- a/2021/11/09/grapth/index.html +++ b/2021/11/09/grapth/index.html @@ -1273,6 +1273,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1421,7 +1423,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/11/10/chrome-recorder/index.html b/2021/11/10/chrome-recorder/index.html index d147671395..384fa06127 100644 --- a/2021/11/10/chrome-recorder/index.html +++ b/2021/11/10/chrome-recorder/index.html @@ -1007,6 +1007,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1155,7 +1157,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/11/17/cses/index.html b/2021/11/17/cses/index.html index 4a7c067316..282349c0df 100644 --- a/2021/11/17/cses/index.html +++ b/2021/11/17/cses/index.html @@ -1020,6 +1020,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1168,7 +1170,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/11/20/leetcode-book.intro/index.html b/2021/11/20/leetcode-book.intro/index.html index fd06187a28..df8af1c189 100644 --- a/2021/11/20/leetcode-book.intro/index.html +++ b/2021/11/20/leetcode-book.intro/index.html @@ -1066,6 +1066,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1214,7 +1216,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/11/28/qiuzhao2021/index.html b/2021/11/28/qiuzhao2021/index.html index 4c5f5b9693..fef0315c38 100644 --- a/2021/11/28/qiuzhao2021/index.html +++ b/2021/11/28/qiuzhao2021/index.html @@ -989,6 +989,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1137,7 +1139,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/12/03/91algo-6/index.html b/2021/12/03/91algo-6/index.html index 680cc884c7..426bd44118 100644 --- a/2021/12/03/91algo-6/index.html +++ b/2021/12/03/91algo-6/index.html @@ -1169,6 +1169,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1317,7 +1319,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/12/13/tencent-2021/index.html b/2021/12/13/tencent-2021/index.html index 90311ad905..e1c2a8e7c9 100644 --- a/2021/12/13/tencent-2021/index.html +++ b/2021/12/13/tencent-2021/index.html @@ -1026,6 +1026,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1174,7 +1176,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/12/16/segment-tree/index.html b/2021/12/16/segment-tree/index.html index 7470e8cc44..97e2cf29fc 100644 --- a/2021/12/16/segment-tree/index.html +++ b/2021/12/16/segment-tree/index.html @@ -1175,6 +1175,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1323,7 +1325,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2021/12/22/leetcode-cheat-hide-cases/index.html b/2021/12/22/leetcode-cheat-hide-cases/index.html index eeccd35802..33b8a61b09 100644 --- a/2021/12/22/leetcode-cheat-hide-cases/index.html +++ b/2021/12/22/leetcode-cheat-hide-cases/index.html @@ -1007,6 +1007,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1155,7 +1157,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/01/04/non-tech-skill/index.html b/2022/01/04/non-tech-skill/index.html index 99e078802a..160f49dc66 100644 --- a/2022/01/04/non-tech-skill/index.html +++ b/2022/01/04/non-tech-skill/index.html @@ -1057,6 +1057,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1205,7 +1207,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/01/27/backward/index.html b/2022/01/27/backward/index.html index 6f7462a410..102235f7c8 100644 --- a/2022/01/27/backward/index.html +++ b/2022/01/27/backward/index.html @@ -1110,6 +1110,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1258,7 +1260,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/02/06/cors-extension/index.html b/2022/02/06/cors-extension/index.html index 896fa40b41..87f6d69c92 100644 --- a/2022/02/06/cors-extension/index.html +++ b/2022/02/06/cors-extension/index.html @@ -981,6 +981,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1129,7 +1131,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/02/11/git-bisect-bug/index.html b/2022/02/11/git-bisect-bug/index.html index b140ced495..1d79855ffa 100644 --- a/2022/02/11/git-bisect-bug/index.html +++ b/2022/02/11/git-bisect-bug/index.html @@ -1044,6 +1044,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1192,7 +1194,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/02/20/wfh2/index.html b/2022/02/20/wfh2/index.html index 92bdbe7d29..ee8da311e4 100644 --- a/2022/02/20/wfh2/index.html +++ b/2022/02/20/wfh2/index.html @@ -1046,6 +1046,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1194,7 +1196,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/03/04/zhandui/index.html b/2022/03/04/zhandui/index.html index 8a339e85da..42764038f5 100644 --- a/2022/03/04/zhandui/index.html +++ b/2022/03/04/zhandui/index.html @@ -994,6 +994,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1142,7 +1144,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/03/12/91algo-7/index.html b/2022/03/12/91algo-7/index.html index 977be86078..448ede4f24 100644 --- a/2022/03/12/91algo-7/index.html +++ b/2022/03/12/91algo-7/index.html @@ -1166,6 +1166,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1314,7 +1316,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/03/21/interviewer/index.html b/2022/03/21/interviewer/index.html index f718aedfce..e941505c25 100644 --- a/2022/03/21/interviewer/index.html +++ b/2022/03/21/interviewer/index.html @@ -1070,6 +1070,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1218,7 +1220,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/04/06/git-merge-base/index.html b/2022/04/06/git-merge-base/index.html index 6256b383ac..5834170073 100644 --- a/2022/04/06/git-merge-base/index.html +++ b/2022/04/06/git-merge-base/index.html @@ -1033,6 +1033,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1181,7 +1183,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/04/26/books-2022/index.html b/2022/04/26/books-2022/index.html index e7d733352e..3c20c2059f 100644 --- a/2022/04/26/books-2022/index.html +++ b/2022/04/26/books-2022/index.html @@ -1155,6 +1155,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1303,7 +1305,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/05/15/daily-featured-2022-04/index.html b/2022/05/15/daily-featured-2022-04/index.html index 95fc700d5f..22ef132198 100644 --- a/2022/05/15/daily-featured-2022-04/index.html +++ b/2022/05/15/daily-featured-2022-04/index.html @@ -1072,6 +1072,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1220,7 +1222,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/05/21/google-interview-tao/index.html b/2022/05/21/google-interview-tao/index.html index 936f21ba6e..8b78178be8 100644 --- a/2022/05/21/google-interview-tao/index.html +++ b/2022/05/21/google-interview-tao/index.html @@ -1015,6 +1015,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1163,7 +1165,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/07/09/91algo-8/index.html b/2022/07/09/91algo-8/index.html index 7a38094a65..ac53c10abc 100644 --- a/2022/07/09/91algo-8/index.html +++ b/2022/07/09/91algo-8/index.html @@ -1167,6 +1167,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1315,7 +1317,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2022/10/15/91algo-9/index.html b/2022/10/15/91algo-9/index.html index b88181b4bb..d2436af153 100644 --- a/2022/10/15/91algo-9/index.html +++ b/2022/10/15/91algo-9/index.html @@ -1177,6 +1177,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1325,7 +1327,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/01/01/goutou-2/index.html b/2023/01/01/goutou-2/index.html index a115a6c431..0564fbe3f8 100644 --- a/2023/01/01/goutou-2/index.html +++ b/2023/01/01/goutou-2/index.html @@ -1003,6 +1003,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1151,7 +1153,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/01/02/how-leetcode/index.html b/2023/01/02/how-leetcode/index.html index e0e548ee0b..bf405e12f1 100644 --- a/2023/01/02/how-leetcode/index.html +++ b/2023/01/02/how-leetcode/index.html @@ -1100,6 +1100,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1248,7 +1250,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/01/18/2022/index.html b/2023/01/18/2022/index.html index bdf957ac71..ace07bc91f 100644 --- a/2023/01/18/2022/index.html +++ b/2023/01/18/2022/index.html @@ -1009,6 +1009,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1157,7 +1159,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/01/19/leetcode-rating/index.html b/2023/01/19/leetcode-rating/index.html index 9b154c888b..e6f07a179b 100644 --- a/2023/01/19/leetcode-rating/index.html +++ b/2023/01/19/leetcode-rating/index.html @@ -991,6 +991,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1139,7 +1141,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/02/01/91algo-10/index.html b/2023/02/01/91algo-10/index.html index d98cb19350..592fc5d0d4 100644 --- a/2023/02/01/91algo-10/index.html +++ b/2023/02/01/91algo-10/index.html @@ -1177,6 +1177,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1325,7 +1327,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/03/27/migrate-to-webpack5_swc/index.html b/2023/03/27/migrate-to-webpack5_swc/index.html index 0333bc2ff9..67b590194c 100644 --- a/2023/03/27/migrate-to-webpack5_swc/index.html +++ b/2023/03/27/migrate-to-webpack5_swc/index.html @@ -1032,6 +1032,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1180,7 +1182,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/06/04/91algo-11/index.html b/2023/06/04/91algo-11/index.html index b247bb795a..51a32fb3fd 100644 --- a/2023/06/04/91algo-11/index.html +++ b/2023/06/04/91algo-11/index.html @@ -1181,6 +1181,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1329,7 +1331,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/08/08/remove-useless-css/index.html b/2023/08/08/remove-useless-css/index.html index 8cafe0362a..53ab44f41d 100644 --- a/2023/08/08/remove-useless-css/index.html +++ b/2023/08/08/remove-useless-css/index.html @@ -1017,6 +1017,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1165,7 +1167,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/08/27/how-to-make-automator/index.html b/2023/08/27/how-to-make-automator/index.html index f3c18ee1e2..c62b574097 100644 --- a/2023/08/27/how-to-make-automator/index.html +++ b/2023/08/27/how-to-make-automator/index.html @@ -1081,6 +1081,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1229,7 +1231,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2023/11/02/91algo-12/index.html b/2023/11/02/91algo-12/index.html index 467f27df6d..d8429ec9cc 100644 --- a/2023/11/02/91algo-12/index.html +++ b/2023/11/02/91algo-12/index.html @@ -1162,6 +1162,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1310,7 +1312,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2024/01/28/error-boundaries/index.html b/2024/01/28/error-boundaries/index.html index 9fce7436b3..83acc93363 100644 --- a/2024/01/28/error-boundaries/index.html +++ b/2024/01/28/error-boundaries/index.html @@ -526,13 +526,18 @@

    总结

     上一页

    -

    + +
    + 前端 css-in-js +
    + @@ -1014,6 +1019,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1162,7 +1169,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2024/03/07/kuma/index.html b/2024/03/07/kuma/index.html index 235651286b..a58b0ce3fe 100644 --- a/2024/03/07/kuma/index.html +++ b/2024/03/07/kuma/index.html @@ -533,18 +533,42 @@

    总结 +
    + +
     上一页
    +

    + +

    + + +
    + 理财 +
    + +
    +
    +
    @@ -1006,6 +1030,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1154,7 +1180,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/2024/02/15/make-money/index.html b/2024/03/10/make-money/index.html similarity index 91% rename from 2024/02/15/make-money/index.html rename to 2024/03/10/make-money/index.html index 15cdad280e..2638ee3344 100644 --- a/2024/02/15/make-money/index.html +++ b/2024/03/10/make-money/index.html @@ -4,7 +4,7 @@ - lucifer的网络博客 + How To Make Monney | lucifer的网络博客 @@ -290,8 +290,8 @@

    lucifer

    - - 2024-02-15 + + How To Make Monney

    @@ -322,7 +322,7 @@

    @@ -330,6 +330,14 @@

    + + + @@ -379,7 +387,9 @@

    -

    How To Make Monney

    1. 了解你赚的是什么钱

    首先你要知道你赚的是什么钱。即什么情况下会赚钱,什么情况下会赔钱。

    +

    有哪些赚钱的方法?具体如何操作?它们的赚钱逻辑是什么?什么情况下会亏钱?

    + +

    1. 了解你赚的是什么钱

    首先你要知道你赚的是什么钱。即什么情况下会赚钱,什么情况下会赔钱。

    这个问题可以很简单,也可以很复杂。

    了解赚的什么钱可以很简单。比如赌马,你赌对了就赚钱,赌错了就赔钱。再比如做生意,利润大于 0 就赚钱,否则就赔钱。再比如做股票,你买了股票,股票涨了你赚钱,股票跌了你赔钱。

    了解赚的什么钱也可以很复杂。

    @@ -475,7 +485,7 @@

    + + @@ -495,7 +508,7 @@

    - + @@ -505,7 +518,7 @@

    @@ -517,7 +530,7 @@

    @@ -529,7 +542,7 @@

    @@ -553,40 +566,21 @@

    -
    - -
     上一页
    -

    - -

    - - -
    - 前端 css-in-js -
    - - -
    -
    @@ -628,7 +622,7 @@

     评论

    @@ -737,7 +731,7 @@

     评论

    @@ -1050,6 +1044,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1198,7 +1194,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/404.html b/404.html index d45c0fb8d9..cd8a6e68fe 100644 --- a/404.html +++ b/404.html @@ -739,6 +739,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -887,7 +889,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/about/index.html b/about/index.html index d72f8398e3..5921de0d63 100644 --- a/about/index.html +++ b/about/index.html @@ -823,6 +823,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -971,7 +973,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2009/01/index.html b/archives/2009/01/index.html index 7f2a670cac..4ef48595e9 100644 --- a/archives/2009/01/index.html +++ b/archives/2009/01/index.html @@ -903,6 +903,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1051,7 +1053,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2009/index.html b/archives/2009/index.html index 57761a1480..380a15af18 100644 --- a/archives/2009/index.html +++ b/archives/2009/index.html @@ -903,6 +903,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1051,7 +1053,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2010/03/index.html b/archives/2010/03/index.html index 9a426916e8..d18d556576 100644 --- a/archives/2010/03/index.html +++ b/archives/2010/03/index.html @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2010/index.html b/archives/2010/index.html index 8e3918087a..a551833f06 100644 --- a/archives/2010/index.html +++ b/archives/2010/index.html @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2019/06/index.html b/archives/2019/06/index.html index 3f295e41cd..b6c23b89df 100644 --- a/archives/2019/06/index.html +++ b/archives/2019/06/index.html @@ -917,6 +917,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1065,7 +1067,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2019/09/index.html b/archives/2019/09/index.html index deaec79a9f..50b7d19934 100644 --- a/archives/2019/09/index.html +++ b/archives/2019/09/index.html @@ -1589,6 +1589,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1737,7 +1739,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2019/10/index.html b/archives/2019/10/index.html index 88378012d9..c0d56afbbe 100644 --- a/archives/2019/10/index.html +++ b/archives/2019/10/index.html @@ -1054,6 +1054,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1202,7 +1204,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2019/11/index.html b/archives/2019/11/index.html index 3fe0c751ad..0e02ee41c9 100644 --- a/archives/2019/11/index.html +++ b/archives/2019/11/index.html @@ -919,6 +919,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1067,7 +1069,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2019/12/index.html b/archives/2019/12/index.html index 228385b3cd..04353120aa 100644 --- a/archives/2019/12/index.html +++ b/archives/2019/12/index.html @@ -1052,6 +1052,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1200,7 +1202,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2019/index.html b/archives/2019/index.html index 4d95c8d87c..5ba9d23c03 100644 --- a/archives/2019/index.html +++ b/archives/2019/index.html @@ -2133,6 +2133,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2281,7 +2283,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2019/page/2/index.html b/archives/2019/page/2/index.html index 5e8ea219a8..7120cff20c 100644 --- a/archives/2019/page/2/index.html +++ b/archives/2019/page/2/index.html @@ -1056,6 +1056,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1204,7 +1206,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/01/index.html b/archives/2020/01/index.html index 7167f8e00a..4bc236d1ae 100644 --- a/archives/2020/01/index.html +++ b/archives/2020/01/index.html @@ -362,7 +362,7 @@

    @@ -494,7 +494,7 @@

    @@ -744,7 +744,7 @@

    @@ -1002,7 +1002,7 @@

    @@ -1529,7 +1529,7 @@

    @@ -1589,12 +1589,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -2131,6 +2131,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2279,7 +2281,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/01/page/2/index.html b/archives/2020/01/page/2/index.html index f8275ea05b..ab81a2ef11 100644 --- a/archives/2020/01/page/2/index.html +++ b/archives/2020/01/page/2/index.html @@ -1057,6 +1057,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1205,7 +1207,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/02/index.html b/archives/2020/02/index.html index 88601ec82f..5b0708ddaf 100644 --- a/archives/2020/02/index.html +++ b/archives/2020/02/index.html @@ -493,7 +493,7 @@

    @@ -884,7 +884,7 @@

    @@ -1540,7 +1540,7 @@

    @@ -2120,6 +2120,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2268,7 +2270,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/03/index.html b/archives/2020/03/index.html index 08a2ade63a..c90ab252e3 100644 --- a/archives/2020/03/index.html +++ b/archives/2020/03/index.html @@ -1967,6 +1967,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2115,7 +2117,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/04/index.html b/archives/2020/04/index.html index e2ea258292..ed2c1e6550 100644 --- a/archives/2020/04/index.html +++ b/archives/2020/04/index.html @@ -1567,6 +1567,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1715,7 +1717,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/05/index.html b/archives/2020/05/index.html index 7c1bd8ed7a..ed75316299 100644 --- a/archives/2020/05/index.html +++ b/archives/2020/05/index.html @@ -356,7 +356,7 @@

    @@ -755,7 +755,7 @@

    @@ -1720,6 +1720,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1868,7 +1870,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/06/index.html b/archives/2020/06/index.html index c86036c482..94c2523e01 100644 --- a/archives/2020/06/index.html +++ b/archives/2020/06/index.html @@ -360,9 +360,9 @@

    @@ -451,8 +451,8 @@

    - - 一文看懂《最大子序列和问题》 + + 穿上衣服我就不认识你了?来聊聊最长上升子序列

    @@ -492,9 +492,9 @@

    @@ -514,7 +514,7 @@

      |   @@ -522,7 +522,7 @@

    @@ -540,10 +540,15 @@

    -

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    +

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    +

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    +

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    +
    +

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    +
    @@ -671,15 +674,10 @@

    -

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    -

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    -

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    -
    -

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    -
    +

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    - + 阅读全文 @@ -689,11 +687,13 @@

    @@ -2114,6 +2114,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2262,7 +2264,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/06/page/2/index.html b/archives/2020/06/page/2/index.html index 82a6ec1c0d..837f8369d3 100644 --- a/archives/2020/06/page/2/index.html +++ b/archives/2020/06/page/2/index.html @@ -295,8 +295,8 @@

    lucifer

    - - 阿里面试题:如何寻找「两个数组」的中位数? + + 力扣加加闪亮登场~

    @@ -336,9 +336,9 @@

    @@ -358,7 +358,7 @@

      |   @@ -366,7 +366,7 @@

    @@ -384,11 +384,12 @@

    -

    一个数组的中位数很容易求,那两个数组呢?

    +

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    +

    @@ -652,12 +651,11 @@

    -

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    -

    +

    一个数组的中位数很容易求,那两个数组呢?

    @@ -1520,7 +1520,7 @@

    @@ -2115,6 +2115,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2263,7 +2265,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/07/index.html b/archives/2020/07/index.html index 51ff0d691d..db4ebe883a 100644 --- a/archives/2020/07/index.html +++ b/archives/2020/07/index.html @@ -536,12 +536,12 @@

    @@ -1191,6 +1191,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1339,7 +1341,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/08/index.html b/archives/2020/08/index.html index f85da3dcff..e9f4f71124 100644 --- a/archives/2020/08/index.html +++ b/archives/2020/08/index.html @@ -494,7 +494,7 @@

    @@ -2233,6 +2233,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2381,7 +2383,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/08/page/2/index.html b/archives/2020/08/page/2/index.html index e6db20f57a..091a7e882d 100644 --- a/archives/2020/08/page/2/index.html +++ b/archives/2020/08/page/2/index.html @@ -949,6 +949,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1097,7 +1099,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/09/index.html b/archives/2020/09/index.html index d8cf5a2482..bbede9ca89 100644 --- a/archives/2020/09/index.html +++ b/archives/2020/09/index.html @@ -613,8 +613,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -656,7 +656,7 @@

    @@ -676,7 +676,7 @@

      |   @@ -684,7 +684,7 @@

    @@ -702,14 +702,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -839,11 +836,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1375,6 +1375,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1523,7 +1525,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/10/index.html b/archives/2020/10/index.html index c219aa684d..cc1872eb27 100644 --- a/archives/2020/10/index.html +++ b/archives/2020/10/index.html @@ -1347,6 +1347,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1495,7 +1497,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/11/index.html b/archives/2020/11/index.html index b06182c19a..eccc3e3b90 100644 --- a/archives/2020/11/index.html +++ b/archives/2020/11/index.html @@ -1725,6 +1725,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1873,7 +1875,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/12/index.html b/archives/2020/12/index.html index 2974b92c11..b3ff9b0985 100644 --- a/archives/2020/12/index.html +++ b/archives/2020/12/index.html @@ -1754,6 +1754,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1902,7 +1904,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/index.html b/archives/2020/index.html index 920bdec5d8..e9f0f66c7c 100644 --- a/archives/2020/index.html +++ b/archives/2020/index.html @@ -2170,6 +2170,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2318,7 +2320,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/page/10/index.html b/archives/2020/page/10/index.html index 4e85642513..9e09a87f5f 100644 --- a/archives/2020/page/10/index.html +++ b/archives/2020/page/10/index.html @@ -458,7 +458,7 @@

    @@ -716,7 +716,7 @@

    @@ -1243,7 +1243,7 @@

    @@ -1303,12 +1303,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -2098,6 +2098,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2246,7 +2248,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/page/2/index.html b/archives/2020/page/2/index.html index 9622546c27..c3048cd10f 100644 --- a/archives/2020/page/2/index.html +++ b/archives/2020/page/2/index.html @@ -2194,6 +2194,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2342,7 +2344,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2020/page/3/index.html b/archives/2020/page/3/index.html index afef0d4233..c57252b75e 100644 --- a/archives/2020/page/3/index.html +++ b/archives/2020/page/3/index.html @@ -295,8 +295,8 @@

    lucifer

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -338,7 +338,7 @@

    @@ -358,7 +358,7 @@

      |   @@ -366,7 +366,7 @@

    @@ -384,14 +384,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -521,11 +518,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -741,7 +741,7 @@

    @@ -2200,6 +2200,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2348,7 +2350,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/page/4/index.html b/archives/2020/page/4/index.html index ae110e7016..671da9a6d1 100644 --- a/archives/2020/page/4/index.html +++ b/archives/2020/page/4/index.html @@ -957,12 +957,12 @@

    @@ -1162,9 +1162,9 @@

    @@ -1253,8 +1253,8 @@

    - - 一文看懂《最大子序列和问题》 + + 穿上衣服我就不认识你了?来聊聊最长上升子序列

    @@ -1294,9 +1294,9 @@

    @@ -1316,7 +1316,7 @@

      |   @@ -1324,7 +1324,7 @@

    @@ -1342,10 +1342,15 @@

    -

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    +

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    +

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    +

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    +
    +

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    +
    @@ -1473,15 +1476,10 @@

    -

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    -

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    -

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    -
    -

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    -
    +

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    - + 阅读全文 @@ -1491,11 +1489,13 @@

    @@ -2167,6 +2167,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2315,7 +2317,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/page/5/index.html b/archives/2020/page/5/index.html index 6a173ef2c7..e25cb552f5 100644 --- a/archives/2020/page/5/index.html +++ b/archives/2020/page/5/index.html @@ -1050,8 +1050,8 @@

    - - 阿里面试题:如何寻找「两个数组」的中位数? + + 力扣加加闪亮登场~

    @@ -1091,9 +1091,9 @@

    @@ -1113,7 +1113,7 @@

      |   @@ -1121,7 +1121,7 @@

    @@ -1139,11 +1139,12 @@

    -

    一个数组的中位数很容易求,那两个数组呢?

    +

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    +

    @@ -1407,12 +1406,11 @@

    -

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    -

    +

    一个数组的中位数很容易求,那两个数组呢?

    @@ -2101,6 +2101,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2249,7 +2251,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/page/6/index.html b/archives/2020/page/6/index.html index 450f567d90..ac430bfdab 100644 --- a/archives/2020/page/6/index.html +++ b/archives/2020/page/6/index.html @@ -984,7 +984,7 @@

    @@ -1113,7 +1113,7 @@

    @@ -1512,7 +1512,7 @@

    @@ -2116,6 +2116,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2264,7 +2266,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/page/7/index.html b/archives/2020/page/7/index.html index 5bd9738edf..51b0078a6a 100644 --- a/archives/2020/page/7/index.html +++ b/archives/2020/page/7/index.html @@ -2095,6 +2095,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2243,7 +2245,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/page/8/index.html b/archives/2020/page/8/index.html index 729d1c4e0d..cf3993b6c3 100644 --- a/archives/2020/page/8/index.html +++ b/archives/2020/page/8/index.html @@ -1493,7 +1493,7 @@

    @@ -2104,6 +2104,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2252,7 +2254,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/2020/page/9/index.html b/archives/2020/page/9/index.html index 0e53e9bb32..5dab24762d 100644 --- a/archives/2020/page/9/index.html +++ b/archives/2020/page/9/index.html @@ -590,7 +590,7 @@

    @@ -1246,7 +1246,7 @@

    @@ -1378,7 +1378,7 @@

    @@ -1510,7 +1510,7 @@

    @@ -2112,6 +2112,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2260,7 +2262,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/01/index.html b/archives/2021/01/index.html index 9fc4da377e..6b5a3cfbcc 100644 --- a/archives/2021/01/index.html +++ b/archives/2021/01/index.html @@ -1206,6 +1206,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1354,7 +1356,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/02/index.html b/archives/2021/02/index.html index 0adba56d69..f6deaf9a39 100644 --- a/archives/2021/02/index.html +++ b/archives/2021/02/index.html @@ -548,12 +548,12 @@

    @@ -1601,6 +1601,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1749,7 +1751,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/03/index.html b/archives/2021/03/index.html index b9fae3caab..549cf9de8b 100644 --- a/archives/2021/03/index.html +++ b/archives/2021/03/index.html @@ -1776,6 +1776,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1924,7 +1926,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/04/index.html b/archives/2021/04/index.html index 79ce35f0d4..968479943a 100644 --- a/archives/2021/04/index.html +++ b/archives/2021/04/index.html @@ -1317,6 +1317,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1465,7 +1467,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/05/index.html b/archives/2021/05/index.html index 53a71a6067..e3ddc5aed9 100644 --- a/archives/2021/05/index.html +++ b/archives/2021/05/index.html @@ -1475,6 +1475,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1623,7 +1625,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/06/index.html b/archives/2021/06/index.html index 54e37528ee..658defcb32 100644 --- a/archives/2021/06/index.html +++ b/archives/2021/06/index.html @@ -1204,6 +1204,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1352,7 +1354,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git a/archives/2021/07/index.html b/archives/2021/07/index.html index e2f21e8952..10003df6c8 100644 --- a/archives/2021/07/index.html +++ b/archives/2021/07/index.html @@ -1467,6 +1467,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1615,7 +1617,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/08/index.html b/archives/2021/08/index.html index 8e93189edb..f490a1b582 100644 --- a/archives/2021/08/index.html +++ b/archives/2021/08/index.html @@ -1187,6 +1187,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1335,7 +1337,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/09/index.html b/archives/2021/09/index.html index e6276c45cf..315bbd9ac3 100644 --- a/archives/2021/09/index.html +++ b/archives/2021/09/index.html @@ -1582,6 +1582,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1730,7 +1732,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/10/index.html b/archives/2021/10/index.html index f940263c10..a6808a2c43 100644 --- a/archives/2021/10/index.html +++ b/archives/2021/10/index.html @@ -1297,6 +1297,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1445,7 +1447,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/11/index.html b/archives/2021/11/index.html index 22b9b0578b..63948de208 100644 --- a/archives/2021/11/index.html +++ b/archives/2021/11/index.html @@ -1421,6 +1421,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1569,7 +1571,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/12/index.html b/archives/2021/12/index.html index d907528712..b48328c7a7 100644 --- a/archives/2021/12/index.html +++ b/archives/2021/12/index.html @@ -1369,6 +1369,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1517,7 +1519,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/index.html b/archives/2021/index.html index 021008583e..908210effc 100644 --- a/archives/2021/index.html +++ b/archives/2021/index.html @@ -2146,6 +2146,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2294,7 +2296,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/page/2/index.html b/archives/2021/page/2/index.html index b16955ebec..9153a7886f 100644 --- a/archives/2021/page/2/index.html +++ b/archives/2021/page/2/index.html @@ -2100,6 +2100,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2248,7 +2250,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/page/3/index.html b/archives/2021/page/3/index.html index aa030de763..fcacc7f4f2 100644 --- a/archives/2021/page/3/index.html +++ b/archives/2021/page/3/index.html @@ -2139,6 +2139,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2287,7 +2289,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git a/archives/2021/page/4/index.html b/archives/2021/page/4/index.html index d69ea8e7dd..74e1d7a701 100644 --- a/archives/2021/page/4/index.html +++ b/archives/2021/page/4/index.html @@ -2173,6 +2173,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2321,7 +2323,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/page/5/index.html b/archives/2021/page/5/index.html index e91cc85f0b..b967f4581e 100644 --- a/archives/2021/page/5/index.html +++ b/archives/2021/page/5/index.html @@ -1335,12 +1335,12 @@

    @@ -2169,6 +2169,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2317,7 +2319,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2021/page/6/index.html b/archives/2021/page/6/index.html index 5a537a6e02..38224e8ca4 100644 --- a/archives/2021/page/6/index.html +++ b/archives/2021/page/6/index.html @@ -1495,6 +1495,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1643,7 +1645,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/01/index.html b/archives/2022/01/index.html index b027d51bd6..82bbce8c07 100644 --- a/archives/2022/01/index.html +++ b/archives/2022/01/index.html @@ -1053,6 +1053,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1201,7 +1203,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/02/index.html b/archives/2022/02/index.html index f118770dc3..e12e9f01fa 100644 --- a/archives/2022/02/index.html +++ b/archives/2022/02/index.html @@ -1170,6 +1170,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1318,7 +1320,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/03/index.html b/archives/2022/03/index.html index d23f236116..a7be2794d1 100644 --- a/archives/2022/03/index.html +++ b/archives/2022/03/index.html @@ -1189,6 +1189,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1337,7 +1339,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/04/index.html b/archives/2022/04/index.html index 073036e860..c7fd0ae0ca 100644 --- a/archives/2022/04/index.html +++ b/archives/2022/04/index.html @@ -1068,6 +1068,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1216,7 +1218,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/05/index.html b/archives/2022/05/index.html index 3812415dc0..1395408c37 100644 --- a/archives/2022/05/index.html +++ b/archives/2022/05/index.html @@ -1048,6 +1048,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1196,7 +1198,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/07/index.html b/archives/2022/07/index.html index b660d9b231..72cef804b9 100644 --- a/archives/2022/07/index.html +++ b/archives/2022/07/index.html @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/10/index.html b/archives/2022/10/index.html index 1b65f696fb..275a2a6c95 100644 --- a/archives/2022/10/index.html +++ b/archives/2022/10/index.html @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/index.html b/archives/2022/index.html index 0fa61e3a34..0308fabcce 100644 --- a/archives/2022/index.html +++ b/archives/2022/index.html @@ -2147,6 +2147,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2295,7 +2297,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2022/page/2/index.html b/archives/2022/page/2/index.html index f3728ec4c7..eefb91a4ac 100644 --- a/archives/2022/page/2/index.html +++ b/archives/2022/page/2/index.html @@ -1309,6 +1309,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1457,7 +1459,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2023/01/index.html b/archives/2023/01/index.html index 4e50f25275..766737eb38 100644 --- a/archives/2023/01/index.html +++ b/archives/2023/01/index.html @@ -1312,6 +1312,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1460,7 +1462,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2023/02/index.html b/archives/2023/02/index.html index aa00de646b..a0c9b4604e 100644 --- a/archives/2023/02/index.html +++ b/archives/2023/02/index.html @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html index f96d1bb3a3..ded100afea 100644 --- a/archives/2023/03/index.html +++ b/archives/2023/03/index.html @@ -911,6 +911,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1059,7 +1061,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html index fad94bc13e..a3235e21c6 100644 --- a/archives/2023/06/index.html +++ b/archives/2023/06/index.html @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html index 8777dde274..61f34e8451 100644 --- a/archives/2023/08/index.html +++ b/archives/2023/08/index.html @@ -1047,6 +1047,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1195,7 +1197,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2023/11/index.html b/archives/2023/11/index.html index 29ad44350e..73b6497751 100644 --- a/archives/2023/11/index.html +++ b/archives/2023/11/index.html @@ -935,6 +935,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1083,7 +1085,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2023/index.html b/archives/2023/index.html index 9f84b2bdc7..1dcebe352d 100644 --- a/archives/2023/index.html +++ b/archives/2023/index.html @@ -2111,6 +2111,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2259,7 +2261,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2024/01/index.html b/archives/2024/01/index.html index dd13cd78da..a8fa6f9ac4 100644 --- a/archives/2024/01/index.html +++ b/archives/2024/01/index.html @@ -919,6 +919,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1067,7 +1069,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2024/03/index.html b/archives/2024/03/index.html index c3999a8f02..20d4956399 100644 --- a/archives/2024/03/index.html +++ b/archives/2024/03/index.html @@ -284,9 +284,136 @@

    lucifer

    + + + + + + + +
    + + +
    +
    @@ -919,6 +1046,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1067,7 +1196,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2024/index.html b/archives/2024/index.html index d820b29a96..34f529e062 100644 --- a/archives/2024/index.html +++ b/archives/2024/index.html @@ -305,8 +305,8 @@

    lucifer

    - - kuma - css-in-js 的未来? + + How To Make Monney

    @@ -336,7 +336,7 @@

    @@ -346,9 +346,9 @@

    @@ -368,7 +368,7 @@

      |   @@ -376,7 +376,7 @@

    @@ -394,10 +394,10 @@

    -

    kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。

    +

    有哪些赚钱的方法?具体如何操作?它们的赚钱逻辑是什么?什么情况下会亏钱?

    - + 阅读全文 @@ -407,9 +407,7 @@

    @@ -421,7 +419,7 @@

    -
    @@ -513,10 +519,10 @@

    - +

    kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。

    - + 阅读全文 @@ -524,6 +530,14 @@

    +
    + + 前端 + + css-in-js + +
    +
    @@ -1161,6 +1175,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1309,7 +1325,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/archives/index.html b/archives/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/index.html +++ b/archives/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/10/index.html b/archives/page/10/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/10/index.html +++ b/archives/page/10/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/11/index.html b/archives/page/11/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/11/index.html +++ b/archives/page/11/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/12/index.html b/archives/page/12/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/12/index.html +++ b/archives/page/12/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/13/index.html b/archives/page/13/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/13/index.html +++ b/archives/page/13/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/14/index.html b/archives/page/14/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/14/index.html +++ b/archives/page/14/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/15/index.html b/archives/page/15/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/15/index.html +++ b/archives/page/15/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/16/index.html b/archives/page/16/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/16/index.html +++ b/archives/page/16/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/17/index.html b/archives/page/17/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/17/index.html +++ b/archives/page/17/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/18/index.html b/archives/page/18/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/18/index.html +++ b/archives/page/18/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/19/index.html b/archives/page/19/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/19/index.html +++ b/archives/page/19/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/2/index.html b/archives/page/2/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/20/index.html b/archives/page/20/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/20/index.html +++ b/archives/page/20/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/3/index.html b/archives/page/3/index.html index 01ea2e4ffc..bd4ecccbfe 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/4/index.html b/archives/page/4/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/4/index.html +++ b/archives/page/4/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/5/index.html b/archives/page/5/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/5/index.html +++ b/archives/page/5/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/6/index.html b/archives/page/6/index.html index 01ea2e4ffc..bd4ecccbfe 100644 --- a/archives/page/6/index.html +++ b/archives/page/6/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/7/index.html b/archives/page/7/index.html index 01ea2e4ffc..bd4ecccbfe 100644 --- a/archives/page/7/index.html +++ b/archives/page/7/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/8/index.html b/archives/page/8/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/8/index.html +++ b/archives/page/8/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/page/9/index.html b/archives/page/9/index.html index a6793e1cc0..880a7ed904 100644 --- a/archives/page/9/index.html +++ b/archives/page/9/index.html @@ -285,11 +285,11 @@

    2024

    - - + + - kuma - css-in-js 的未来? + How To Make Monney @@ -299,11 +299,11 @@

    2024

    - - + + - 2024-02-15 + kuma - css-in-js 的未来? @@ -1729,11 +1729,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度? + 字节跳动的算法面试题是什么难度?(第二弹) @@ -1743,11 +1743,11 @@

    2020

    - + - 字节跳动的算法面试题是什么难度?(第二弹) + 字节跳动的算法面试题是什么难度? @@ -1967,11 +1967,11 @@

    2020

    - + - 一文看懂《最大子序列和问题》 + 穿上衣服我就不认识你了?来聊聊最长上升子序列 @@ -1981,11 +1981,11 @@

    2020

    - + - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + 一文看懂《最大子序列和问题》 @@ -2093,11 +2093,11 @@

    2020

    - + - 阿里面试题:如何寻找「两个数组」的中位数? + 力扣加加闪亮登场~ @@ -2121,11 +2121,11 @@

    2020

    - + - 力扣加加闪亮登场~ + 阿里面试题:如何寻找「两个数组」的中位数? @@ -3551,6 +3551,8 @@

    2009

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -3699,7 +3701,7 @@

    2009

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/atom.xml b/atom.xml index 92972ff5bb..aca0c623e1 100644 --- a/atom.xml +++ b/atom.xml @@ -6,7 +6,7 @@ - 2024-03-10T13:06:01.037Z + 2024-03-10T13:11:10.453Z https://lucifer.ren/blog/ @@ -16,6 +16,26 @@ Hexo + + How To Make Monney + + https://lucifer.ren/blog/2024/03/10/make-money/ + 2024-03-09T16:00:00.000Z + 2024-03-10T13:11:10.453Z + + + + <p>有哪些赚钱的方法?具体如何操作?它们的赚钱逻辑是什么?什么情况下会亏钱?</p> + + + + + + + + + + kuma - css-in-js 的未来? @@ -40,28 +60,6 @@ - - - - https://lucifer.ren/blog/2024/02/15/make-money/ - 2024-02-15T04:00:38.279Z - 2024-03-10T06:28:39.107Z - - - - - - - - - - - - - - - - 关于 Error Boundaries, 你需要知道的一切 diff --git "a/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" "b/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" index 07045b37fc..8bb86a9675 100644 --- "a/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" +++ "b/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" @@ -2188,6 +2188,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2336,7 +2338,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" "b/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" index 67ebf8ea3c..b85c19b1c4 100644 --- "a/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" +++ "b/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" @@ -2198,6 +2198,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2346,7 +2348,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" "b/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" index fc17192fac..a248df2f99 100644 --- "a/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" +++ "b/categories/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" @@ -1348,6 +1348,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1496,7 +1498,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/CD/index.html b/categories/CD/index.html index 86abcef693..484e0cbbf9 100644 --- a/categories/CD/index.html +++ b/categories/CD/index.html @@ -937,6 +937,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1085,7 +1087,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/Easy/index.html b/categories/Easy/index.html index 422c46a03f..65a50919a5 100644 --- a/categories/Easy/index.html +++ b/categories/Easy/index.html @@ -346,7 +346,7 @@

    @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/Github/index.html b/categories/Github/index.html index 2aae945afa..c7c0c0905c 100644 --- a/categories/Github/index.html +++ b/categories/Github/index.html @@ -1055,6 +1055,8 @@

    前言
    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1203,7 +1205,7 @@

    前言
    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/Hard/index.html b/categories/Hard/index.html index 9c738e1bf6..e857da231b 100644 --- a/categories/Hard/index.html +++ b/categories/Hard/index.html @@ -602,7 +602,7 @@

    @@ -1184,6 +1184,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1332,7 +1334,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/LeetCode/LeetCode\351\242\230\350\247\243\344\271\246/index.html" "b/categories/LeetCode/LeetCode\351\242\230\350\247\243\344\271\246/index.html" index 420750a1bc..8776f2b5d1 100644 --- "a/categories/LeetCode/LeetCode\351\242\230\350\247\243\344\271\246/index.html" +++ "b/categories/LeetCode/LeetCode\351\242\230\350\247\243\344\271\246/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/LeetCode/index.html b/categories/LeetCode/index.html index bcbe4fe740..20d1177f3c 100644 --- a/categories/LeetCode/index.html +++ b/categories/LeetCode/index.html @@ -2174,6 +2174,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2322,7 +2324,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/LeetCode/page/2/index.html b/categories/LeetCode/page/2/index.html index 3460110982..bbdc3dac22 100644 --- a/categories/LeetCode/page/2/index.html +++ b/categories/LeetCode/page/2/index.html @@ -297,8 +297,8 @@

    lucifer

    - - 一文看懂《最大子序列和问题》 + + 穿上衣服我就不认识你了?来聊聊最长上升子序列

    @@ -338,9 +338,9 @@

    @@ -360,7 +360,7 @@

      |   @@ -368,7 +368,7 @@

    @@ -386,10 +386,15 @@

    -

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    +

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    +

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    +

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    +
    +

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    +
    @@ -517,15 +520,10 @@

    -

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    -

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    -

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    -
    -

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    -
    +

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    - + 阅读全文 @@ -535,11 +533,13 @@

    @@ -1131,7 +1131,7 @@

    @@ -2132,6 +2132,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2280,7 +2282,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/categories/LeetCode/page/3/index.html b/categories/LeetCode/page/3/index.html index 376cfa5dce..edd56c7883 100644 --- a/categories/LeetCode/page/3/index.html +++ b/categories/LeetCode/page/3/index.html @@ -478,7 +478,7 @@

    @@ -610,7 +610,7 @@

    @@ -1211,6 +1211,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1359,7 +1361,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/LeetCode/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" "b/categories/LeetCode/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" index 0c7f0e44ea..67dd518d8d 100644 --- "a/categories/LeetCode/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" +++ "b/categories/LeetCode/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/LeetCode/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" "b/categories/LeetCode/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" index 0e92167b7c..ff15974c97 100644 --- "a/categories/LeetCode/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" +++ "b/categories/LeetCode/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" @@ -1067,6 +1067,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1215,7 +1217,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/Medium/index.html b/categories/Medium/index.html index 6e9ded681b..fa078b199f 100644 --- a/categories/Medium/index.html +++ b/categories/Medium/index.html @@ -346,9 +346,9 @@

    @@ -480,7 +480,7 @@

    @@ -1062,6 +1062,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1210,7 +1212,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/React/index.html b/categories/React/index.html index 4dd000bfc8..2ae8b98dea 100644 --- a/categories/React/index.html +++ b/categories/React/index.html @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/TypeScript/index.html b/categories/TypeScript/index.html index 3670edf4ec..89a07008ff 100644 --- a/categories/TypeScript/index.html +++ b/categories/TypeScript/index.html @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/devtool/index.html b/categories/devtool/index.html index 33ffe07ac9..9036118218 100644 --- a/categories/devtool/index.html +++ b/categories/devtool/index.html @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/index.html b/categories/index.html index eaf632ecc0..68c2113a59 100644 --- a/categories/index.html +++ b/categories/index.html @@ -941,6 +941,13 @@

    所有分类

    + + + + (1) + + + @@ -1690,6 +1697,8 @@

    所有分类

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1838,7 +1847,7 @@

    所有分类

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/categories/vite/index.html b/categories/vite/index.html index 2fc542a2db..3443c4ab3a 100644 --- a/categories/vite/index.html +++ b/categories/vite/index.html @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" "b/categories/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" index 22a4474c20..78a5cc87d5 100644 --- "a/categories/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" +++ "b/categories/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\344\270\255\347\255\211/index.html" "b/categories/\344\270\255\347\255\211/index.html" index 62470dc648..4edb5d162c 100644 --- "a/categories/\344\270\255\347\255\211/index.html" +++ "b/categories/\344\270\255\347\255\211/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\344\271\246/index.html" "b/categories/\344\271\246/index.html" index ba9814e8df..95679b502e 100644 --- "a/categories/\344\271\246/index.html" +++ "b/categories/\344\271\246/index.html" @@ -1203,6 +1203,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1351,7 +1353,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\344\271\246/\347\256\227\346\263\225/index.html" "b/categories/\344\271\246/\347\256\227\346\263\225/index.html" index eb1e12379e..05566ad9a6 100644 --- "a/categories/\344\271\246/\347\256\227\346\263\225/index.html" +++ "b/categories/\344\271\246/\347\256\227\346\263\225/index.html" @@ -1064,6 +1064,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1212,7 +1214,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\344\271\246\345\215\225/index.html" "b/categories/\344\271\246\345\215\225/index.html" index dd6524059d..177bfc8da2 100644 --- "a/categories/\344\271\246\345\215\225/index.html" +++ "b/categories/\344\271\246\345\215\225/index.html" @@ -952,6 +952,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1100,7 +1102,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\344\271\246\346\221\230/index.html" "b/categories/\344\271\246\346\221\230/index.html" index 580dbc4d37..90505c7bcc 100644 --- "a/categories/\344\271\246\346\221\230/index.html" +++ "b/categories/\344\271\246\346\221\230/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\344\272\214\345\210\206/index.html" "b/categories/\344\272\214\345\210\206/index.html" index 64977674de..c877ba95d2 100644 --- "a/categories/\344\272\214\345\210\206/index.html" +++ "b/categories/\344\272\214\345\210\206/index.html" @@ -1075,6 +1075,8 @@

    前言
    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1223,7 +1225,7 @@

    前言
    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\344\272\214\345\217\211\346\240\221/index.html" "b/categories/\344\272\214\345\217\211\346\240\221/index.html" index cc0bff8e9d..3f95eaa235 100644 --- "a/categories/\344\272\214\345\217\211\346\240\221/index.html" +++ "b/categories/\344\272\214\345\217\211\346\240\221/index.html" @@ -932,6 +932,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1080,7 +1082,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" "b/categories/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" index 2202a47f66..96f4c9067e 100644 --- "a/categories/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" +++ "b/categories/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" @@ -1314,6 +1314,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1462,7 +1464,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" "b/categories/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" index 0820990fe7..0c9975cd2d 100644 --- "a/categories/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" +++ "b/categories/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" @@ -1198,6 +1198,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1346,7 +1348,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/TypeScript/index.html" "b/categories/\345\211\215\347\253\257/TypeScript/index.html" index afd97eeb8f..2b86f731ba 100644 --- "a/categories/\345\211\215\347\253\257/TypeScript/index.html" +++ "b/categories/\345\211\215\347\253\257/TypeScript/index.html" @@ -1864,6 +1864,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2012,7 +2014,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/TypeScript/\346\263\233\345\236\213/index.html" "b/categories/\345\211\215\347\253\257/TypeScript/\346\263\233\345\236\213/index.html" index ed2342e518..bf439a72e3 100644 --- "a/categories/\345\211\215\347\253\257/TypeScript/\346\263\233\345\236\213/index.html" +++ "b/categories/\345\211\215\347\253\257/TypeScript/\346\263\233\345\236\213/index.html" @@ -1089,6 +1089,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1237,7 +1239,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/css-in-js/index.html" "b/categories/\345\211\215\347\253\257/css-in-js/index.html" index 3686482355..2ab73ea3de 100644 --- "a/categories/\345\211\215\347\253\257/css-in-js/index.html" +++ "b/categories/\345\211\215\347\253\257/css-in-js/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/eslint/index.html" "b/categories/\345\211\215\347\253\257/eslint/index.html" index b9ad8cf59f..06459e5829 100644 --- "a/categories/\345\211\215\347\253\257/eslint/index.html" +++ "b/categories/\345\211\215\347\253\257/eslint/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/index.html" "b/categories/\345\211\215\347\253\257/index.html" index 809bc1340d..303c6a9fd2 100644 --- "a/categories/\345\211\215\347\253\257/index.html" +++ "b/categories/\345\211\215\347\253\257/index.html" @@ -2172,6 +2172,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2320,7 +2322,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/page/2/index.html" "b/categories/\345\211\215\347\253\257/page/2/index.html" index 214d35b4c7..56fe580529 100644 --- "a/categories/\345\211\215\347\253\257/page/2/index.html" +++ "b/categories/\345\211\215\347\253\257/page/2/index.html" @@ -2219,6 +2219,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2367,7 +2369,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/page/3/index.html" "b/categories/\345\211\215\347\253\257/page/3/index.html" index 4f77640e2d..78f1309682 100644 --- "a/categories/\345\211\215\347\253\257/page/3/index.html" +++ "b/categories/\345\211\215\347\253\257/page/3/index.html" @@ -1864,6 +1864,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2012,7 +2014,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/web-component/index.html" "b/categories/\345\211\215\347\253\257/web-component/index.html" index 516ec7318f..5f5f21c15f 100644 --- "a/categories/\345\211\215\347\253\257/web-component/index.html" +++ "b/categories/\345\211\215\347\253\257/web-component/index.html" @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/webkit/index.html" "b/categories/\345\211\215\347\253\257/webkit/index.html" index c645953f98..f556f9d916 100644 --- "a/categories/\345\211\215\347\253\257/webkit/index.html" +++ "b/categories/\345\211\215\347\253\257/webkit/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/\346\265\213\350\257\225/index.html" "b/categories/\345\211\215\347\253\257/\346\265\213\350\257\225/index.html" index 34fe6a4aa2..2e848bf2dc 100644 --- "a/categories/\345\211\215\347\253\257/\346\265\213\350\257\225/index.html" +++ "b/categories/\345\211\215\347\253\257/\346\265\213\350\257\225/index.html" @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/\346\265\217\350\247\210\345\231\250/index.html" "b/categories/\345\211\215\347\253\257/\346\265\217\350\247\210\345\231\250/index.html" index 46c20964c7..68b7520f5f 100644 --- "a/categories/\345\211\215\347\253\257/\346\265\217\350\247\210\345\231\250/index.html" +++ "b/categories/\345\211\215\347\253\257/\346\265\217\350\247\210\345\231\250/index.html" @@ -1059,6 +1059,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1207,7 +1209,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/\347\256\227\346\263\225/index.html" "b/categories/\345\211\215\347\253\257/\347\256\227\346\263\225/index.html" index 1fec47b936..237e33d43d 100644 --- "a/categories/\345\211\215\347\253\257/\347\256\227\346\263\225/index.html" +++ "b/categories/\345\211\215\347\253\257/\347\256\227\346\263\225/index.html" @@ -1335,6 +1335,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1483,7 +1485,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\253\257/\347\273\204\344\273\266\345\214\226/index.html" "b/categories/\345\211\215\347\253\257/\347\273\204\344\273\266\345\214\226/index.html" index 319ae03d0b..de7018c155 100644 --- "a/categories/\345\211\215\347\253\257/\347\273\204\344\273\266\345\214\226/index.html" +++ "b/categories/\345\211\215\347\253\257/\347\273\204\344\273\266\345\214\226/index.html" @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\274\200\345\222\214/index.html" "b/categories/\345\211\215\347\274\200\345\222\214/index.html" index 6e89fc9b4f..f4ca351d6d 100644 --- "a/categories/\345\211\215\347\274\200\345\222\214/index.html" +++ "b/categories/\345\211\215\347\274\200\345\222\214/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\211\215\347\274\200\345\222\214/\344\272\214\347\273\264\345\211\215\347\274\200\345\222\214/index.html" "b/categories/\345\211\215\347\274\200\345\222\214/\344\272\214\347\273\264\345\211\215\347\274\200\345\222\214/index.html" index 2742d5245d..8d9827d944 100644 --- "a/categories/\345\211\215\347\274\200\345\222\214/\344\272\214\347\273\264\345\211\215\347\274\200\345\222\214/index.html" +++ "b/categories/\345\211\215\347\274\200\345\222\214/\344\272\214\347\273\264\345\211\215\347\274\200\345\222\214/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" "b/categories/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" index 94857a4523..4c568bbfa9 100644 --- "a/categories/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" +++ "b/categories/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" @@ -2188,6 +2188,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2336,7 +2338,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" "b/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" index 219d8eabd1..3838eb0f96 100644 --- "a/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" +++ "b/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" @@ -2200,6 +2200,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2348,7 +2350,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" "b/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" index 5532cab791..e50656479e 100644 --- "a/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" +++ "b/categories/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" @@ -1616,6 +1616,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1764,7 +1766,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" "b/categories/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" index 34f339cdfe..e42d887b92 100644 --- "a/categories/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" +++ "b/categories/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" @@ -1059,6 +1059,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1207,7 +1209,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\221\275\344\273\244\350\241\214/index.html" "b/categories/\345\221\275\344\273\244\350\241\214/index.html" index e03b860836..a1558b6241 100644 --- "a/categories/\345\221\275\344\273\244\350\241\214/index.html" +++ "b/categories/\345\221\275\344\273\244\350\241\214/index.html" @@ -926,6 +926,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/categories/\345\233\276/index.html" "b/categories/\345\233\276/index.html" index c80a7c0659..ff63bc8f14 100644 --- "a/categories/\345\233\276/index.html" +++ "b/categories/\345\233\276/index.html" @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\240\206/index.html" "b/categories/\345\240\206/index.html" index e8004915b5..e561192851 100644 --- "a/categories/\345\240\206/index.html" +++ "b/categories/\345\240\206/index.html" @@ -1080,6 +1080,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1228,7 +1230,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\245\275\346\234\252\346\235\245/index.html" "b/categories/\345\245\275\346\234\252\346\235\245/index.html" index 73b8524dd7..0e86dcadb5 100644 --- "a/categories/\345\245\275\346\234\252\346\235\245/index.html" +++ "b/categories/\345\245\275\346\234\252\346\235\245/index.html" @@ -930,6 +930,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1078,7 +1080,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" "b/categories/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" index 74c056f41d..b1bec8eb20 100644 --- "a/categories/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" +++ "b/categories/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" @@ -475,7 +475,7 @@

    @@ -1057,6 +1057,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1205,7 +1207,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\267\245\345\205\267/Chrome/index.html" "b/categories/\345\267\245\345\205\267/Chrome/index.html" index a847e7e856..8a6e8862c0 100644 --- "a/categories/\345\267\245\345\205\267/Chrome/index.html" +++ "b/categories/\345\267\245\345\205\267/Chrome/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\267\245\345\205\267/VSCODE/index.html" "b/categories/\345\267\245\345\205\267/VSCODE/index.html" index 442702aced..df5e21ae56 100644 --- "a/categories/\345\267\245\345\205\267/VSCODE/index.html" +++ "b/categories/\345\267\245\345\205\267/VSCODE/index.html" @@ -1184,6 +1184,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1332,7 +1334,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\267\245\345\205\267/index.html" "b/categories/\345\267\245\345\205\267/index.html" index 0e39a2baf9..ecd0b6f186 100644 --- "a/categories/\345\267\245\345\205\267/index.html" +++ "b/categories/\345\267\245\345\205\267/index.html" @@ -1447,6 +1447,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1595,7 +1597,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/categories/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/categories/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" index b679c18696..af5a5fecd8 100644 --- "a/categories/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" +++ "b/categories/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\345\274\202\350\256\256\357\274\201/index.html" "b/categories/\345\274\202\350\256\256\357\274\201/index.html" index dbb2ef009b..ee0607e2c9 100644 --- "a/categories/\345\274\202\350\256\256\357\274\201/index.html" +++ "b/categories/\345\274\202\350\256\256\357\274\201/index.html" @@ -924,6 +924,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1072,7 +1074,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" "b/categories/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" index 798438685b..33a76dee1e 100644 --- "a/categories/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" +++ "b/categories/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" @@ -1051,6 +1051,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1199,7 +1201,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/D2/index.html" "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/D2/index.html" index 8ac98a850c..0c30591b49 100644 --- "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/D2/index.html" +++ "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/D2/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/Google-IO/index.html" "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/Google-IO/index.html" index cf2ebd9ffe..be5aac0f1d 100644 --- "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/Google-IO/index.html" +++ "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/Google-IO/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/JSConf/index.html" "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/JSConf/index.html" index 8bd742137d..fafdb25413 100644 --- "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/JSConf/index.html" +++ "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/JSConf/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/QCon/index.html" "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/QCon/index.html" index f065b6b80e..0227f689bf 100644 --- "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/QCon/index.html" +++ "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/QCon/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/React-Conf/index.html" "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/React-Conf/index.html" index c85728e17e..4cffdf9e40 100644 --- "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/React-Conf/index.html" +++ "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/React-Conf/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" index cb635265a3..213edec66e 100644 --- "a/categories/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" +++ "b/categories/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\217\222\344\273\266/index.html" "b/categories/\346\217\222\344\273\266/index.html" index 594bedbfb3..e96562ae11 100644 --- "a/categories/\346\217\222\344\273\266/index.html" +++ "b/categories/\346\217\222\344\273\266/index.html" @@ -1451,6 +1451,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1599,7 +1601,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\217\222\344\273\266/\346\265\217\350\247\210\345\231\250\346\217\222\344\273\266/index.html" "b/categories/\346\217\222\344\273\266/\346\265\217\350\247\210\345\231\250\346\217\222\344\273\266/index.html" index d1a1d2fe3f..a51f6dff5f 100644 --- "a/categories/\346\217\222\344\273\266/\346\265\217\350\247\210\345\231\250\346\217\222\344\273\266/index.html" +++ "b/categories/\346\217\222\344\273\266/\346\265\217\350\247\210\345\231\250\346\217\222\344\273\266/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\217\222\344\273\266/\347\256\227\346\263\225/index.html" "b/categories/\346\217\222\344\273\266/\347\256\227\346\263\225/index.html" index 7e9d40ef2f..a065fe9b23 100644 --- "a/categories/\346\217\222\344\273\266/\347\256\227\346\263\225/index.html" +++ "b/categories/\346\217\222\344\273\266/\347\256\227\346\263\225/index.html" @@ -926,6 +926,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/categories/\346\220\234\347\264\242/index.html" "b/categories/\346\220\234\347\264\242/index.html" index 4c15ef76c6..33ead906ec 100644 --- "a/categories/\346\220\234\347\264\242/index.html" +++ "b/categories/\346\220\234\347\264\242/index.html" @@ -974,6 +974,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1122,7 +1124,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/categories/\346\225\260\345\255\246/index.html" "b/categories/\346\225\260\345\255\246/index.html" index 2b8782c708..a2b731e6e3 100644 --- "a/categories/\346\225\260\345\255\246/index.html" +++ "b/categories/\346\225\260\345\255\246/index.html" @@ -412,12 +412,12 @@

    @@ -934,6 +934,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1082,7 +1084,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\345\255\246/\347\256\227\346\263\225/index.html" "b/categories/\346\225\260\345\255\246/\347\256\227\346\263\225/index.html" index 3833e2e620..3999a2255f 100644 --- "a/categories/\346\225\260\345\255\246/\347\256\227\346\263\225/index.html" +++ "b/categories/\346\225\260\345\255\246/\347\256\227\346\263\225/index.html" @@ -412,12 +412,12 @@

    @@ -934,6 +934,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1082,7 +1084,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/hashtable/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/hashtable/index.html" index a948496be9..3e856342b9 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/hashtable/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/hashtable/index.html" @@ -354,9 +354,9 @@

    @@ -620,7 +620,7 @@

    @@ -749,7 +749,7 @@

    @@ -881,7 +881,7 @@

    @@ -1013,7 +1013,7 @@

    @@ -1593,6 +1593,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1741,7 +1743,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" index ec6d9824a1..ba7059593a 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" @@ -321,8 +321,8 @@

    lucifer

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -364,7 +364,7 @@

    @@ -384,7 +384,7 @@

      |   @@ -392,7 +392,7 @@

    @@ -410,14 +410,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -547,11 +544,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -767,7 +767,7 @@

    @@ -1027,9 +1027,9 @@

    @@ -1161,7 +1161,7 @@

    @@ -1290,9 +1290,9 @@

    @@ -2154,6 +2154,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2302,7 +2304,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" index 5d7be451a5..1efa74902e 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" @@ -473,7 +473,7 @@

    @@ -602,7 +602,7 @@

    @@ -731,7 +731,7 @@

    @@ -863,7 +863,7 @@

    @@ -1002,7 +1002,7 @@

    @@ -1265,7 +1265,7 @@

    @@ -1397,7 +1397,7 @@

    @@ -1529,7 +1529,7 @@

    @@ -2133,6 +2133,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2281,7 +2283,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" index 700507cf85..e71a2fc9e8 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" @@ -340,7 +340,7 @@

    @@ -473,7 +473,7 @@

    @@ -1722,6 +1722,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1870,7 +1872,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/index.html" index 1f7d7aa18a..c576766823 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221/index.html" @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\233\276/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\233\276/index.html" index bf60966c8f..e50b199413 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\233\276/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\233\276/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\347\254\246\344\270\262/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\347\254\246\344\270\262/index.html" index 5427c89f1a..bdf5d9c5bb 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\347\254\246\344\270\262/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\347\254\246\344\270\262/index.html" @@ -348,7 +348,7 @@

    @@ -487,7 +487,7 @@

    @@ -1069,6 +1069,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1217,7 +1219,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221/index.html" index 8d31b79718..1063a4ffff 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221/index.html" @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204/index.html" index 451cd201e7..2a39ee6c40 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204/index.html" @@ -321,8 +321,8 @@

    lucifer

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -364,7 +364,7 @@

    @@ -384,7 +384,7 @@

      |   @@ -392,7 +392,7 @@

    @@ -410,14 +410,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -547,11 +544,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -767,7 +767,7 @@

    @@ -898,9 +898,9 @@

    @@ -1032,7 +1032,7 @@

    @@ -1293,7 +1293,7 @@

    @@ -1422,7 +1422,7 @@

    @@ -1561,7 +1561,7 @@

    @@ -2141,6 +2141,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2289,7 +2291,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\225/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\225/index.html" index 121bea12b9..49a3de728f 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\225/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\225/index.html" @@ -1460,6 +1460,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1608,7 +1610,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250/index.html" index 0d5659d35f..9250772ccb 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250/index.html" @@ -348,7 +348,7 @@

    @@ -487,7 +487,7 @@

    @@ -1070,6 +1070,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1218,7 +1220,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\344\272\214\345\217\211\346\240\221/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\344\272\214\345\217\211\346\240\221/index.html" index c1ff64fa9e..26b0b9c982 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\344\272\214\345\217\211\346\240\221/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\344\272\214\345\217\211\346\240\221/index.html" @@ -936,6 +936,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1084,7 +1086,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\215\225\350\260\203\346\240\210/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\215\225\350\260\203\346\240\210/index.html" index fe5b500c99..e5dab27bfb 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\215\225\350\260\203\346\240\210/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\215\225\350\260\203\346\240\210/index.html" @@ -1050,6 +1050,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1198,7 +1200,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\255\227\347\254\246\344\270\262/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\255\227\347\254\246\344\270\262/index.html" index 9ce48d356d..a8d06be613 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\255\227\347\254\246\344\270\262/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\345\255\227\347\254\246\344\270\262/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\346\225\260\347\273\204/index.html" "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\346\225\260\347\273\204/index.html" index cc558b8dce..6935f36203 100644 --- "a/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\346\225\260\347\273\204/index.html" +++ "b/categories/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\346\225\260\347\273\204/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\227\245\350\256\260/index.html" "b/categories/\346\227\245\350\256\260/index.html" index 8a5864b3e4..7b48fb25a8 100644 --- "a/categories/\346\227\245\350\256\260/index.html" +++ "b/categories/\346\227\245\350\256\260/index.html" @@ -1048,6 +1048,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1196,7 +1198,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\227\245\350\256\260/\346\212\200\346\234\257/index.html" "b/categories/\346\227\245\350\256\260/\346\212\200\346\234\257/index.html" index 424f133e7a..f351330f61 100644 --- "a/categories/\346\227\245\350\256\260/\346\212\200\346\234\257/index.html" +++ "b/categories/\346\227\245\350\256\260/\346\212\200\346\234\257/index.html" @@ -1048,6 +1048,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1196,7 +1198,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\230\245\346\213\233/index.html" "b/categories/\346\230\245\346\213\233/index.html" index 619d7f32a2..ee6c0fe521 100644 --- "a/categories/\346\230\245\346\213\233/index.html" +++ "b/categories/\346\230\245\346\213\233/index.html" @@ -1231,6 +1231,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1379,7 +1381,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\240\210/index.html" "b/categories/\346\240\210/index.html" index 8de0257ad6..f60d424da5 100644 --- "a/categories/\346\240\210/index.html" +++ "b/categories/\346\240\210/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\240\221/index.html" "b/categories/\346\240\221/index.html" index 243bd01424..42348da1f2 100644 --- "a/categories/\346\240\221/index.html" +++ "b/categories/\346\240\221/index.html" @@ -932,6 +932,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1080,7 +1082,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\240\241\346\213\233/index.html" "b/categories/\346\240\241\346\213\233/index.html" index 41c21c8636..a20e4ef1b0 100644 --- "a/categories/\346\240\241\346\213\233/index.html" +++ "b/categories/\346\240\241\346\213\233/index.html" @@ -1459,6 +1459,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1607,7 +1609,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" "b/categories/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" index 15a600ab1a..054a6bfe66 100644 --- "a/categories/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" +++ "b/categories/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-09/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-09/index.html" index 4d8ee4f8c1..d90ed22695 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-09/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-09/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-10/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-10/index.html" index b0f23f2f47..b69b4ee889 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-10/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-10/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-11/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-11/index.html" index 4cbc245594..e5238fa6bf 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-11/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-11/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-12/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-12/index.html" index cddc22d582..1b9c7cb834 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-12/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2019-12/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-01/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-01/index.html" index a3670b194f..4661101471 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-01/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-01/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-03/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-03/index.html" index 7ea72f29b4..cf5754849c 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-03/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2020-03/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2022-04/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2022-04/index.html" index 813a40de0f..4106bd26d5 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2022-04/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/2022-04/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" index 3d857dd012..8b1de7c88f 100644 --- "a/categories/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" +++ "b/categories/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" @@ -1695,6 +1695,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1843,7 +1845,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\265\217\350\247\210\345\231\250/index.html" "b/categories/\346\265\217\350\247\210\345\231\250/index.html" index 6c974bcb12..fe16031e30 100644 --- "a/categories/\346\265\217\350\247\210\345\231\250/index.html" +++ "b/categories/\346\265\217\350\247\210\345\231\250/index.html" @@ -1056,6 +1056,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1204,7 +1206,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\265\217\350\247\210\345\231\250/webkit/index.html" "b/categories/\346\265\217\350\247\210\345\231\250/webkit/index.html" index c645953f98..f556f9d916 100644 --- "a/categories/\346\265\217\350\247\210\345\231\250/webkit/index.html" +++ "b/categories/\346\265\217\350\247\210\345\231\250/webkit/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\346\265\217\350\247\210\345\231\250/\344\272\213\344\273\266/index.html" "b/categories/\346\265\217\350\247\210\345\231\250/\344\272\213\344\273\266/index.html" index 6b55e7adaa..781f4c1a01 100644 --- "a/categories/\346\265\217\350\247\210\345\231\250/\344\272\213\344\273\266/index.html" +++ "b/categories/\346\265\217\350\247\210\345\231\250/\344\272\213\344\273\266/index.html" @@ -924,6 +924,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1072,7 +1074,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\220\206\350\264\242/index.html" "b/categories/\347\220\206\350\264\242/index.html" new file mode 100644 index 0000000000..9ff2d0bf70 --- /dev/null +++ "b/categories/\347\220\206\350\264\242/index.html" @@ -0,0 +1,1398 @@ + + + + + + + Category: 理财 | lucifer的网络博客 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +

    lucifer

    + + + + + + + +
    + + + + +
    + + +
    +
    + +
    + + + +
    + + + + + + + + + + +
    + + +
    + + + +
    + + + + + + + + + + + + +
    + + +
    + + + +
    + +
    + 本站使用 + Material X + 作为主题 。 +
    + + 载入天数...载入时分秒... + +
    + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\347\224\265\345\275\261/index.html" "b/categories/\347\224\265\345\275\261/index.html" index b629600f32..ca279bd29c 100644 --- "a/categories/\347\224\265\345\275\261/index.html" +++ "b/categories/\347\224\265\345\275\261/index.html" @@ -1049,6 +1049,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1197,7 +1199,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\224\265\345\275\261/\350\247\202\345\220\216\346\204\237/index.html" "b/categories/\347\224\265\345\275\261/\350\247\202\345\220\216\346\204\237/index.html" index 2485887607..84435b35af 100644 --- "a/categories/\347\224\265\345\275\261/\350\247\202\345\220\216\346\204\237/index.html" +++ "b/categories/\347\224\265\345\275\261/\350\247\202\345\220\216\346\204\237/index.html" @@ -1049,6 +1049,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1197,7 +1199,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\231\276\345\272\246/index.html" "b/categories/\347\231\276\345\272\246/index.html" index aeb4712f26..4a97569480 100644 --- "a/categories/\347\231\276\345\272\246/index.html" +++ "b/categories/\347\231\276\345\272\246/index.html" @@ -930,6 +930,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1078,7 +1080,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/BFS/index.html" "b/categories/\347\256\227\346\263\225/BFS/index.html" index 99f6975a2d..183b1326d1 100644 --- "a/categories/\347\256\227\346\263\225/BFS/index.html" +++ "b/categories/\347\256\227\346\263\225/BFS/index.html" @@ -407,12 +407,12 @@

    @@ -477,9 +477,9 @@

    @@ -1063,6 +1063,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1211,7 +1213,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/DFS/index.html" "b/categories/\347\256\227\346\263\225/DFS/index.html" index 5f5313a85b..fda76e36ea 100644 --- "a/categories/\347\256\227\346\263\225/DFS/index.html" +++ "b/categories/\347\256\227\346\263\225/DFS/index.html" @@ -346,7 +346,7 @@

    @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/index.html" "b/categories/\347\256\227\346\263\225/index.html" index 058f01cdfb..18ffb48761 100644 --- "a/categories/\347\256\227\346\263\225/index.html" +++ "b/categories/\347\256\227\346\263\225/index.html" @@ -1146,8 +1146,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -1189,7 +1189,7 @@

    @@ -1209,7 +1209,7 @@

      |   @@ -1217,7 +1217,7 @@

    @@ -1235,14 +1235,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -1372,11 +1369,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1460,7 +1460,7 @@

    @@ -2244,6 +2244,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2392,7 +2394,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\347\256\227\346\263\225/page/2/index.html" "b/categories/\347\256\227\346\263\225/page/2/index.html" index b2c2a10db4..5f3ab228a5 100644 --- "a/categories/\347\256\227\346\263\225/page/2/index.html" +++ "b/categories/\347\256\227\346\263\225/page/2/index.html" @@ -399,12 +399,12 @@

    @@ -469,9 +469,9 @@

    @@ -603,7 +603,7 @@

    @@ -691,8 +691,8 @@

    - - 阿里面试题:如何寻找「两个数组」的中位数? + + 【LeetCode日记】 1162. 地图分析

    @@ -732,9 +732,9 @@

    @@ -754,7 +754,7 @@

      |   @@ -762,7 +762,7 @@

    @@ -780,11 +780,15 @@

    -

    一个数组的中位数很容易求,那两个数组呢?

    +

    LeetCode 上有很多小岛题,虽然官方没有这个标签, 但是在我这里都差不多。不管是思路还是套路都比较类似,大家可以结合起来练习。

    +

    - + 阅读全文 @@ -794,15 +798,11 @@

    @@ -825,8 +825,8 @@

    - - 【LeetCode日记】 1162. 地图分析 + + 阿里面试题:如何寻找「两个数组」的中位数?

    @@ -866,9 +866,9 @@

    @@ -888,7 +888,7 @@

      |   @@ -896,7 +896,7 @@

    @@ -914,15 +914,11 @@

    -

    LeetCode 上有很多小岛题,虽然官方没有这个标签, 但是在我这里都差不多。不管是思路还是套路都比较类似,大家可以结合起来练习。

    - +

    一个数组的中位数很容易求,那两个数组呢?

    @@ -1002,7 +1002,7 @@

    @@ -1131,7 +1131,7 @@

    @@ -1260,7 +1260,7 @@

    @@ -1523,7 +1523,7 @@

    @@ -2136,6 +2136,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2284,7 +2286,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\347\256\227\346\263\225/page/3/index.html" "b/categories/\347\256\227\346\263\225/page/3/index.html" index 5e08d46d17..7592e2d1fc 100644 --- "a/categories/\347\256\227\346\263\225/page/3/index.html" +++ "b/categories/\347\256\227\346\263\225/page/3/index.html" @@ -340,7 +340,7 @@

    @@ -470,7 +470,7 @@

    @@ -602,7 +602,7 @@

    @@ -734,7 +734,7 @@

    @@ -864,7 +864,7 @@

    @@ -997,7 +997,7 @@

    @@ -1129,7 +1129,7 @@

    @@ -1189,12 +1189,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -1985,6 +1985,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2133,7 +2135,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\347\256\227\346\263\225/\344\272\214\345\210\206\346\263\225/index.html" "b/categories/\347\256\227\346\263\225/\344\272\214\345\210\206\346\263\225/index.html" index ad876566e6..bf3281a101 100644 --- "a/categories/\347\256\227\346\263\225/\344\272\214\345\210\206\346\263\225/index.html" +++ "b/categories/\347\256\227\346\263\225/\344\272\214\345\210\206\346\263\225/index.html" @@ -408,12 +408,12 @@

    - 数学 - 数据结构 算法 + 数学 + 中位数 二分法 @@ -1057,6 +1057,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1205,7 +1207,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\347\256\227\346\263\225/\344\275\215\350\277\220\347\256\227/index.html" "b/categories/\347\256\227\346\263\225/\344\275\215\350\277\220\347\256\227/index.html" index 3ad087c771..a33d573b07 100644 --- "a/categories/\347\256\227\346\263\225/\344\275\215\350\277\220\347\256\227/index.html" +++ "b/categories/\347\256\227\346\263\225/\344\275\215\350\277\220\347\256\227/index.html" @@ -346,7 +346,7 @@

    @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/index.html" "b/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/index.html" index 5b78b4050c..43a6114536 100644 --- "a/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/\351\235\242\350\257\225/index.html" "b/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/\351\235\242\350\257\225/index.html" index a6b00c43dd..4feadd929f 100644 --- "a/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/\351\235\242\350\257\225/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\211\215\347\253\257/\351\235\242\350\257\225/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\211\215\347\274\200\345\222\214/index.html" "b/categories/\347\256\227\346\263\225/\345\211\215\347\274\200\345\222\214/index.html" index 300d5958d0..c0cf276aaa 100644 --- "a/categories/\347\256\227\346\263\225/\345\211\215\347\274\200\345\222\214/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\211\215\347\274\200\345\222\214/index.html" @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" "b/categories/\347\256\227\346\263\225/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" index 20d4e80089..f7f230be41 100644 --- "a/categories/\347\256\227\346\263\225/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" @@ -354,7 +354,7 @@

    @@ -487,7 +487,7 @@

    @@ -616,7 +616,7 @@

    @@ -748,7 +748,7 @@

    @@ -878,7 +878,7 @@

    @@ -938,12 +938,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -1464,6 +1464,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1612,7 +1614,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\217\214\346\214\207\351\222\210/index.html" "b/categories/\347\256\227\346\263\225/\345\217\214\346\214\207\351\222\210/index.html" index adbb07695a..a91647ea6d 100644 --- "a/categories/\347\256\227\346\263\225/\345\217\214\346\214\207\351\222\210/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\217\214\346\214\207\351\222\210/index.html" @@ -344,9 +344,9 @@

    @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\233\236\346\226\207/index.html" "b/categories/\347\256\227\346\263\225/\345\233\236\346\226\207/index.html" index e5b50e6b12..408c3d67de 100644 --- "a/categories/\347\256\227\346\263\225/\345\233\236\346\226\207/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\233\236\346\226\207/index.html" @@ -346,7 +346,7 @@

    @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\233\236\346\272\257/index.html" "b/categories/\347\256\227\346\263\225/\345\233\236\346\272\257/index.html" index 4abb8a7fe4..78c7b6593d 100644 --- "a/categories/\347\256\227\346\263\225/\345\233\236\346\272\257/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\233\236\346\272\257/index.html" @@ -346,7 +346,7 @@

    @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\255\220\345\272\217\345\210\227/index.html" "b/categories/\347\256\227\346\263\225/\345\255\220\345\272\217\345\210\227/index.html" index 44d53aa1d4..085f03094f 100644 --- "a/categories/\347\256\227\346\263\225/\345\255\220\345\272\217\345\210\227/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\255\220\345\272\217\345\210\227/index.html" @@ -346,7 +346,7 @@

    @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\260\261\345\234\260\347\256\227\346\263\225/index.html" "b/categories/\347\256\227\346\263\225/\345\260\261\345\234\260\347\256\227\346\263\225/index.html" index 76be072115..b3c51894f2 100644 --- "a/categories/\347\256\227\346\263\225/\345\260\261\345\234\260\347\256\227\346\263\225/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\260\261\345\234\260\347\256\227\346\263\225/index.html" @@ -346,7 +346,7 @@

    @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250/index.html" "b/categories/\347\256\227\346\263\225/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250/index.html" index 579470557c..a7dd62b0eb 100644 --- "a/categories/\347\256\227\346\263\225/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" "b/categories/\347\256\227\346\263\225/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" index 1d54a74168..67738469d2 100644 --- "a/categories/\347\256\227\346\263\225/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" +++ "b/categories/\347\256\227\346\263\225/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" @@ -346,7 +346,7 @@

    @@ -935,6 +935,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1083,7 +1085,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\346\225\260\345\255\246/index.html" "b/categories/\347\256\227\346\263\225/\346\225\260\345\255\246/index.html" index ef394ff53e..69faa542fc 100644 --- "a/categories/\347\256\227\346\263\225/\346\225\260\345\255\246/index.html" +++ "b/categories/\347\256\227\346\263\225/\346\225\260\345\255\246/index.html" @@ -348,7 +348,7 @@

    @@ -478,7 +478,7 @@

    @@ -538,12 +538,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -1064,6 +1064,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1212,7 +1214,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\346\246\202\347\216\207/index.html" "b/categories/\347\256\227\346\263\225/\346\246\202\347\216\207/index.html" index 768967a2a9..91fd1e087c 100644 --- "a/categories/\347\256\227\346\263\225/\346\246\202\347\216\207/index.html" +++ "b/categories/\347\256\227\346\263\225/\346\246\202\347\216\207/index.html" @@ -346,7 +346,7 @@

    @@ -406,12 +406,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -932,6 +932,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1080,7 +1082,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\346\257\215\351\242\230/index.html" "b/categories/\347\256\227\346\263\225/\346\257\215\351\242\230/index.html" index 317306082f..f3f2b77198 100644 --- "a/categories/\347\256\227\346\263\225/\346\257\215\351\242\230/index.html" +++ "b/categories/\347\256\227\346\263\225/\346\257\215\351\242\230/index.html" @@ -941,6 +941,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1089,7 +1091,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" "b/categories/\347\256\227\346\263\225/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" index 3b08d42113..b9e8ed3cdc 100644 --- "a/categories/\347\256\227\346\263\225/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" +++ "b/categories/\347\256\227\346\263\225/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" @@ -305,8 +305,8 @@

    lucifer

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -348,7 +348,7 @@

    @@ -368,7 +368,7 @@

      |   @@ -376,7 +376,7 @@

    @@ -394,14 +394,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -531,11 +528,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1069,6 +1069,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1217,7 +1219,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\347\256\227\346\263\225/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" "b/categories/\347\256\227\346\263\225/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" index 56dee10f63..2c29c0dee4 100644 --- "a/categories/\347\256\227\346\263\225/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" +++ "b/categories/\347\256\227\346\263\225/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\347\256\227\346\263\225/\350\203\214\345\214\205\351\227\256\351\242\230/index.html" "b/categories/\347\256\227\346\263\225/\350\203\214\345\214\205\351\227\256\351\242\230/index.html" index e9e4437a34..bd728b241f 100644 --- "a/categories/\347\256\227\346\263\225/\350\203\214\345\214\205\351\227\256\351\242\230/index.html" +++ "b/categories/\347\256\227\346\263\225/\350\203\214\345\214\205\351\227\256\351\242\230/index.html" @@ -346,7 +346,7 @@

    @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\351\200\222\345\275\222/index.html" "b/categories/\347\256\227\346\263\225/\351\200\222\345\275\222/index.html" index c19ff32879..f2bda373a4 100644 --- "a/categories/\347\256\227\346\263\225/\351\200\222\345\275\222/index.html" +++ "b/categories/\347\256\227\346\263\225/\351\200\222\345\275\222/index.html" @@ -346,7 +346,7 @@

    @@ -406,12 +406,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -932,6 +932,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1080,7 +1082,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225/\351\223\276\350\241\250\345\217\215\350\275\254/index.html" "b/categories/\347\256\227\346\263\225/\351\223\276\350\241\250\345\217\215\350\275\254/index.html" index 0edc528618..e60b4fdc90 100644 --- "a/categories/\347\256\227\346\263\225/\351\223\276\350\241\250\345\217\215\350\275\254/index.html" +++ "b/categories/\347\256\227\346\263\225/\351\223\276\350\241\250\345\217\215\350\275\254/index.html" @@ -346,7 +346,7 @@

    @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" "b/categories/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" index ed1af43c44..544eb74f0f 100644 --- "a/categories/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" +++ "b/categories/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225\357\274\214\345\212\250\346\200\201\350\247\204\345\210\222/index.html" "b/categories/\347\256\227\346\263\225\357\274\214\345\212\250\346\200\201\350\247\204\345\210\222/index.html" index 23fbb2fcab..909302f1a1 100644 --- "a/categories/\347\256\227\346\263\225\357\274\214\345\212\250\346\200\201\350\247\204\345\210\222/index.html" +++ "b/categories/\347\256\227\346\263\225\357\274\214\345\212\250\346\200\201\350\247\204\345\210\222/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225\357\274\214\345\272\217\345\210\227\345\214\226/index.html" "b/categories/\347\256\227\346\263\225\357\274\214\345\272\217\345\210\227\345\214\226/index.html" index d5e020a0f6..5d6a885a22 100644 --- "a/categories/\347\256\227\346\263\225\357\274\214\345\272\217\345\210\227\345\214\226/index.html" +++ "b/categories/\347\256\227\346\263\225\357\274\214\345\272\217\345\210\227\345\214\226/index.html" @@ -936,6 +936,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1084,7 +1086,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\256\227\346\263\225\357\274\214\346\273\221\345\212\250\347\252\227\345\217\243/index.html" "b/categories/\347\256\227\346\263\225\357\274\214\346\273\221\345\212\250\347\252\227\345\217\243/index.html" index 6196dbe249..5f9ca76ea0 100644 --- "a/categories/\347\256\227\346\263\225\357\274\214\346\273\221\345\212\250\347\252\227\345\217\243/index.html" +++ "b/categories/\347\256\227\346\263\225\357\274\214\346\273\221\345\212\250\347\252\227\345\217\243/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\272\277\346\256\265\346\240\221/index.html" "b/categories/\347\272\277\346\256\265\346\240\221/index.html" index b3432dbf32..7d9addf351 100644 --- "a/categories/\347\272\277\346\256\265\346\240\221/index.html" +++ "b/categories/\347\272\277\346\256\265\346\240\221/index.html" @@ -973,6 +973,8 @@

    背景
    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1121,7 +1123,7 @@

    背景
    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" "b/categories/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" index fce6868d5f..d361459548 100644 --- "a/categories/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" +++ "b/categories/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" "b/categories/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" index 8115496c0a..097f5d723f 100644 --- "a/categories/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" +++ "b/categories/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" @@ -346,7 +346,7 @@

    @@ -935,6 +935,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1083,7 +1085,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\207\252\345\212\250\345\214\226/index.html" "b/categories/\350\207\252\345\212\250\345\214\226/index.html" index 21bf376bd2..a2ae70c2b3 100644 --- "a/categories/\350\207\252\345\212\250\345\214\226/index.html" +++ "b/categories/\350\207\252\345\212\250\345\214\226/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" "b/categories/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" index fd23033ab0..966e9134d9 100644 --- "a/categories/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" +++ "b/categories/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\231\276\347\232\256/index.html" "b/categories/\350\231\276\347\232\256/index.html" index ca413b1c3c..ab3d464cef 100644 --- "a/categories/\350\231\276\347\232\256/index.html" +++ "b/categories/\350\231\276\347\232\256/index.html" @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" "b/categories/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" index 5ac5e7cafb..ffe35a6bab 100644 --- "a/categories/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" +++ "b/categories/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\260\267\346\255\214/index.html" "b/categories/\350\260\267\346\255\214/index.html" index 1e883cfcbd..71ce891b0e 100644 --- "a/categories/\350\260\267\346\255\214/index.html" +++ "b/categories/\350\260\267\346\255\214/index.html" @@ -1201,6 +1201,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1349,7 +1351,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\260\267\346\255\214/\350\275\257\345\256\236\345\212\233/index.html" "b/categories/\350\260\267\346\255\214/\350\275\257\345\256\236\345\212\233/index.html" index c611b1c562..513645a02c 100644 --- "a/categories/\350\260\267\346\255\214/\350\275\257\345\256\236\345\212\233/index.html" +++ "b/categories/\350\260\267\346\255\214/\350\275\257\345\256\236\345\212\233/index.html" @@ -924,6 +924,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1072,7 +1074,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\260\267\346\255\214/\351\235\242\350\257\225/index.html" "b/categories/\350\260\267\346\255\214/\351\235\242\350\257\225/index.html" index 3928a9863a..9de2a79768 100644 --- "a/categories/\350\260\267\346\255\214/\351\235\242\350\257\225/index.html" +++ "b/categories/\350\260\267\346\255\214/\351\235\242\350\257\225/index.html" @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\264\252\345\251\252/index.html" "b/categories/\350\264\252\345\251\252/index.html" index e35474d5b1..8ae6e34db9 100644 --- "a/categories/\350\264\252\345\251\252/index.html" +++ "b/categories/\350\264\252\345\251\252/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" "b/categories/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" index a8cef3eb19..255b34c91d 100644 --- "a/categories/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" +++ "b/categories/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\350\275\257\344\273\266\345\267\245\345\205\267/index.html" "b/categories/\350\275\257\344\273\266\345\267\245\345\205\267/index.html" index 4681aa0bbb..3a6f0b57a7 100644 --- "a/categories/\350\275\257\344\273\266\345\267\245\345\205\267/index.html" +++ "b/categories/\350\275\257\344\273\266\345\267\245\345\205\267/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\351\223\276\350\241\250/index.html" "b/categories/\351\223\276\350\241\250/index.html" index e8d5f38182..272789c3c2 100644 --- "a/categories/\351\223\276\350\241\250/index.html" +++ "b/categories/\351\223\276\350\241\250/index.html" @@ -932,6 +932,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1080,7 +1082,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\351\235\242\347\273\217/index.html" "b/categories/\351\235\242\347\273\217/index.html" index 76abb2dc19..f283c7ac51 100644 --- "a/categories/\351\235\242\347\273\217/index.html" +++ "b/categories/\351\235\242\347\273\217/index.html" @@ -1330,6 +1330,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1478,7 +1480,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/categories/\351\235\242\350\257\225/index.html" "b/categories/\351\235\242\350\257\225/index.html" index 442fecedcb..e06bd23a4b 100644 --- "a/categories/\351\235\242\350\257\225/index.html" +++ "b/categories/\351\235\242\350\257\225/index.html" @@ -564,8 +564,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -607,7 +607,7 @@

    @@ -627,7 +627,7 @@

      |   @@ -635,7 +635,7 @@

    @@ -653,14 +653,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -790,11 +787,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1328,6 +1328,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1476,7 +1478,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/categories/\351\235\242\350\257\225/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" "b/categories/\351\235\242\350\257\225/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" index a5db322d05..02b86ee7ec 100644 --- "a/categories/\351\235\242\350\257\225/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" +++ "b/categories/\351\235\242\350\257\225/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" @@ -305,8 +305,8 @@

    lucifer

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -348,7 +348,7 @@

    @@ -368,7 +368,7 @@

      |   @@ -376,7 +376,7 @@

    @@ -394,14 +394,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -531,11 +528,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1069,6 +1069,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1217,7 +1219,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/content.json b/content.json index 15f4abf3a8..83c98c6b21 100644 --- a/content.json +++ b/content.json @@ -1 +1 @@ -{"meta":{"title":"lucifer的网络博客","subtitle":"LeetCode 前端","description":"lucifer的个人博客,用来记录LeeCode刷题过程和心得,以及构建大前端知识体系","author":"lucifer","url":"https://lucifer.ren/blog","root":"/blog/"},"pages":[{"title":"404 Not Found","date":"2021-03-09T10:48:39.754Z","updated":"2021-03-09T10:48:39.754Z","comments":true,"path":"404.html","permalink":"https://lucifer.ren/blog/404.html","excerpt":"","text":"**404 Not Found** **很抱歉,您访问的页面不存在** 可能是输入地址有误或该地址已被删除"},{"title":"技术大佬和他们的博客","date":"2022-03-09T07:15:42.874Z","updated":"2022-03-09T07:15:42.874Z","comments":true,"path":"friends/index.html","permalink":"https://lucifer.ren/blog/friends/index.html","excerpt":"","text":"我的友链卡片博客名称:lucifer 的网络博客博客网址:https://lucifer.ren/blog/博客头像:https://tva1.sinaimg.cn/large/006tNbRwly1ga7ognflh9j30b40b4q3w.jpg 需要是 https 链接哦~ 博客介绍:一个脑洞很大的程序员,Github 40K LeetCode https://github.com/azl397985856/leetcode ,公众号《力扣加加》。 加入我如需添加友链,请添加微信(DevelopeEngineer)备注“友链交换”,格式如上。 除了提供上面必须的基本信息之外,你还可以提供: PV 和 UV 数据 feedly 订阅地址 以上数据是为了计算你的排名,为了你的排名更加靠前,鼓励大家提供。"},{"title":"所有分类","date":"2021-03-09T10:48:39.816Z","updated":"2021-03-09T10:48:39.816Z","comments":true,"path":"categories/index.html","permalink":"https://lucifer.ren/blog/categories/index.html","excerpt":"","text":""},{"title":"","date":"2021-03-09T10:48:39.817Z","updated":"2021-03-09T10:48:39.817Z","comments":true,"path":"mylist/index.html","permalink":"https://lucifer.ren/blog/mylist/index.html","excerpt":"","text":""},{"title":"关于我","date":"2023-01-07T13:51:59.268Z","updated":"2023-01-07T13:51:59.268Z","comments":true,"path":"about/index.html","permalink":"https://lucifer.ren/blog/about/index.html","excerpt":"","text":"我是一个 Github 40K star 的前端架构师,leetcode 刷题插件 leetcode-cheatsheet 作者,掌握各种算法套路,写了十几万字的算法刷题套路电子书,公众号回复电子书获取。 除了我的本职工作外,我会在开源社区进行一些输出和分享,比较受欢迎的有宇宙最强的前端面试指南 和我的第一本小书 目前本人正在写一本关于《leetcode 题解》的实体书,因此可能更新会比较慢,如果有人想要做些贡献或者合作的也可以直接用下面的邮箱联系我, azl397985856@gmail.com。 我的前端干货,比如性能优化,工程化,架构思想以及前端领域的算法都会在公众号《脑洞前端》同步。 刷题困难?关注公众号《力扣加加》就够了。"},{"title":"所有标签","date":"2021-03-09T10:48:39.817Z","updated":"2021-03-09T10:48:39.817Z","comments":true,"path":"tags/index.html","permalink":"https://lucifer.ren/blog/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"kuma - css-in-js 的未来?","slug":"kuma","date":"2024-03-06T16:00:00.000Z","updated":"2024-03-10T13:06:01.037Z","comments":true,"path":"2024/03/07/kuma/","link":"","permalink":"https://lucifer.ren/blog/2024/03/07/kuma/","excerpt":"kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。","text":"kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。 什么是 css-in-jsCSS-in-JS 是一种将 CSS 代码嵌入到 JavaScript 代码中的技术。它可以提供一些优势,例如更好的组件化、更好的性能、更好的开发体验等。 以 emotion 为例,如下代码就是一个典型的 css-in-js 的例子: 12345678910111213141516import React from \"react\";import { css } from \"@emotion/react\";const buttonStyles = css` padding: 10px 20px; border: none; border-radius: 4px; background-color: #007bff; color: #fff; font-size: 16px; cursor: pointer;`;function Button({ children }) { return <button css={buttonStyles}>{children}</button>;} 相对地,就是传统的 css 代码: 123456789.button { padding: 10px 20px; border: none; border-radius: 4px; background-color: #007bff; color: #fff; font-size: 16px; cursor: pointer;} 123456import React from \"react\";import \"./button.css\";function Button({ children }) { return <button className=\"button\">{children}</button>;} css-in-js 的优缺点首先我们来看下 css-in-js 的优点: 更好的组件化:css-in-js 可以让你将样式和组件放在一起,这样可以更好地组织代码。(想象一下引入 antd 等组件库的时候,我们需要自己单独引入一下 css 文件。而如果组件库是用 css-in-js 写的,就不会有这个问题了) 某些情况下有更好的性能:css-in-js 可以减少网络请求数量,因此某些情况下可以提供更好的性能。 由于 css-in-js 是运行时的,所以可以根据不同的条件动态生成样式。 然后我们来看下 css-in-js 的缺点: 学习成本:css-in-js 有一定的学习成本,因为它需要学习新的语法和工具。 某些情况下有性能问题:如果 css 很多的话,css-in-js 可能会有性能问题,因为它可能会增加 JavaScript 代码的大小。 kuma 是什么,它是如何解决传统 css-in-js 的问题的?kuma 的核心卖点是零运行时的 css-in-js 技术(zero-runtime CSS-in-JS)。同时也利用了运行时 CSS-in-JS 的表达能力。这两种技术的结合可以提供强大的样式能力,同时也保持了良好的性能。 “Zero-runtime CSS-in-JS”是一种在构建时生成 CSS 的技术,而不是在运行时。这意味着所有的 CSS 都在 JavaScript 代码执行之前就已经被生成和插入到页面中,这可以减少运行时的性能开销。 kuma 使用 Babel 插件或 Webpack 加载器来实现。babel 插件可以将 css-in-js 的代码转换成 css 代码。这样就可以在构建时生成 css 代码。也就是说你写的是 css-in-js 的代码,但是构建后生成的是 css 文件。这样可以同时获得 css-in-js 的优点,又避免了 css-in-js 的缺点。 但是核心的点在于并不是所有的 css 都可以在静态地生成,有些 css 是需要在运行时生成的。这如何处理呢? kuma 的解决方案是静态提取可以在构建时确定的样式,并对可能动态更改的样式执行静态“脏检查”,并在运行时注入它们。 而这一切对开发者来说是透明的,你只需要写 css-in-js 的代码,然后构建后就会生成 css 文件。 kuma 的核心原理假设我们写了如下的 css-in-js 代码: 123456789101112131415function App() { return ( <Heading as=\"h3\" className={css` color: red; @media (max-width: sm) { color: blue; } `} > Kuma UI </Heading> );} 经过 kuma 的处理,会变成如下的 css 代码: 12345678._1 { color: red;}@media (max-width: sm) { ._1 { color: blue; }} 和如下的 jsx 代码: 123function App() { return <Heading as=\"h3\" className=\"_1\">Kuma UI</Heading>;} lucifer 提示:如果让你自己实现这个转化,你会吗? 了解到 kumaui 做了什么之后,我们接下来看它是如何完成这样的转化的。 kumaui 的 css 方法本质上是接受一个模板字符串,然后将其转化成 css 代码。这个过程是通过 Babel 插件来完成的。 比如核心代码 kuma-ui/packages/babel-plugin/src/transform.ts 1234567891011121314151617181920212223import { transformSync } from \"@babel/core\";import { compile } from \"@kuma-ui/compiler\";import pluin from \".\";import { sheet } from \"@kuma-ui/sheet\";export function transform(code: string, id: string) { const result = transformSync(code, { filename: id, sourceMaps: true, plugins: [pluin], }); if (!result || !result.code) return; const bindings = ( result.metadata as unknown as { bindings: Record<string, string> } ).bindings; const compiled = compile(result.code, id, bindings); result.code = compiled.code; (result.metadata as unknown as { css: string }).css = sheet.getCSS() + compiled.css; sheet.reset(); return result;} 如上代码就是就是一个 babel 插件,核心逻辑写在了 compile 函数里面。compile 函数接受一个 css-in-js 的代码,然后将其转化成 css 代码。 kuma-ui/packages/compiler/src/compile.ts 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667import { Project, Node, SyntaxKind, JsxOpeningElement, JsxSelfClosingElement,} from \"ts-morph\";import { collectPropsFromJsx } from \"./collector\";import { extractProps } from \"./extractor\";import { componentList } from \"@kuma-ui/core/components/componentList\";import { optimize } from \"./optimizer/optimize\";import { processTaggedTemplateExpression } from \"./processTaggedTemplateExpression\";const project = new Project({});const compile = ( code: string, id: string, bindings: Record<string, string>,) => { const css: string[] = []; const source = project.createSourceFile(id, code, { overwrite: true }); source.forEachDescendant((node) => { if ( node.getKind() === SyntaxKind.JsxElement || node.getKind() === SyntaxKind.JsxSelfClosingElement ) { let openingElement: JsxOpeningElement | JsxSelfClosingElement; if (node.getKind() === SyntaxKind.JsxElement) { const jsxElement = node.asKindOrThrow(SyntaxKind.JsxElement); openingElement = jsxElement.getOpeningElement(); } else { openingElement = node.asKindOrThrow(SyntaxKind.JsxSelfClosingElement); } const jsxTagName = openingElement.getTagNameNode().getText(); // Check if the current JSX element is a Kuma component const originalComponentName = Object.keys(bindings).find( (key) => bindings[key] === jsxTagName && Object.values(componentList).some((c) => c === key), ); if (!originalComponentName) return; const componentName = originalComponentName as (typeof componentList)[keyof typeof componentList]; const extractedPropsMap = collectPropsFromJsx(openingElement); const result = extractProps( componentName, openingElement, extractedPropsMap, ); if (result) css.push(result.css); optimize( componentName, openingElement, extractedPropsMap[\"as\"] as string | undefined, ); } if (Node.isTaggedTemplateExpression(node)) { processTaggedTemplateExpression(node, bindings); } }); return { code: source.getFullText(), id, css: css.join(\" \") };};export { compile }; 可以看出遇到 kuma 组件就会调用 extractProps 函数,然后将其中的 css-in-js 代码转化成 css 代码。 上面的代码忽略细节后其实非常简单,不做过多解释。 而关于大家比较关心的 dirty check,其实和 vue 的 block tree 有点类似,对于没有 JS表达式的节点,可以完整提取出 css(就像我们前面举的例子一样)。而对于动态的比如如下代码: 12345678910111213function App() { const useRed = localStorage.getItem(\"useRed\"); return ( <Heading as=\"h3\" className={css` color: ${useRed ? \"red\" : \"blue\"}; `} > Kuma UI </Heading> );} 由于无法在构建时确定 color 是 red 和 blue,那就还是需要在运行时注入它们。(这个只是为了方便大家理解举的例子,并不意味着 kuma 是对这种非常简单的动态处理也没有做“静态化”处理)。 幸运的是,静态的应用场景还是比较多的,因此总体上 kuma 还是比较快的。 关于更深入的 dirty check 原理内容完全可以开一期文章来讲,这里就不展开了。 总结kuma 是一个 css-in-js 的解决方案,它利用了”zero-runtime CSS-in-JS”技术,同时也利用了运行时 CSS-in-JS 的表达能力。这两种技术的结合可以提供强大的样式能力,同时也保持了良好的性能。 kuma 的核心原理是通过 Babel 插件将 css-in-js 的代码转化成 css 代码。这样可以在构建时生成 css 代码。也就是说你写的是 css-in-js 的代码,但是构建后生成的是 css 文件。这样可以同时获得 css-in-js 的优点,又避免了 css-in-js 的缺点。 kuma ui 仓库地址:https://github.com/kuma-ui/kuma-ui kuma ui 官网:https://www.kuma-ui.com/docs","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"css-in-js","slug":"前端/css-in-js","permalink":"https://lucifer.ren/blog/categories/前端/css-in-js/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"css-in-js","slug":"css-in-js","permalink":"https://lucifer.ren/blog/tags/css-in-js/"}]},{"title":"","slug":"make-money","date":"2024-02-15T04:00:38.279Z","updated":"2024-03-10T06:28:39.107Z","comments":true,"path":"2024/02/15/make-money/","link":"","permalink":"https://lucifer.ren/blog/2024/02/15/make-money/","excerpt":"","text":"How To Make Monney1. 了解你赚的是什么钱首先你要知道你赚的是什么钱。即什么情况下会赚钱,什么情况下会赔钱。 这个问题可以很简单,也可以很复杂。 了解赚的什么钱可以很简单。比如赌马,你赌对了就赚钱,赌错了就赔钱。再比如做生意,利润大于 0 就赚钱,否则就赔钱。再比如做股票,你买了股票,股票涨了你赚钱,股票跌了你赔钱。 了解赚的什么钱也可以很复杂。 比如做空股票,你融券卖了股票,股票跌了你赚钱,股票涨了你赔钱,因为你是借的股。做空外汇也是一样的。 再比如结构化理财产品,你买了一个结构化理财产品,产品的收益是由多种金融工具的表现决定的,比如股票、债券、商品、汇率等。这时候你就需要了解这些金融工具的表现,才能知道你赚的是什么钱。除此之外,结构化产品还会有很多附加条件。 后面我们了解赚钱的手段的时候,一定先要搞清楚赚的是什么钱。即什么时候我会赚钱,什么时候我会赔钱。 2. 赚钱的手段存款,撸空投,新币挖矿基本是无风险的。而铭文和基金是有风险的。其中撸空投,只需要时间(有手就行)。 利用信息差则是一个很大的话题,这里不做详细介绍。只是有一些有意思的信息差例子的时候会提一下,帮助大家打开思路。 2.0 存款将钱存到银行,银行会给你利息。利息是你赚的钱。利息是由存款金额、存款期限、利率决定的。 也可以存货币基金,可以 T+0 支取。部分货币基金,比如支付宝,微信,招商银行都提供随时消费的货币基金。 另外你可以转化为 u,然后存活期。部分时间 u 的利率会特别高,比如最近(2024-02) u 的利率普遍都是年化 10%。 如果不考虑国家和社会的重大变故,这类理财几乎没有风险,因此风险账面上不会赔钱。 什么时候我会赚钱?账面上一定会赚钱,不过有可能是约定日子才能取。由于这种理财方式时间会比较久,因此可能需要考虑通货膨胀的影响。这样的话存款利率大于通货膨胀率的时候会赚钱。 什么时候我会赔钱?存款利率小于通货膨胀率的时候。 2.1 撸空投撸空投是一种赚钱的手段。空投是指项目方免费发放代币给用户。撸空投就是指用户通过参与项目方的活动,获取免费的代币。 比如之前参与的 strk 空投,就可以通过绑定 github 免费获取 111 strk 代币。 什么时候我会赚钱?空投的代币上线交易所,且价格大于 0 的时候。什么时候我会赔钱?理论上不存在,因为你没有任何资金投入。 也有的空投是直接不需要你参与,叫零交互空投。直接给你转 token 的,这种空投是最好的,因为你不需要任何成本(时间成本都没有),直接就有钱了。 那么如何提前埋伏零交互空投呢? 首先要储备头部资产: 蓝筹 NFT(人数多,共识高),如 NodeMonkey、Bitcoin Frog、Bitcoin Puppets 等。 BRC 20 头部铭文,如 ORDI、SATS。 各个协议的头部资产,如 ARC 20 中的 Atom 和 Quark,BRC 420 中的蓝盒子等。 其次,要做好资产分钱包,通过上述的例子发现,各个钱包空投的资产近乎相同,为了体现出比特币生态的公平性,未来大部分空投还是会设定一个下界限(像 Runestone 的空投规则一样,只要满足要求就可以获得等额的空投份额)。 最后,做好资产隔离,避免不同协议资产混用。 Reference: 空投大年,一文梳理值得参与的项目 2024 测试网交互大全:埋伏下一个空投大毛 2.2 铭文铭文是一种利用新技术在比特币上创建的数字印记。如果将比特币比作数字黄金,那么铭文便是黄金饰品,它们共享相同的本质。 有人为铭文起了一个好名字——BRC-20,称其为新的代币分发方式,没有项目方、没有跑路、没有 rug pull 风险,让大家机会均等。然而,事实真的如此吗?? BRC20 代币是一种在比特币链上铸造的实验性 meme 代币,利用 ordinals 协议把代币部署在链上,任何人都可以铸造,按照先到先得原理,不支持智能合约。 ERC-20 的出现才是重头戏,解决了高昂 gas 费,提高了出快速度,完美解决了 BRC-20 的弊端,现在铭文市场乱七八糟什么项目都来凑一下热闹,但把问题简单化,BRC-20 提出了概念,ERC-20 解决了问题,也就是说目前只有这两个具备实际价值,其它都是蹭热度! 整个打铭文的流程简单来讲就分为三个步骤。 一、准备好 web3 钱包并充好 btc 也就是 gas 费,一般选择 unisat 或者 uni web3 钱包。二、查询要打的铭文的铭刻情况看是否已经被打完,在 unisat 网站都可以查到。三、选择一个手续费比较低的时间点进行 mint,一般零晨会低一点 流程看似是很简单的,但是注意事项目,小伙伴们一定要搞清,以免出现一些不必要损失。打铭文最大的开支就是 gas 费也就是手续费,一旦参与不管你是否最后能不能够打到,手续费都会花掉,不会退回的。所在,选择适当的手续费设置是很重要的。第二个注意事项,就是要打一些龙头的项目,有热度的项目,这样才能让你打的铭有价值可以流通。 铸造好的铭文可以在铭文市场进行出售,价格相差非常大,涨跌幅也非常大,价格就是铭文市场的交易价格,当然如果铸造失败,就可以理解为你的价格是 0。 铸币的成本约等于 gas 费。 什么时候会赚钱?铭文的代币上线交易所,且价格大于铸币成本。什么时候会赔钱?铸币成本大于代币价格。 2.3 新币挖矿新币挖矿是一种赚钱的手段。新币挖矿是指用户通过参与项目方的活动,获取代币。新币挖矿的活动有很多种,比如质押、流动性挖矿、空投等。 你可以将 bnb 存到活期,当有新币可以挖的时候,就会自动挖矿。挖矿结束一般一天左右就可以交易,你可以将其转化为 usdt 等其他币种。 什么时候我会赚钱?赚的钱是新币,付出的是 bnb 等其他货币。因此 bnb 等其他货币的跌幅小于新币的卖出价的时候会赚钱。反之会赔钱。 2.4 基金刚刚提到了货币基金。除此之外,也可以投资股票基金、债券基金、混合基金、指数基金等。 目前我买的比较多的是指数基金和股票基金。指数基金是一种被动投资的方式。指数基金的收益是由指数的表现决定的。比如沪深 300 指数基金,就是跟踪沪深 300 指数的表现。而股票基金是一种主动投资的方式。股票基金的收益是由基金经理的投资决策决定的。 债券基金基本上就是起到对冲的作用,债券基金的收益是由债券的表现决定的。比如债务人还不上钱,债券基金就会亏钱。 什么时候我会赚钱?基金的收益大于 0 的时候会赚钱。什么时候我会赔钱?基金的收益小于 0 的时候会赔钱。 那什么是基金的收益,如何看基金的收益呢?基金的收益是由基金的净值决定的。比如你买了一个基金,基金的净值是 1 ,过了一段时间,基金的净值变成了 1.1 ,那么你赚了 0.1 。反之亏了 0.1 。 那什么影响基金的净值。答案是基金经理持有的股票的表现。比如你买了一个股票基金,基金经理持有的股票价格都涨了,那么基金的净值就会涨。 那什么影响股票的价格?答案是股票的成交价格。具体来说就是出价中最低的。比如你卖了一个股票,你出价 10 元,别人出价 9 元,那么交易所优先处理 9 元的,如果有人愿意用大于等于 9 元的买就可能成交了,成交后这个股票的价格就是 9 元。 相反,如果你买了一个股票,你出价 10 元,别人出价 11 元,那么交易所优先处理 11 元的,如果有人愿意用小于等于 11 元的卖就可能成交了,成交后这个股票的价格就是 11 元。 这个成交价格的变动就是股价的变动。股价的变动就是基金的净值的变动。基金的净值的变动就是基金的收益的变动。 可以看出基金的收益其实就是大家的心理预期。比如大家都认为股票会涨,那么股票的价格就会涨,基金的净值就会涨,基金的收益就会涨。那么什么时候大家会认为股票会涨呢?答案是有利好的消息。这也是大家都对一些利好和利空消息特别敏感的原因。 利好和利空消息在短期上基本上和股价是正相关的。长期上,还是要看公司的盈利能力。因此如果你是短期投资者,那么你就要关注利好和利空消息。如果你是长期投资者,那么你就要关注公司的盈利能力。 2.5 股票(或等价产品)这里的等价产品指的是金融衍生品,比如虚拟币,期权,期货,ETF 等。 什么是网格交易? 2.6 利用信息差比如最近 sora 特别火,很多人就在卖 sora 内测账号,卖 sora 课程。(虽然 sora 截止到目前还没开放内测,但这并不影响他们赚钱,因为韭菜不知道,这也是一种信息差) 当然信息差并不一定是这种消极的。比如很多网站都有推荐返佣,比如推荐返佣 10%,那么你可以通过推荐别人来赚钱。这个时候你可以搞一些路边的扫码送礼品的活动,比如你可以在路边发放一些小礼品,然后让别人扫码,这样你就可以赚到推荐返佣。相信大家在街上也遇到过这种人。 2.7 杠杆和合约杠杆和合约是两种风险比较大的赚钱的手段,两者有很多相似的地方。 杠杆是一种利用小额的资金来进行数倍于原始金额的投资,以期望获取相对投资标的物波动的数倍收益率,抑或亏损。 合约是买方同意在一段指定时间之后按特定价格接收某种资产,卖方同意在一段指定时间之后按特定价格交付某种资产的协议。 杠杆和合约有什么区别? 【1】.操作方法不一样:杠杆是通过平台借币的方式,来在现货市场中超额配置资产,操作过程就会包含借币费率+交易费率。合约是采用交割合约的模式,意味着在进行交易前就可以选择产品本身的杠杆倍数,这种模式去除了现货市场中需要借币才能进行杠杆的操作。 【2】.定义不一样: 杠杆交易就是利用小额的资金来进行数倍于原始金额的投资,以期望获取相对投资标的物波动的数倍收益率,抑或亏损。合约是买方同意在一段指定时间之后按特定价格接收某种资产,卖方同意在一段指定时间之后按特定价格交付某种资产的协议。 【3】.规则不一样:杠杆交易是投资者用自有资金作为担保,从银行或经纪商处提供的融资放大来进行外汇交易,也就是放大投资者的交易资金。期货合约是由交易所设计,经国家监管机构审批上市的标准化的合约。期货合约的持有者可借交收现货或进行对冲交易来履行或解除合约义务。 【4】.特点不一样:杠杆交易有 24 小时交易、全球性市场、交易品种少、风险可灵活控制,双向交易,操作灵活,高杠杆比例、交易费用低、入市门槛低等特点。期货合约的特点则是以小博大、双向交易、不必担心履约问题、市场透明以及组织严密,效率高。 什么时候我会赚钱? 如果做多,价格上涨超过手续费和交易费,你会赚钱。 如果做空,价格下跌超过手续费和交易费,你会赚钱。 需要特别注意的是,杠杆和合约都可能会发生爆仓,风险很大。 3. 庄家对散户的影响(你是如何亏钱的)TBD","categories":[],"tags":[]},{"title":"关于 Error Boundaries, 你需要知道的一切","slug":"error-boundaries","date":"2024-01-27T16:00:00.000Z","updated":"2024-01-28T08:36:22.853Z","comments":true,"path":"2024/01/28/error-boundaries/","link":"","permalink":"https://lucifer.ren/blog/2024/01/28/error-boundaries/","excerpt":"在我们的应用中,难免会遇到一些异常情况,比如网络请求失败,或者是用户输入了一些非法的数据等等。这些异常情况如果没有得到处理,就会导致应用崩溃,从而影响用户体验。而 Error Boundaries 就是用可以处理这些异常情况中的一部分。","text":"在我们的应用中,难免会遇到一些异常情况,比如网络请求失败,或者是用户输入了一些非法的数据等等。这些异常情况如果没有得到处理,就会导致应用崩溃,从而影响用户体验。而 Error Boundaries 就是用可以处理这些异常情况中的一部分。 什么是 Error BoundariesError Boundaries 是 React 16 引入的一个新特性,用于捕获子组件树在渲染过程中抛出的异常,从而避免整个组件树的崩溃。 使用方法很简单,只需要定义一个组件,实现 componentDidCatch 和 render 方法即可。 一般而言,我们会在 getDerivedStateFromError 中将状态设置为 true, 然后 render 中根据这个设置的状态来渲染不同内容。如下代码所示: 1234567891011121314151617181920212223242526272829class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } componentDidCatch(error, info) { // Example \"componentStack\": // in ComponentThatThrows (created by App) // in ErrorBoundary (created by App) // in div (created by App) // in App logErrorToMyService(error, info.componentStack); } render() { if (this.state.hasError) { // You can render any custom fallback UI return this.props.fallback; } return this.props.children; }} Error Boundaries 的好处与限制Error Boundaries 的好处是显而易见的,它可以避免一个组件错误导致整个页面无法正常渲染的问题。 尽管 Error Boundaries 可以捕获子组件树中的异常,从而避免一个组件错误导致整个页面无法正常渲染的问题。但是它也有一些限制: 只能捕获子组件树中的异常,不能捕获自身组件树中的异常。 不能捕获事件处理器中的异常。 不能捕获异步渲染中的异常。 不能捕获服务端渲染中的异常。 不能捕获自身组件的构造函数中的异常。 对于这些问题,我们必须结合其他的异常处理机制来解决,本文不再赘述。 Error Boundaries 的底层实现原理Error Boundaries 的底层实现原理是什么呢? 遇到错误的时候,React 首先做的是捕获到这个错误,接下来将这个错误交给 componentDidCatch 来处理。类似我们在 JavaScript 中使用 try/catch 来捕获异常,然后在 catch 中处理异常。 我们可以通过源码来一探究竟。 1234567891011121314151617181920function handleError(root, errorInfo) { // Call componentDidCatch or handleError on the boundaries below us let boundary = root; while (boundary) { const inst = getInst(boundary); if (inst !== null && typeof inst.componentDidCatch === 'function') { invokeGuardedCallbackAndCatchFirstError( 'componentDidCatch', inst, errorInfo ); return; } else if (typeof boundary.tag !== 'number') { // Host components don't have lifecycle methods // so we don't need to try to invoke them return; } boundary = boundary.return; }} 可以看到,当一个组件抛出异常时,接下来 React 会从当前组件开始,向上遍历整个组件树,直到找到一个 Error Boundaries 组件,然后调用它的 componentDidCatch 方法。 Error Boundaries 的异常恢复我们期望在用户遇到错误的时候, 可以通过一定的方式来恢复应用的正常运行。比如,我们可以在 componentDidCatch 中调用 setState 来更新组件的状态,从而重新渲染组件。 由于 Error Boundaries 本质上是一个普通高阶 React 组件,因此我们可以通过重新给 children 设置 key 来触发组件的重新渲染。类似这样: 1234567891011121314151617181920212223242526272829303132333435363738394041class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { error: null, errorInfo: null }; } componentDidCatch(error, errorInfo) { // Catch errors in any components below and re-render with error message this.setState({ error: error, errorInfo: errorInfo }); // You can also log error messages to an error reporting service here } retry() { this.setState({ error: null, errorInfo: null, key: Math.random() }); } render() { if (this.state.errorInfo) { // Error path return ( <div> <h2 onClick={this.retry}>Something went wrong. Click to recover</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {this.state.error && this.state.error.toString()} <br /> {this.state.errorInfo.componentStack} </details> </div> ); } // Normally, just render children return <React.Fragment key={this.state.key}>{this.props.children}</React.Fragment> }} 组件的数据来源主要有 state 和 props。 另外我们也会用一些 store 中的数据,甚至有的人使用全局变量。 对于 state 和 props,甚至是 store,我们都可以很容易地使用上次没有报错的数据来重新渲染组件。不过对于全局变量,我们就无能为力了。因此我们尽量不要在组件中使用全局变量。这也是我们使用数据管理框架的原因。因为这样可以使得数据是可预见的。 Error Boundaries 的上报当我们遇到错误的时候,我们可以将错误信息上报到服务器,这样我们就可以知道用户遇到了什么问题,从而可以及时修复。 除了业务数据外,我们还可以将: 组件树的结构上报到服务器,这样我们就可以知道用户遇到问题的时候,页面的结构是怎样的,从而可以更好地定位问题。 组件的状态和属性传到后端,包括 hooks 数据等。 页面上可以展示出一个 hash,然后将 hash 上报到后端。 当用户反馈问题到我们的时候,我们可以通过这个 hash 来找到这个问题,并定位问题原因。 如何处理错误边界我们可以将 Error Boundaries 用于整个应用,也可以将 Error Boundaries 用于某个组件。如何决定呢? 一个原则是将相同业务功能的放到一起,用一个 Error Boundaries 来处理。这样一个功能挂了,相关功能也应该不可用。而不相关功能则期望是不受影响。这需要大家根据自己的业务来决定。 如何测试我们的应用的 Error Boundaries 是否合理?我建议手动将每一个组件都抛出异常,然后看看 Error Boundaries: 是否可以捕获到异常 是否可以恢复应用的正常运行 一个组件挂了后是否符合上一节我们提到的原则 如果觉得效率低,也可以写一个脚本来自动化测试。 总结本文介绍了 Error Boundaries 的基本用法,以及它的好处与限制。同时我们也介绍了 Error Boundaries 的底层实现原理,以及如何在遇到错误的时候,恢复应用的正常运行。最后我们还介绍了如何将错误信息上报到服务器。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"异常处理","slug":"异常处理","permalink":"https://lucifer.ren/blog/tags/异常处理/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第 12 期)","slug":"91algo-12","date":"2023-11-01T16:00:00.000Z","updated":"2023-11-02T05:39:32.733Z","comments":true,"path":"2023/11/02/91algo-12/","link":"","permalink":"https://lucifer.ren/blog/2023/11/02/91algo-12/","excerpt":"第十二期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"第十二期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第 12 期的正式开启的时间为 2023-11-16。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第十二期和往期的不同。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 目前正在和力扣官方合作, 讲义部分内容也在做相应调整。 预计之后出版后大家可以直接使用力扣会员免费无限查看讲义。 由于后期会发布到力扣官方,因此质量上还在进一步打磨。这对于大家来说是一个好消息。 等到我的讲义出版到力扣,力扣会员除了可以免费查看外。力扣会员还有很多其他用处。比如公司题库,leetbook等。 力扣免费题目已经有了很多经典的了,也覆盖了所有的题型,只是很多公司的真题都是锁定的。个人觉得如果你准备找工作的时候,可以买一个会员。另外会员很多leetbook 也可以看,结合学习计划,效率还是蛮高的。如果你要买力扣会员的话,这里有我的专属力扣折扣:https://leetcode.cn/premium/?promoChannel=lucifer (年度会员多送两个月会员,季度会员多送两周会员) 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2023-11-16 至 2024-02-14 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 新的一期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行。 参与方式:发 30 元红包给 lucifer 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 进群填写一个表单(表单地址在群公告),接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 另外也提供了免费参与的方式。 购书免费参与如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q: 题目每一期都是一样的么? A: 大部分是一样的,少部分会更新。比如第九期我们就更换了 5 道打卡题目。而 5 道占比全部打卡题目(91道)还是不算多的。除了打卡题目, 我们也有作业题目,第九期同样我们增加了几道作业题目。后续我们也会按照类似的节奏进行。 Q:第十二期和上一期内容一样吗? A:参考文章开头的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。另外大家可以使用前面介绍的技巧,使用 chatgpt 辅助,这样即使只有一门编程语言基础也完全可以应对。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"如何自己实现一个自动化框架?","slug":"how-to-make-automator","date":"2023-08-26T16:00:00.000Z","updated":"2023-08-29T03:56:58.860Z","comments":true,"path":"2023/08/27/how-to-make-automator/","link":"","permalink":"https://lucifer.ren/blog/2023/08/27/how-to-make-automator/","excerpt":"大家眼里的自动化框架一般都和测试进行绑定,这也可以理解, 毕竟自动化的目的就在于模拟用户行为,确认是否正常工作,代替传统的手工测试。 但实际上自动化和测试是两个问题,我们完全可以单独实现。 比如我可以将自动化框架 A 和 测试框架 B 结合起来使用。也可以将自动化框架 A 和 测试框架 C 一起使用。二者本应该是独立的。 因此本文将只聚焦自动化部分。如果需要扩展自动化测试功能,那么只需要集成一个测试框架进行简单接入就好了,不算复杂。","text":"大家眼里的自动化框架一般都和测试进行绑定,这也可以理解, 毕竟自动化的目的就在于模拟用户行为,确认是否正常工作,代替传统的手工测试。 但实际上自动化和测试是两个问题,我们完全可以单独实现。 比如我可以将自动化框架 A 和 测试框架 B 结合起来使用。也可以将自动化框架 A 和 测试框架 C 一起使用。二者本应该是独立的。 因此本文将只聚焦自动化部分。如果需要扩展自动化测试功能,那么只需要集成一个测试框架进行简单接入就好了,不算复杂。 什么是自动化框架? 用户角度 从用户角度上讲,自动化框架是一个能够自动操作软件的框架,其提供了一套用于编脚本的语言,用户使用这套语言就可以模拟各种各样使用软件的行为。 输入输出角度 从输入输出的角度上讲,自动化框架就是给定一段测试脚本,能够根据其脚本内容执行对应行为的系统。也就是说,输入是一段脚本,输出可能 IO 操作,返回一段特定的文本等等。 自动化框架需要支持什么功能?从前面的解释很容易看出,自动化框架核心就是模拟用户行为。靠什么模拟?靠脚本代码。 模拟用户操作比如我们想模拟用户打开浏览器,在地址栏输入 https://lucifer.ren/blog 我们可以将上面的步骤具体定义为几步: 打开浏览器 输入 https://lucifer.ren/blog 这就是一个用户操作序列。 如果我们想要实现自动化框架,如果支持类似这样的用户操作序列? 主流的做法是面向对象编程, 先抽象几个类。比如 Browser, Page, Element 等等,然后给每个类定义细粒度的 API。通过 API 的组合完成上面的功能。 比如的自动化框架名字叫 my-automator,我们就可以实现如下几个 API。 一段示意测试脚本,这里我们的脚本采用 JS 语言编写(下同,不再赘述)。 123456const automator = require(\"my-automator\");const browser = automator.launch(options);browser.open({ url: \"https://lucifer.ren/blog\",}); browser 就是 Browser 类的一个实例,我们定义了一个 open 方法。 类似的,如果我们模拟这样的一个操作序列。 打开浏览器 输入 https://lucifer.ren/blog 找到第一篇文章 点击阅读全文 找到页面下方的留言框 输入留言内容:赞! 这里有一些前置知识。 这些知识需要自动化框架的使用者知晓,而自动化框架的开发者无需知晓。 这里的页面有多篇文章,每一篇文章都有一个阅读全文按钮,按钮用一个 div 实现,div 上有一个名为 readmore 的 class,其下有一个 a 标签,点击 a 标签会跳到对应文章。 文章详情页面的评论框是一个 textarea,有一个名为 gt-header-textarea 的 class。 我们可以这样设计我们的 API。 1234567891011const automator = require(\"my-automator\");const browser = automator.launch(options);const page = browser.open({ url: \"https://lucifer.ren/blog\",});const element = page.select(\".readmore a\"); // 类似于 document.querySelector 功能element.click();await browser.waitForNavigation(); // 等待页面跳转完成const textarea = browser.currentPage.select(\".gt-header-textarea\");textarea.input(\"赞\"); 也就是说,如果我们实现了这几个 API,就可以自动化测试上面的这一套用户操作序列了。 那么理论上我们只需要不断完成 API,就可以造一个完整的自动化框架了。文章后面会讲如果实现这些 API 的整体思路。 当然如何设计 API 不是本文重点, 大家完全可以采用自己认可的方式进行。大家只需要理解,我们是按照这种面向对象的方式,根据常见的用户操作进行细粒度拆分 API 即可。 返回特定的信息除了模拟用户操作,我们还需要能够返回特定的信息。 比如当前页面是否在 loading, 当前 button 上显示的文字是什么。 这个有两个用途: 方便根据不同情况写代码。还是以前面的跳转到最新文章下留言为例,我想看下当前第一篇文章是人为置顶的文章,还是按照时间排它是最新的。(假设我们会给置顶文章加一个特殊 class),那么就需要有一个获取元素 class 的 API。 方便集成测试框架。还是以前面的跳转到最新文章下留言为例,我想知道这个过程有没有成功。那么可以根据 selectAll 方法找到所有留言,然后根据留言内容判断是否刚才成功留言了。那么就需要获取元素内容。这也是一种信息。 有了这两个功能,大部分常用的自动化框架的内容都可以实现。 如果实现一个自动化框架首先不管是 web 的自动化, 还是小程序的自动化,甚至是手机的自动化,我们都需要明确一点。那就是:不管是 web 应用也好,小程序应用也好,手机应用也好。他们都是通过某种语言编写的,并且这些语言都可以满足我们前面提到的第二条”返回特定的信息“。 但是它不是根据自动化脚本自动执行,而是需要用户驱动。 不过这个很好办。我们只需设计一个通信机制。 测试脚本通过这套通信机制通知应用需要执行何种操作。 然后应用根据脚本发送的信息,执行操作,然后返回结果信息。 也就是说我们需要: 设计一套测试脚本和应用的通信手段。 实现一套应用代码,这段代码监听发送的消息,并将消息转化为页面的操作,然后返回结果信息。 对于第一个问题为了足够通用,通信我们选择 websocket,主流自动化框架都支持,尤其是 web 自动化框架。第二个问题就更简单了,只要你掌握应用编写的技巧,那么这套植入到应用中的代码并不难写。 整体架构 如图,测试脚本通过 websocket 消息和应用通信,应用收到消息做对应动作,然后返回对应信息给测试脚本。 其中 handle 是对结果进行处理。 比如把不支持序列化的属性去掉或者转化为可序列化的结构。 功能实现消息的结果,我们可以参考 CDP。CDP 使用 method 来标识不同类型的参数。其中 method 的命名采用固定格式: 1[domain_name].[method_name] 比如你想调用 document.querySelector,就可以发送一个 websocket 消息给 chrome,其中 method 字段固定为 “DOM.querySelector”,参数为 nodeId 和 selector,返回值是选中结果 DOM 的 nodeId。 基于 CDP,你可以自己定制一个远程 devtool 工具,而不必使用 chrome 内置的 devtool。其实 chrome 内置的 devtool 也是基于 CDP 做的。 我们也可以采用这种格式来实现,下面代码是以 web 自动化框架为例,其他框架同理。 测试脚本端我们需要分别实现 Browser,Page 和 Element 类。 测试脚本端主要就是发送消息, 然后等待应用端返回对应的结果消息,最后回传给用户即可。 Browser: 123456class Browser { constructor() {} open({ url }) { // open page in browser }} Page: 12345678910111213141516171819202122232425262728class Page { constructor() {} select({ selector }) { return new Promise((resolve, reject) => { const id = uuid() ws.send({ id method: 'PAGE.select', // PAGE 是 domain, select 是 method query: { selector } }) // 等待应用端消息 ws.addEventListener(\"message\", (event) => { const data = event.data if (data.id === id) { resolve(data.result) // 这里其实可以移除监听了 } // ... }); }) } selectAll({ selector }) { // ... } // ...} Element 1234567891011121314151617181920212223242526272829class Element { constructor(id) { this.id = id } click() { return new Promise((resolve, reject) => { const id = uuid() ws.send({ id method: 'ELEMENT.click', // ELEMENT 是 domain, click 是 method query: { id: this.id } }) // 等待应用端消息 ws.addEventListener(\"message\", (event) => { const data = event.data if (data.id === id) { resolve(data.result) // 这里其实可以移除监听了 } // ... }); }) } attribute(name) { // ... }} 可以看出测试脚本端代码都是类似的。 应用端我们需要植入一段代码到应用端。这套代码如果你了解应用代码如何编写, 也不会难。 总之,应用端就是监听消息,做动作,然后通过 websocket 将结果回传即可。 123456789101112131415161718192021222324252627282930const domMap = new Map();function getElementById(id) { return domMap.get(id);}function setElementById(id, ele) { return domMap.set(id, ele);}ws.addEventListener(\"message\", (event) => { const data = event.data; if (data.method === \"select\") { const ele = document.querySelector(data.query.selector); const id = uuid(); setElementById(id, ele); ws.send({ id: data.id, result: { id, ...handle(ele), }, }); } if (data.method === \"click\") { const element = getElementById(data.query.id); ws.send({ id: data.id, result: handle(element.click()), }); } // ...}); 总结不管是 web 端自动化框架,还是小程序端自动化框架,甚至是手机端,电脑端自动化框架,我们都可以使用这个思路来完成。 即: 注入一段代码到应用端。 定义一套脚本语言,脚本发送消息到应用端。 应用响应消息后做动作,最后返回结果信息给脚本端。 使用面向对象编程方法,结合 CDP 的定义格式可以帮助我们写出更清晰易懂的代码。 同时我们的自动化框架也可以轻松集成任意的测试框架,实现自动化测试的目的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"自动化","slug":"自动化","permalink":"https://lucifer.ren/blog/categories/自动化/"},{"name":"devtool","slug":"devtool","permalink":"https://lucifer.ren/blog/categories/devtool/"}],"tags":[{"name":"前端,自动化,automator","slug":"前端,自动化,automator","permalink":"https://lucifer.ren/blog/tags/前端,自动化,automator/"},{"name":"chrome","slug":"chrome","permalink":"https://lucifer.ren/blog/tags/chrome/"}]},{"title":"如何移除项目中未使用的 CSS","slug":"remove-useless-css","date":"2023-08-07T16:00:00.000Z","updated":"2023-08-08T09:51:56.146Z","comments":true,"path":"2023/08/08/remove-useless-css/","link":"","permalink":"https://lucifer.ren/blog/2023/08/08/remove-useless-css/","excerpt":"使用 chrome 的 devtool 可以查看项目中未被使用的 JS 和 CSS。具体可以参考下 chrome 官方的博客: Coverage: Find unused JavaScript and CSS 有没有方法可以自己检测呢?甚至是做成工具集成到 CI/CD 甚至 code review 中呢? 看完本文,你能学到如何自己手撸一个这样的工具。同时也会推荐社区里经过验证的好用的同类型库。","text":"使用 chrome 的 devtool 可以查看项目中未被使用的 JS 和 CSS。具体可以参考下 chrome 官方的博客: Coverage: Find unused JavaScript and CSS 有没有方法可以自己检测呢?甚至是做成工具集成到 CI/CD 甚至 code review 中呢? 看完本文,你能学到如何自己手撸一个这样的工具。同时也会推荐社区里经过验证的好用的同类型库。 前置知识 postcss jsdom 正文推荐两个库,其中第一个是我使用过的。另外一个研究了一下,和第一个大同小异,下面我会具体分析,大家看完后可以根据自己的情况进行选择。 uncss首先第一个工具:uncss 一个最简洁的用法(全部使用默认配置): 1234567var uncss = require('uncss');var files = ['my', 'array', 'of', 'HTML', 'files', 'or', 'http://urls.com'],uncss(files, function (error, output) { console.log(output);}); 它会在内存中使用 jsdom 打开你的 HTML 文件, 然后像上面提到的 chrome devtools 的 coverage 功能一样分析你的哪些 CSS 选择器是没有被使用到的。 其配置项也非常丰富,具体可以参考官方文档 它的原理非常简单,第一个核心部分是 process 函数。 省略非核心代码 12345678async function process(opts) { // getHTML 会调用 jsdom.fromSource ,生成 page 对象。由于可以输入多个文件,因此会返回 pages 数组 const pages = await getHTML(opts.html, opts); // ... 省略非核心代码 // getStylesheets 会调用 jsdom.getStylesheets 得到样式文件 return getStylesheets(opts.files, opts, pages);} 经过上面的预处理后会走到第二个关键部分是 uncss 函数。 123456789101112131415161718192021222324/** * Main exposed function * @param {Array} pages List of jsdom pages * @param {Object} css The postcss.Root node * @param {Array} ignore List of selectors to be ignored * @return {Promise} */module.exports = async function uncss(pages, css, ignore) { const nestedUsedSelectors = await Promise.all( pages.map((page) => getUsedSelectors(page, css)) ); const usedSelectors = _.flatten(nestedUsedSelectors); const filteredCss = filterUnusedRules(css, ignore, usedSelectors); const allSelectors = getAllSelectors(css); return [ filteredCss, { /* Get the selectors for the report */ all: allSelectors, unused: _.difference(allSelectors, usedSelectors), used: usedSelectors, }, ];}; 核心就是 getUsedSelectors(pages, css) ,根据你提供的 HTML 和 CSS,找到被使用的选择器, 全部的选择器减去被使用的选择器自然就是没有被使用到的选择器。 1234567891011121314/** * Find which selectors are used in {pages} * @param {Array} page List of jsdom pages * @param {Object} css The postcss.Root node * @return {Promise} */function getUsedSelectors(page, css) { let usedSelectors = []; css.walkRules((rule) => { usedSelectors = _.concat(usedSelectors, rule.selectors.map(dePseudify)); }); return jsdom.findAll(page.window, usedSelectors);} getUsedSelectors 则是使用 postcss, 对 CSS 进行 AST 转化后,调用 dePseudify 进行处理。 dePseudify 基本上就是直接调用了 postcss-selector-parser。不熟悉 postcss-selector-parser 的话也没关系,看下它官方提供的 demo 就懂了。 123456789const parser = require(\"postcss-selector-parser\");const transform = (selectors) => { selectors.walk((selector) => { // do something with the selector console.log(String(selector)); });};const transformed = parser(transform).processSync(\"h1, h2, h3\"); 可以看出, 就是给定一个 css 文本,它会逐个输出 css 中的选择器。 简单总结一下它的原理,使用 jsdom 读取 html 和 css。jsdom 可以得到已经被使用的选择器, postcss 以及插件会得到 css 中的全部选择器。两者相减,就是没有被使用的 css。 purifycss相比上一个库,这个就非常小巧了。提供的配置也比较少,官方文档 12345678import purify from \"purify-css\";let content = \"\";let css = \"\";let options = { output: \"filepath/output.css\",};purify(content, css, options); 核心代码就是 purify 函数。 123456789101112131415const purify = (searchThrough, css, options, callback) => { // ... 省略非核心代码 let cssString = FileUtil.filesToSource(css, \"css\"), content = FileUtil.filesToSource(searchThrough, \"content\"); let wordsInContent = getAllWordsInContent(content), selectorFilter = new SelectorFilter(wordsInContent, options.whitelist), tree = new CssTreeWalker(cssString, [selectorFilter]); tree.beginReading(); let source = tree.toString(); fs.writeFile(options.output, source, (err) => { if (err) return err; });}; getAllWordsInContent() 是获取全部的 css 选择器。 tree.beginReading() 是为了获取已经被引用的 css 选择器。 两者相减就是未被使用的选择器。 其中 tree.beginReading 则是调用了另外一个库 rework 实现的。rework(this.startingSource).use(this.readPlugin.bind(this)); 本质上也是借助于 rework 提供的插件系统,自己实现了一个插件 selectorFilter 来找到被使用的选择器。而 selectorFilter 的核心就是 filterSelectors 这样一个函数: 12345678910111213141516171819function filterSelectors(selectors) { let contentWords = this.contentWords, rejectedSelectors = this.rejectedSelectors, usedSelectors = [] selectors.forEach(selector => { let words = getAllWordsInSelector(selector), usedWords = words.filter(word => contentWords[word]) if (usedWords.length === words.length) { usedSelectors.push(selector) } else { rejectedSelectors.push(selector) } }) return usedSelectors} 如果你没听过 rework 也没关系, 你可以将 rework 看成是 postcss 去理解,问题不大。 总结相信读完本文,你已经明白如何自己实现一个这样的工具,只不过还有需要考虑就是了。 如果你是想在开发阶段大概看一下未被使用的代码,推荐 chrome 的 devtool 工具 coverage。 如果你想使用现成工具,个人更推荐使用 uncss,因为其实基于 jsdom 的, 实现上更接近 chrome 的 coverage 功能,支持的功能也更多。你甚至可以基于它实现 e2e 测试后,出具一份未被使用的选择器名单。 而 purifycss 则简单许多,但是原理是相似的,都是将 css 进行 ast 化,然后使用插件分析规则作为被使用的选择器,然后 html 中引用的选择器作为全部的选择器,两者相减得出没有被使用的选择器。","categories":[],"tags":[{"name":"Chrome","slug":"Chrome","permalink":"https://lucifer.ren/blog/tags/Chrome/"},{"name":"CSS","slug":"CSS","permalink":"https://lucifer.ren/blog/tags/CSS/"},{"name":"AST","slug":"AST","permalink":"https://lucifer.ren/blog/tags/AST/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第 11 期)","slug":"91algo-11","date":"2023-06-03T16:00:00.000Z","updated":"2023-11-02T05:17:52.233Z","comments":true,"path":"2023/06/04/91algo-11/","link":"","permalink":"https://lucifer.ren/blog/2023/06/04/91algo-11/","excerpt":"第十一期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"第十一期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第 11 期的正式开启的时间为 2023-06-10。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第十一期和往期的不同。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 其实之前也是有作业的, 只不过并不会对每一个小节都留作业,因此后续我们会继续完善作业。 上一期开始,我们正式在活动结束后发放 HTML 版本的电子书, 效果很好。这一期也会同样发放 html 版本, 并解决一些 bug。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 我们的题解大部分都是提供了多种语言实现的。而目前 chatgpt 发展势头很猛,我们大家可以使用 chatgpt 来帮助自己翻译代码。比如我们的题解只提供了 python, 而你是 Go 语言从业者, 就可以使用 chatgpt 将其翻译为 Go。根据我的使用经验, 翻译就直接丢给它就行,准确率很高(偶尔出错,不过是小错误, 跑一下就发现了)。关于如何使用 chatgpt,可以参与活动后查看活动主页的 FAQ。 另外也可以使用 chatgpt 给自己讲解代码。 比如有几行代码,不明白在做什么,就可以直接让 chatgpt 可以解释。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2023-06-10 至 2023-09-08 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第十一期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 如果进群后发现自己是 50 名之后 100 名之前需要在 2023/02/02前补交10元,否则会被清退,只能原价参与了。 大家可以通过进群后点击右上角的三个点,查看自己头像所在位置判断自己是第几名进群的,这是因为头像顺序就是入群顺序。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q: 题目每一期都是一样的么? A: 大部分是一样的,少部分会更新。比如第九期我们就更换了 5 道打卡题目。而 5 道占比全部打卡题目(91道)还是不算多的。除了打卡题目, 我们也有作业题目,第九期同样我们增加了几道作业题目。后续我们也会按照类似的节奏进行。 Q:第十一期和上一期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。具体不同可以参考文章前半部分的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。另外大家可以使用前面介绍的技巧,使用 chatgpt 辅助,这样即使只有一门编程语言基础也完全可以应对。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"记一次从 webpack 4 升级 webpack 5 + swc 的经历","slug":"migrate-to-webpack5_swc","date":"2023-03-26T16:00:00.000Z","updated":"2023-03-28T02:57:55.525Z","comments":true,"path":"2023/03/27/migrate-to-webpack5_swc/","link":"","permalink":"https://lucifer.ren/blog/2023/03/27/migrate-to-webpack5_swc/","excerpt":"最近将项目的 webpack 4 升级到了 webpack 5,用了一两天的时间,遇到了一些网上找不到资料的问题, 于是决定将过程记录一下。","text":"最近将项目的 webpack 4 升级到了 webpack 5,用了一两天的时间,遇到了一些网上找不到资料的问题, 于是决定将过程记录一下。 直接找到官方提供的迁移教程 To v5 from v4 进行操作。 升级 webpack将项目依赖的版本直接改为最新版本,注意将变更同步到 package.json 和 lock 文件。 逐个检查 loader 和 plugin 的 兼容性我用到了一个叫 virtual-module-webpack-plugin 的插件,根据 readme 的介绍,直接改为 webpack-virtual-modules 即可。 写法有一点小小的变动。 before: 123456plugins: [ new VirtualModulePlugin({ moduleName: \"src/mysettings.json\", contents: JSON.stringify({ greeting: \"Hello!\" }), }),]; after: 12345plugins: [ new VirtualModulesPlugin({ \"src/mysettings.json\": JSON.stringify({ greeting: \"Hello!\" }), }),]; 检查项目中是否使用到了 breakchange 功能使用官方提供的命令在项目下跑一下即可。 1node --trace-deprecation node_modules/webpack/bin/webpack.js 核心就是在项目下跑一下 webpack,webpack 会打印一些 deprecation 警告,通过上面的命令可以将其筛出来。 内置模块不再 polyfill 由于 webpack 5 内置不在 polyfill path,process 等,因此如果你的项目用到了,需要自己处理。处理方式也很简单。 对于 path: 1config.resolve.fallback.path = require.resolve(\"path-browserify\"); 更多请参考:how-to-polyfill-node-core-modules-in-webpack-5 个别 sourcemap 选项变更 比如 cheap-module-eval-sourcemap 改为了 eval-cheap-module-source-map 这个问题虽然网上我没找到解决方案,但是我问了 chatgpt 它告诉了怎么做(感谢chatgpt)。 如果你开发了一些插件和 loader, 那么要注意一些 api 变了。 比如 assets.compilation,再或者 compiler.hooks.invalid.call 这些变化虽然没有在官方文档体现,但是你的项目中如果使用 ts,那么其会直接报错,应该也不算太坑。 循环依赖webpack4 默认帮助我们处理循环依赖问题。 而webpack5则不会。 webpack5 会报错:webpack 5 cannot access '__webpack_default_export__' before initialization 因此如果你的项目中使用了循环依赖, 可以尝试改变写法。如果你不知道哪里有循环依赖,也可以使用 webpack 官方推荐的检测插件。 Circular Dependency Plugin - Detect modules with circular dependencies when bundling — Maintainer: Aaron Ackerman types 丢失webpack 4 的版本,如果直接使用 api 模式, 那么可以使用很多它导出的类型,webpack 5 中不再导出, 可以使用 patch-package 的方式修改包,也可以自己 declare webpack module 去扩展。 比如不再导出的类型有: MultiWatching LoaderContext Plugin … 升级到 swc-loader之前用的是 babel-loader,这次顺便升级一下 swc,速度据说比 babel-loader 快很多。实际使用我的项目要快 20%-40%。由于每个项目不一样,仅供参考。 升级 swc-loader 很简单。 before: 123456789101112module: { rules: [ { test: /\\.m?js$/, exclude: /(node_modules)/, use: { // `.babelrc` can be used to configure swc loader: \"babel-loader\", }, }, ];} after: 123456789101112module: { rules: [ { test: /\\.m?js$/, exclude: /(node_modules)/, use: { // `.swcrc` can be used to configure swc loader: \"swc-loader\", }, }, ];} 由于我项目中使用了 sourcemap,因此直接修改会报错。 1swc-loader 0: failed to read input source map from user-provided sourcemap 解决方式很简单,直接 swc-loader 传递 sourceMap: false 即可,这样swc-loader 就不处理 sourcemap 了。如果 为 true 的话,swc-loader 会将 sourcemap JSON.stringify 一下,从而出问题。 swc-loader 处理这部分的源码大概是: 1swc.transform(source, options).then(output => callback(null, output.code, parseMap ? JSON.parse(output.map) : output.map), err => callback(err)) 也就是说设置不解析 sourcemap 后就不会序列化了。 其他遇到的问题 node.setImmediate 不支持了,其实 node 上的东西都不支持了。比如 buffer, path 等等。 optimization.noEmitOnErrors 变为了 optimization.emitOnErrors,这个就是 true 和 false 换一下的事,不理解 webpack5 为啥要改这个。 compiler.outputFileSyste.write 签名好像变了 compilation.assets 的类型变了。之前 Source 类的 map 函数返回值可以是 null, 现在只能是 Object 类型 这几个解决方式网上都有,也很简单,不再赘述。 总结这次迁移使得项目性能提升 20% - 30%,总共花了一两天的时间,效果还不错。 另外也会之后升级 rspack 做了准备,因此 rspack 给的迁移例子都是 webpack 5 的, 也就是说如果 webpack 5 出问题更容易找到资料。 我自己试了一下, 目前强行迁移到 rspack 坑很多,并且网上几乎没资料可以参考,所以决定先等等等到社区相对成熟了再入坑。","categories":[],"tags":[{"name":"webpack","slug":"webpack","permalink":"https://lucifer.ren/blog/tags/webpack/"},{"name":"swc","slug":"swc","permalink":"https://lucifer.ren/blog/tags/swc/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第X期)","slug":"91algo-10","date":"2023-01-31T16:00:00.000Z","updated":"2023-06-03T11:19:57.208Z","comments":true,"path":"2023/02/01/91algo-10/","link":"","permalink":"https://lucifer.ren/blog/2023/02/01/91algo-10/","excerpt":"X 是罗马数字中的 X, 也就是数字 10,这里是第十期,一个对我很有意义的节点。 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"X 是罗马数字中的 X, 也就是数字 10,这里是第十期,一个对我很有意义的节点。 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第十期的正式开启的时间为 2023-02-14。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第十期和往期的不同。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 其实之前也是有作业的, 只不过并不会对每一个小节都留作业,因此后续我们会继续完善作业。 电子书可能会以 html 的形式给出,这样大家阅读体验会更好。不会出现什么代码无法滚动,导航不管用等等问题。html 版本虽然搜索功能很强,但是目前还有若干我不太满意的地方(我依赖的工具很久不更新了,有些地方要想实现可能需要多费功夫才行),会在第十期逐步优化,争取令大家获得更好的阅读体验。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2023-02-14 至 2023-05-15 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第十期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 如果进群后发现自己是 50 名之后 100 名之前需要在 2023/02/02前补交10元,否则会被清退,只能原价参与了。 大家可以通过进群后点击右上角的三个点,查看自己头像所在位置判断自己是第几名进群的,这是因为头像顺序就是入群顺序。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q: 题目每一期都是一样的么? A: 大部分是一样的,少部分会更新。比如第九期我们就更换了 5 道打卡题目。而 5 道占比全部打卡题目(91道)还是不算多的。除了打卡题目, 我们也有作业题目,第九期同样我们增加了几道作业题目。后续我们也会按照类似的节奏进行。 Q:第十期和上一期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。具体不同可以参考文章前半部分的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"一款显示题目对应周赛难度分的浏览器插件| TamperMonkey | Chrome | FireFox","slug":"leetcode-rating","date":"2023-01-18T16:00:00.000Z","updated":"2023-01-19T07:27:03.353Z","comments":true,"path":"2023/01/19/leetcode-rating/","link":"","permalink":"https://lucifer.ren/blog/2023/01/19/leetcode-rating/","excerpt":"","text":"leetcode rating website经常有同学反映力扣难度设置的不合理。 比如:“为什么我觉得这个中等题目比某个困难还要难啊?”。 这其实很正常,尤其是在力扣上。因此我也不建议大家完全按照难度去刷题, 而是结合通过率等其他指标去刷。 而今天介绍的这个网站能够将力扣中的题目通过各个维度算出一个更接近题目实际难度的隐藏分。这个分数和你的竞赛分数是一致的。大家可以根据你的竞赛分去刷题。 比如你的竞赛分是 1800, 那么你就可以直接搜索 1800 - 1900 的题目进行练习,效率高很多。 网站地址:https://zerotrac.github.io/leetcode_problem_rating/#/ leetcode rating extension有一位朋友基于上面网站作为数据源自己写了一个扩展,直接在力扣官网里显示对应题目分数(如果有分数的话)。 这是一款显示题目对应周赛难度分的浏览器插件| TamperMonkey | Chrome | FireFox 它是基于油猴的脚本, 大家需要先安装油猴扩展, 然后通过油猴去启动即可。 使用效果: 仓库地址:https://github.com/zhang-wangz/LeetCodeRating 总结如果你需要根据第几场周赛或者分数进行筛选,建议你直接使用网站即可。如果你平时都是在力扣刷题,比如每天做一下力扣的每日一题活动, 那么建议你安装浏览器扩展。另外扩展目前在不断完善中,后续可能增加更多功能,大家有可以去仓库提 issue 或者 pr。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"我的 2022 年总结","slug":"2022","date":"2023-01-17T16:00:00.000Z","updated":"2023-01-19T08:41:23.343Z","comments":true,"path":"2023/01/18/2022/","link":"","permalink":"https://lucifer.ren/blog/2023/01/18/2022/","excerpt":"从工作,自媒体,生活,技术,游戏,家人朋友等角度记录一下我的 2022。","text":"从工作,自媒体,生活,技术,游戏,家人朋友等角度记录一下我的 2022。 工作2022 投入工作的时间无疑是最多的。倒不是坐在电脑前敲代码时间久,即使我出去吃饭可能也会带着一些工作思考,这也算是一种隐性时间。 不过 2022 大多数公司都不好过,包括我们公司。因此激励也会比往年弱。这个时候更应该努力,思考如何打破现状,寻找突破点。 “更关注用户一点” 是我的工作中的年度词语。这一年更多地倾听用户的声音,帮助他们解决问题。这同时为了收获了一些好评和认可。虽然吐槽更多,但是这些认可就足以支撑我继续优化产品了。 希望 2023 能让大多数的用户都对我的产品满意,并有所期待! 自媒体不得不承认,2022 花在自媒体的时间比往年要少很多。Github,博客以及算法活动更新频率都较往前有所降低。 91 天学算法活动自媒体中投入精力最多的当然是我们的算法活动了。为了每一期都更有质量,我精心准备了很多作业,不断更新和打磨讲义,往讲义里塞入了很多常见套路总结,使得讲义硬度大大增加,这是我 2022 年投入在自媒体上最引以为豪的东西了。 Github 综合获星超过 10 kleetcode 项目贡献 80% 以上。 目前获星 ⭐️ 50.2 k 由于我几乎每天都会做题,其实也很容易,直接跟着力扣官方的每日一题做就行了,每天花不了几分钟时间。遇到好题目的话我也会更新题解,如果有必要也会更新到 Github 的 leetcode 仓库中。并定期将题解更新到我的刷题插件 leetcode cheatsheet。这不就在前没多久刚刚提交了一版本,更新了题库以及增加了差分数组模板。 博客博客更新不到 20 篇,其中还有一些是我的算法活动宣传。 有点愧疚,希望 2023 年可以多多更新干货。 视频视频已经全面停更了,包括 bilibili 在内的视频网站全面停止更新。 原因也很真实,文字我都不想更新,视频怎么会去更新呢? 😄 生活2022 年上半年还比较稳定,而到了今年年底疫情突然放开,我周围的人几乎都中招了,包括我。 老人和孩子可遭殃了,尤其是有基础病的老人,一点消息都没有的突然放开大家都没有准备,这是对那些弱势群体的极大不尊重。 理财年底冲了一波业绩,很多票都红了,目前账面盈利并不多。目前市场还是接近底部的位置,因此对这个结果还是满意的。 美元人民币汇率之前一度涨到 7.2+,现在已经回落到 6.7 了,这意味着相比于 7.2 时期有利于我们买买买海淘了。 另外现在不在公众号投放广告了,而是选择了对大家影响更小的 Github 上投放,再加上一些推广和我的理财收入,基本可以覆盖基本生活开支了。 技术这一年对技术的深入研究大多是源自于工作上的需求。通过订阅日报和周报来使自己不会太脱离大部队太多。通过每日一题练习自己对其他语言的掌握情况,比如 golang,python。这真的是一种不错的学习新语言的方法啊(这个方法我之前给大家推荐过) 😄 游戏巫师 3 重置版,魂系游戏(艾尔登,黑魂等),瘟疫传说安魂曲等等大作充实了我的业务生活。 一些小众游戏我也很喜欢,比如类恶魔城的游戏 《蒂德莉特的奇境冒险》。 目前在玩的游戏是 AI 梦境档案第二部,然后还预定了火焰纹章 engage,过年应该刚好能玩上。 另外非常期待今年的塞尔达王国之泪还有卧龙。 家人&朋友由于疫情影响,家人的身体状况有所影响,这该死的玩意。 今年和朋友们接触的也很少,并不仅仅是因为疫情,还有一个原因是居家办公时间久了出去的时间也少了(其实疫情某种程度加深了这个影响)。 希望 2023 家人身体健康,朋友开开心心,不那么忙碌,没事能多聚聚。 爱情没有太强的欲望想找另外一半。可能只是某些瞬间想有个人陪吧,但这其实和找对象不是一回事。 不强求的态度可能会越来越不想找对象,因此我还是要努力先寻找机会,完成一个终身大事。","categories":[{"name":"年终总结","slug":"年终总结","permalink":"https://lucifer.ren/blog/categories/年终总结/"}],"tags":[{"name":"年终总结","slug":"年终总结","permalink":"https://lucifer.ren/blog/tags/年终总结/"},{"name":"2022","slug":"2022","permalink":"https://lucifer.ren/blog/tags/2022/"}]},{"title":"力扣刷题的正确姿势是什么?","slug":"how-leetcode","date":"2023-01-01T16:00:00.000Z","updated":"2023-01-19T07:16:07.810Z","comments":true,"path":"2023/01/02/how-leetcode/","link":"","permalink":"https://lucifer.ren/blog/2023/01/02/how-leetcode/","excerpt":"本文原本是打算加到我的新书《算法通关之路》的附录部分。不过由于力扣官方不过审,因此只好作罢。将这部分内容发到这里给大家参考。 《算法通关之路》介绍以及购买可访问:https://leetcode-solution.cn/book-intro","text":"本文原本是打算加到我的新书《算法通关之路》的附录部分。不过由于力扣官方不过审,因此只好作罢。将这部分内容发到这里给大家参考。 《算法通关之路》介绍以及购买可访问:https://leetcode-solution.cn/book-intro 力扣(LeetCode )网站使用方法力扣(LeetCode )官网收录了许多互联网公司的算法题目,一度被称为刷题神器。这里我们就来介绍下如何使用 力扣(LeetCode )网站。 由于力扣(LeetCode)本身也处于不断迭代之后,因此本文部分内容有可能在将来会变得不再适用。 以力扣国际站为例,其官网给出了四个分类:Algorithms、Database、Shell 和 Concurrency,分别表示算法题、数据库题、Shell 和并发题,第一个就是我们所需要刷的算法题。 并发是 2019 年才添加的新的模块。 点开 Algorithms 后,我们可以看到一个题目的列表,每个题目都有一个唯一的序号。力扣(LeetCode )目前有 1000 多道题目,并且一直持续更新,其中有一些是带锁的,需要会员才能查看。 后面的接受率(Acceptance)表示提交的正确率,Difficulty 表示难易程度。难易程度有三个级别,分别是 Easy、Medium 和 Hard。 Easy 通常不需要太多思考和也不会有复杂的细节,比较特别适合新手或者拿来热身。 Medium 级别就会有些难度,一般都会涉及到经典的算法,需要一定的思考。 Hard 级别是最难的,有些时候是算法本身的难度,有些时候特别需要你考虑到各种细节。 这里分享一个小技巧给大家。衡量一道题目难不难除了看难度之外,还可以看下接受率,接受率越低代表题目越难,这个指标有时候比难度更靠谱。 你可以对题目进行筛选和排序。 如果我们只想要找某一类型的题或者某个公司的题库,可以通过 Tags 或 Company 来筛选。 另外我们在做某一题时,觉得还想再做一个类似的,可以点击题目描述下方 Show Similar Problems 或 Tags 来找到相似的问题。 每个题目都有各自的 Discuss 区域。在这里,许多人都把自己的思路和代码放到了上面,你可以发贴提问,也可以回复别人,里面大神很多,题解质量都很高,如果实在没有思路或者想看下有没有更好的思路可以来逛一下。通常来说我建议你优先看留言最多或者投票最多的。 点开某一个题目,会跳转到具体题目详情页面,你可以在右侧的代码区切换选择自己需要的编程语言。 代码编写完了之后,不要急着提交,先测试运行一下(Run Code 按钮)。你可以多写几个测试用力跑一下,没有问题再提交,要知道比赛的时候错误提交要加时间的。 提交通过之后,可以点开 More Details 查看详细运行结果信息。 每道题旁边的 My Submissions 可以找到自己的对于该题的提交情况,这里可以看到自己过去所有的提交,点 Accepted 或 Wrong Answer 就可以查看自己过去提交的代码情况,包括代码是什么,运行时间以及全部用户提交的时间分布图等。 以上就是 力扣(LeetCode )的主要功能,希望通过这一节内容能让你对 力扣(LeetCode )网站有所了解,从而更快地进行刷题。 刷题工具所谓“工欲善其事必先利其器”,接下来给大家带来一些提高刷题效率的小工具。 Leetcode Problem Rating经常有同学反映力扣难度设置的不合理。 比如:“为什么我觉得这个中等题目比某个困难还要难啊?”。 这其实很正常,尤其是在力扣上。因此我也不建议大家完全按照难度去刷题, 而是结合通过率等其他指标去刷。 而今天介绍的这个网站能够将力扣中的题目通过各个维度算出一个更接近题目实际难度的隐藏分。这个分数和你的竞赛分数是一致的。大家可以根据你的竞赛分去刷题。 比如你的竞赛分是 1800, 那么你就可以直接搜索 1800 - 1900 的题目进行练习,效率高很多。 网站地址:https://zerotrac.github.io/leetcode_problem_rating/#/ 力扣代码调试器 力扣代码调试器是一款简单易用的代码调试工具,它能够随时检查代码的运行情况以及选择性地运行代码,以便您排错、调试。 力扣代码调试器支持以下特色功能: 支持无限制添加断点,单步运行调试 一键监听表达式,动态追踪变量,及时定位错误 「力扣代码调试器」处于内测阶段,具体功能可能会不定期调整,请以最终 release 版本为准。代码调试器目前支持 C++、Java、Python、Python3、C 等 5 种语言,持续更新中。 值得注意的是,这是一个会员才可以使用的功能。 力扣(LeetCode )For VSCODE 这个是一个可以让你在 VSCODE 编辑器中选题,写代码,测试,提交的力扣题目的插件。主要特点是可以利用自己编辑器的一切优势,包括但不限于编辑器主题,代码智能提示, 代码片段,调试工具。也就是说,上面提到的力扣代码调试器的大多数功能,你都可以通过 VSCODE 以及其扩展插件实现。 缺点是部分功能使用体验不好, 比如错误提示不明显,经常误提交等,不过最让我感到不舒服的是自定义测试用例,明显没有力扣官方做的好。 大家可以在 vscode 插件商店搜索 leetcode 进行下载安装。 LeetCode-Cheat 题解模板为了方便大家写出格式良好的题解,插件现在内置题解模板功能,目前模板只有一套,这套模板是我经常使用的题解模板。 安装好我们的插件(版本需要 v0.8.0 及以上)后,打开力扣中文版,会发现如下的按钮。 点击之后会自动引导你到一个新的页面, 该页面的题解语言,题目地址和题目名称信息会自动填充。 你可以快速完成时间复杂度,空间复杂度的插入,复杂度已经按照性能好坏的顺序给大家排好了,点击即可插入。 此外我们提供了若干常用的公式供你快速复制使用。除了公式,其他内容都可以在右侧的预览区域查看。 写完只会可以点击复制,将其复制到其他地方以便持久化存储。由于我们没有做持久化存储,因此页面刷新内容就会消失哦。 最后祝大家写出漂亮的题解! 数据结构可视化你可以使用 canvas 自由绘制各种数据结构,不管是写题解还是帮助理解题目都很有用。 我们提供了很多内置的模板供你快速上手。 如果你对内置的模板不满意,也可以将自己的模板保存以便下次使用。 学习路线算法怎么学?推荐按专题来。具体到某一个专题怎么学?这里提供了一个学习路线帮助你。本功能旨在将一个专题中的题目进行分类。专题本质就是对题目的一种划分,学习路线基于专题又进行了一次划分。 复杂度分析你的代码能会超时么?复杂度分析帮助你。 一键复制所有内置测试用例省去了一个个手动复制的过程,效率翻倍! 代码模板提供了大量的经过反复验证的模板。模板的作用是在你理解了问题的基础上,快速书写,并减少出错概率,即使出错,也容易 debug。 禅定模式 点击之后会变成这样: 底部控制台会消失,当你鼠标重新移过来或者退出禅定模式就出现了。 查看题解当你在任意非题目详情页或者我还没有收录的题目详情页的时候, 我都会列出当前我总结的所有题解。 其实我给比较经典的题目做了题解,因此这个题目数目不是很多,目前是 173 道题。另外有时候我直接写了专题,没有单独给每道题写题解,因此数量上要比 173 多很多。 当你进到一个我写了题解的题目详情页的时候, 你就可以正式使用我的插件了。 它可以: 给出这道题目的前置知识。换句话说就是我需要先掌握什么才能做出这道题。 这个题目的关键点。 哪些公司出了这道题。 我实在不会了,给我看看题解吧。好,满足你。 题解我就不看了,直接 show me code 吧。好,满足你。 根据公司,查找题目。面试突击必备 后期规划: 更多公司信息。 持续完善题目的公司信息,这个过程需要大家的帮助,大家可以把自己面试遇到的问题发给我(附带公司和岗位信息),我可以免费提供咨询服务。 岗位信息。 可视化调试。 可视化展示你的代码允许情况。 自动制定复习计划。 A I 智能提示。即新的提示也可以根据题目信息推测可能的解法。 等等 需要的话,可以去我的公众号《力扣加加》回复插件获取。 LeetBoard 这是一个帮助你写写画画的一个浏览器扩展工具。很多时候,我们需要在纸上演算一下,验证自己的思路,这个时候就可以用这个工具。 这个工具提供了多种常见数据结构的可视化,方便大家快速书写。目前支持的数据格式有:数组,链表,二叉树。 debug-visualizer 这是一个支持可视化调试的 VSCODE 插件。相比于平时我们 debug ,它能帮助我们可视化内存中的数据结构。比如数组,树,图等都可以胜任。由于其不是为力扣量身制作的,实际上它还有很多其他的用处, 比如可视化图表,比如折线图。其功能非常强大, 你甚至可以基于它定制一套自己专属的数据结构。 大家可以在 vscode 插件商店搜索 leetcode 进行下载安装。 总结这五种工具,除了 debug-visualizer,其他都是为力扣量身定做的。 各个工具功能侧重各不相同, 大家也可以结合起来使用。总的来说: 如果你想在编辑器中写力扣,那么 力扣(LeetCode )编辑器插件是一个不错的选择。配合 debug-visualizer 可以实现丝滑般柔顺的感觉。 如果你想在力扣解题过程中写写画画,屡屡思路,建议使用 LeetBoard。 如果你想看看题目的公司信息, 题解等,建议使用我写的 LeetCode-Cheat。 如果你不想使用本地编辑器, 并按照一堆插件,只想用浏览器去完成,那么力扣代码调试器绝对是不二选择。 另外还有一些网站可以可视化算法的执行过程,这对算法初学者来说尤其重要,algorithm-visualizer 就是这样的一个网站,它收录了几乎全部常见的算法, 相信可以满足你。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"入职 Google 一年多的感触(你们的大狗头来啦~)","slug":"goutou-2","date":"2022-12-31T16:00:00.000Z","updated":"2023-01-19T07:15:53.935Z","comments":true,"path":"2023/01/01/goutou-2/","link":"","permalink":"https://lucifer.ren/blog/2023/01/01/goutou-2/","excerpt":"这篇文章是 91 天学算法最具人气奖获奖选手狗头的投稿,原文可以在这里访问到。 以下为原文内容。","text":"这篇文章是 91 天学算法最具人气奖获奖选手狗头的投稿,原文可以在这里访问到。 以下为原文内容。 正文大家好,大狗头来啦~ 狗头已经入职狗家一年多啦。随着在职时间的增加,越发的觉得沟通在软件工程师职业中起着非常重要的作用。 大家因为觉得「工程师嘛是技术岗位」而常常忽略掉沟通这个软技巧(soft skill)的重要性。或者有时候,大家会觉得,「我只是个刚开始工作的大头兵,会写代码就行啦,沟通没那么重要。」 其实沟通一直是非常重要的。对于刚开始工作的工程师来说也是! 沟通技巧为什么呢?因为在大佬眼里,刚开始工作的工程师有啥技术呀!好沟通,说啥就懂,团队气氛融洽,大家都开心干活才是重点啊! 换句话说,在一个「代码写的还行的刺头」和一个「代码写的稍差的善于沟通脾气好大家都喜欢跟他说话的人」之间,大佬会选谁呢?当然是后者啦~因为代码写的差可以教,但是刺头很难改变呀~ 当然,这可能是个有点理想化的例子。换成更贴近真实情况的例子就是「大佬们希望招一个又会写代码、又好沟通、又有上进心的人」。刚毕业的工程师看起来代码水平都差不多的情况下,自然就选择看起来好沟通的那个啦~ 但沟通是一个非常宏大的命题,这里提供两个具体的沟通技巧。 一、多用「好奇心」来代替「质问和指责」当你觉得 xxx 不应该这样,无论是薪水还是技术,你的提问方式可以是「我想知道我的薪水是由什么决定的呢?」而不是「为啥还不给我涨工资呢,你看不到我加班?」 「我想知道我距离升职还有哪些需要努力的地方呢?」而不是「我想升职,咋还不给我升职?我都在这两年啦!」 这样的好处是:首先这个提问方式会让对方舒服很多。然后当对方想了一遍,对方自我得出来了「哦这个事儿确实不合理」的结果的时候,你的目的也达到了。如果对方跟你说「因为 xxx」,那你也在没让对方不舒服的情况下拿到了需要知道的信息。一举两得。 二、在寻求反馈的时候给对方垫话当你在真诚的寻求对方给反馈的时候,可以给对方垫一些话,让对方觉得不会冒犯到你。因为反馈有的时候可能包含你做的不够好的内容,而并不是每个人都能真的接受这样的「批评」或者「带有建设性的意见」(actionable feedback)。作为被询问意见的对象,在跟你不是很熟悉的情况下,往往会倾向于选择比较安全的「夸赞反馈」。被夸当然很好也很有必要啦,但是有时候我们也真心的需要被指出「做的不好的地方」。这个时候,如果能先在对话当中说出自己的反思,比如「我觉得这个项目我可能 xxx 做的不太好,xxx 改进就好了,下次可以…」,然后再询问对方能不能给你点意见,对方就会比较好接话,你也更容易获得深层的意见。 最后希望大家都能在职场上步步高升,加油加油~ Lucifer 补充正如狗头所说“沟通是一个非常宏大的命题“, 我来给大家补充几点内容。 先说明背景遇到很多人问我问题都是直接”我在做 xxxx,但是不 work, 能给我看看为什么不 work 么?“ 如果你对 xx 没那么熟悉的话,这个时候很多人都会一头雾水。 更好的做法是,不要假设对方对你的业务很熟悉(除非他确信真的非常熟悉你的业务细节。实际上真的有这样的人,但是大多数情况下并不是)。 比如你可以说:我正在做 xxx 业务线的一个 yyy 功能,这个功能需要用到 zzz,做这个 zzz 的时候我遇到了一个问题,目前的现状是 。。。, 但是我的预期是 。。。,方便帮我看下为什么么? 请求帮助前,自己做好充分的准备寻求对方帮助的话自己要做好充足的准备,尽量不要出现对方付出的劳动比你自己还多情况,切忌让对方直接给你写代码。比如你请教张三某件事情怎么做的时候,不要直接问”听说你做过 xxx ,我遇到了 xxx 问题,你给我讲讲呗?” 甚至 “你告诉我应该具体咋写呗“, ”你帮我写写吧“。 更合适的做法是,描述你做了什么努力。然后说”可以帮我看下我的实现哪里有问题么?“ 总之就是把对方当成一个审查者的角色, 而不是一个执行者的角色。","categories":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/categories/谷歌/"},{"name":"软实力","slug":"谷歌/软实力","permalink":"https://lucifer.ren/blog/categories/谷歌/软实力/"}],"tags":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/tags/谷歌/"},{"name":"沟通","slug":"沟通","permalink":"https://lucifer.ren/blog/tags/沟通/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第九期)","slug":"91algo-9","date":"2022-10-14T16:00:00.000Z","updated":"2023-02-07T04:15:30.347Z","comments":true,"path":"2022/10/15/91algo-9/","link":"","permalink":"https://lucifer.ren/blog/2022/10/15/91algo-9/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第九期的正式开启的时间为 2022-11-01。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第九期和往期的不同。 很多参加活动的同学其实是因为有算法笔试的需求的。因为我一直在考虑有没有什么办法更加地帮助到这些同学。 后来我突然注意到力扣官方和一些公司有一些合作,有一些公开的笔试,大家可以自由参与。 比如这场笔试:https://leetcode.cn/open-exam/perfectworld-2023/ 我的想法是尽量每个月让大家都参与一次公开笔试,然后将里面的题目以及题解更新到我们的 91 天学算法中。 但是这个频率不是我们能控制的, 需要看这个月没有有公司有这种公开笔试。具体形式还没想好,后面定了后会在 91 天学算法群里通知大家。 除此之外还有一些常规更新: 讲义和打卡增加最近更新时间,方便大家追踪讲义以及周期性打卡的更新。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 电子书可能会以 html 的形式给出,这样大家阅读体验会更好。不会出现什么代码无法滚动,导航不管用等等问题。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2022-11-01 至 2023-01-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第九期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第九期和上一期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。具体不同可以参考文章前半部分的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第八期)","slug":"91algo-8","date":"2022-07-08T16:00:00.000Z","updated":"2023-02-07T04:15:30.348Z","comments":true,"path":"2022/07/09/91algo-8/","link":"","permalink":"https://lucifer.ren/blog/2022/07/09/91algo-8/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言好多同学问下一期什么时候开始,其实最近比较忙,所以怕到时候大家有问题没有办法及时回复,因此推迟几天到月中给大家开始。 第八期的正式开启的时间为 2022-07-15。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第八期和往期的不同。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 电子书可能会以 html 的形式给出,这样大家阅读体验会更好。不会出现什么代码无法滚动,导航不管用等等问题。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-07-15 至 2022-10-13 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第八期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第八期和前七内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"恭喜又一个小姐姐进了谷歌","slug":"google-interview-tao","date":"2022-05-20T16:00:00.000Z","updated":"2023-01-07T13:53:04.630Z","comments":true,"path":"2022/05/21/google-interview-tao/","link":"","permalink":"https://lucifer.ren/blog/2022/05/21/google-interview-tao/","excerpt":"这是 JinTao(aka TJ) 的一篇分享自己如何面试准备进谷歌的经历分享,经历还是蛮曲折的,想去大公司的话非常具体参考价值。 TJ 是我的一个老朋友了,《91 天学算法》也跟了好几期,如果你也参加过《91 天学算法》,那么很有可能知道她。我俩私底下交流也蛮多的,得知她最近刚刚进了谷歌,就邀请她来写一下自己的面试准备经历。 本文中的 lucifer: 是我自己添加的内容,不是作者写的,大多是补充一些资料以便让大家进一步学习。","text":"这是 JinTao(aka TJ) 的一篇分享自己如何面试准备进谷歌的经历分享,经历还是蛮曲折的,想去大公司的话非常具体参考价值。 TJ 是我的一个老朋友了,《91 天学算法》也跟了好几期,如果你也参加过《91 天学算法》,那么很有可能知道她。我俩私底下交流也蛮多的,得知她最近刚刚进了谷歌,就邀请她来写一下自己的面试准备经历。 本文中的 lucifer: 是我自己添加的内容,不是作者写的,大多是补充一些资料以便让大家进一步学习。 关于刷题西法的讲义和他的方法足够足够了。另外,狗头刷题经验已经说很好了,很全面了。做题,注重理解,做西法推荐的经典题,和复习是关键。时间长了会忘记,会缓慢。 lucifer: 关于狗头的刷题经验,大家可以去《力扣加加》历史文章中搜索”狗头“关键字 谷歌很难说刷题范围,把西法讲义好好做,基础题做好,记得面试的时候交流积极主动且清楚,只要运气正常没遇到超级超级难的题,就可以了。谷歌比较注重 followup,比如一开始一个简单题,不要急着秒,要想各种 case,和面试官讨论,其实那些 case 有可能会在之后 followup 出。 这里的讲义指的是 《91 天学算法》的资料,活动介绍与参与方式见我的博客置顶帖。地址:https://lucifer.ren/blog/ 谷歌的面经不一定有意义,这里是指,”别人面了,靠记忆从英文翻译成了中文写出来,且非常模糊的那类面经“,时间不够的话,可以不看。leetcode tag 高频还是有参考意义的,但也不要指望遇到原题,几乎不可能,注重西法的讲义学习和练习就好。 面试过程一般的过程(可能不适用 meta,但狗家是追求这样的)是: 第一步,面试官说完题目,至少得花时间好好理解题意,套自己想的好的 test case,问清 corner case 处理情况。千万不要马上开始写。 第二步,说暴力法,说复杂度,想怎么优化,说方法,得到面试官的认可,才开始写代码。 第三步,写代码(一般西法有归纳模版,经典题一般有基本套路,得提前练熟) 第四步,过自己的 testing case,然后分析复杂度。 第五步,应对可能的 followup。一般都会有。 lucifer: 我在网上找到一份 《Interview Cheat Sheet》,这个 PDF 列举了面试的模板步骤。,详细指示了如何一步步完成面试。地址:https://github.com/azl397985856/leetcode/blob/master/assets/cheatsheet.pdf 整个过程尽可能积极,创造快乐积极的氛围,把面试官当同事。 lucifer: 这句话我熟啊,上次听似乎还是在狗头那儿 心态面试途中除了刷题挫败,还有就是对自己怀疑&对现状不满之类的负面情绪需要处理。如果需要心理咨询,尽快做,比如 betterhelp,也许可以减少弯路;尽可能每天瑜伽垫上跟视频运动下,同时多输入积极的东西,看看别人在最艰难的时候是怎么做的,给自己打打鸡血。每天至少 10 分钟,闭眼憧憬下未来,越疯狂和理想越好,想不到看不到,就很难有行动力,尤其是当外界没有一点正反馈的时候。最后就是保持感激,最艰难的时候,一定会有支持和帮助你的人,如果没有的话,就换圈子和积极寻找。再差再难过也得有点信念吧,这点是最难也是最重要的。 lucifer: 好的状态可以事半功倍 学习资料北美的同学,可以考虑一下 CodePath,是个很好的公益组织,有免费的刷题课程帮助提升 tech diversity,讲师都是名企的,一般都是 CodePath 之前的校友,现在反哺。如果已经上班了,也可以考虑志愿做 CodePath mentor,可以有效提高沟通能力。和西法讲义结合,强强联手,另外 AlgoExpert 也不错的。如果时间有限,可以只刷 AlgoExpert。题不在多,在于思路覆盖面。 最后找工作可以很漫长,也可以很快,很多变数,而且很看缘分,很多时候更是自己和自己的斗争吧,在最低谷的时候,也请相信自己。","categories":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/categories/谷歌/"},{"name":"面试","slug":"谷歌/面试","permalink":"https://lucifer.ren/blog/categories/谷歌/面试/"}],"tags":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/tags/谷歌/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"}]},{"title":"快来 **伯克利大学** 学计算机","slug":"daily-featured-2022-04","date":"2022-05-14T16:00:00.000Z","updated":"2023-01-07T14:01:24.672Z","comments":true,"path":"2022/05/15/daily-featured-2022-04/","link":"","permalink":"https://lucifer.ren/blog/2022/05/15/daily-featured-2022-04/","excerpt":"","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2022-042022-04-28[工具]import-local 是一个 NodeJS 端的工具,用于检测本地是否安装了某个包。如果你在开发一个 node 的 cli 应用,并且想要提高性能使用用户本地安装好的包,它就很适合你。 via: https://github.com/sindresorhus/import-local 2022-04-28[好文]rest api 中的 POST 和 PUT 有什么区别?一个用于新建资源,一个用于更新资源?不是的! via: https://stackoverflow.com/questions/630453/what-is-the-difference-between-post-and-put-in-http 2022-04-25[网站]一个低代码平台,通过它可以拖拽生成自己的网站。 值得一提的是,一些框架已经开始集成它了。你可以通过 builder.io 导出代码,然后通过一些工具生成各个框架的中间代码(比如 react,vue),也可以直接生成原生 JS 代码。 via: https://www.builder.io/ 2022-04-24[工具] 今天是中国五一假期的调休。调休真的是一个反人类的设计。 cypress 是一个 e2e 测试工具,可以很容易地集成到各个测试框架中去,比如 jest。 via: https://github.com/cypress-io/cypress 2022-04-23[好文]Navidrome 是一个音乐管理系统,你可以将其部署到本地,然后通过网页播放器播放本地音乐。Navidrome 体验 via: https://demo.navidrome.org/app/ 很多其他的工具使用的都是网上现成的资源,比如 1listen 就是使用的虾米,QQ 和网易云的音乐源。 via: https://www.navidrome.org/ 2022-04-22[好文]之前我写过 TypeScript 系列教程,其中有一篇是 上帝视角看 TypeScript 这个文章和我的那篇很像,都是从宏观上带大家理解 TypeScript 究竟做了什么。这篇文章比我的更细致一点,推荐大家结合起来阅读。 via: https://www.huy.rocks/everyday/04-01-2022-typescript-how-the-compiler-compiles 2022-04-21[网站]yandex 提供了简洁的在线翻译功能。 你可以直接输入文字进行翻译,也可以上传文件整体翻译。 它还提供了 api 供开发者使用,我的 leetcode 项目的部分内容就是用它进行翻译的。不得不承认,专有名词的翻译还是不行,比如动态规划会翻译为 dynamic planning。 via: https://translate.yandex.com/ 2022-04-20[好文]只要 5 美元就可以破解指纹解锁?Your Fingerprint Can Be Hacked For $5. Here’s How. via: https://blog.kraken.com/post/11905/your-fingerprint-can-be-hacked-for-5-heres-how/ 2022-04-19[好文]如果检测有没有全局变量引起的内存泄漏?这篇文章告诉你,作者提供了 js 代码,大家可以直接拿来主义。 文中有一点没有提到,其实很多全局变量是需要一定条件才会触发的。因此要想真正将其集成到项目的 CI 中,还需要一些额外的条件,那就是在程序中手动多次调用检测方法,而不是调用一次就完事了。 via: https://mmazzarolo.com/blog/2022-02-14-find-what-javascript-variables-are-leaking-into-the-global-scope/ 2022-04-18[工具]上海疫情使得很多人买不到菜。热爱折腾的网友开源了抢菜软件。 注:如非必要,不要使用这种极端方法,这会给其他没有菜吃的人带来很多麻烦。 via: ddshop dingdong-helper 2022-04-15[技巧]Github 的 issue 有很多不好用的地方,比如不支持 comment 自定义排序,以至于有一些插件“钻了空子”,提供了按照 reactions 进行排序的功能。 Disscussion 弥补了这块空白。 Disscussion 内置两种排序规则,分别是时间顺序和投票数。 你可以结合使用 issue 和 Disscussion 获得更好的体验。 近期 Github 还给 Disscussion 提供了问答社区才有的功能 - 选为答案。 只需要在新建 Disscussion 的时候类别选择 Q&A 就可以体验这个功能了。 via: https://github.com/azl397985856/leetcode/discussions 2022-04-14[好文]JS 的继承和传统的 class 继承(比如 Java 的)有什么区别?(How does JavaScript’s prototypal inheritance differ from classical inheritance?) via: https://dev.to/chalarangelo/how-does-javascripts-prototypal-inheritance-differ-from-classical-inheritance-oii 2022-04-13[网站]和昨天的推荐类似,这个网站也是移除不想要的部分的神奇网站。 只不过它不是移除图片中不想要的部分,而是分离音频中的人声和非人声。这样就可以达到移除人声或者移除噪音的效果。 via: https://vocalremover.org/ 2022-04-12[网站]一个无需注册的在线网站,你可以用它来移除图片中的部分内容。 via: https://www.magiceraser.io/ 2022-04-11[网站]一个俄罗斯的网站,据说是全世界最大的名画博物馆。 并且提供免费的高清下载,比如蒙娜丽莎这里可以直接下载,分辨率是 3931 * 5178,4 M 左右的大小。 via: https://gallerix.asia/ 2022-04-08[网站]Games104 网站提供了从零学习游戏引擎的教程,有成型的完整代码托管在开源的 Github 仓库。 有做游戏的,或者想了解游戏引擎的可以看一下。 via: https://games104.boomingtech.com/sc/course-list/ 2022-04-07[好文]chrome 103 目前支持了 fs api。 用户可以通过 fs api 来读取文件,写入文件,删除文件,创建文件等。 比如读取文件的代码: 12345678910let fileHandle;document.querySelector(\".pick-file\").onclick = async () => { [fileHandle] = await window.showOpenFilePicker(); const file = await fileHandle.getFile(); const content = await file.text(); return content;}; 除了 chrome 103 ,其他部分浏览器的新版本也提供了支持,具体支持情况如下图。 via: https://css-tricks.com/getting-started-with-the-file-system-access-api/ 2022-04-06[杂谈]想去贵州看樱花~ via: https://fashion.sina.cn/l/ds/2022-03-07/detail-imcwipih5777616.d.html 2022-04-05[工具]bitbucket 是一个开源的代码仓库,可以用来存放开源项目的代码。 和 Github,Gitlab 不同,bitbucket 内置了 jira 用于管理需求 ,snyk 用于管理 包安全。个人感觉 Github 和 Gitlab 在这几方面体验还没那么好。 via: https://bitbucket.org 2022-04-03[技巧]vscode 中会自动为 typescript 项目选择 workspace 的 node_modules 的 typescript,但是我们可以手动选择 workspace。 方法很简单, 你只需要打开一个 workespace 下的 TypeScript 文件,然后点击右下角的 TypeScript 旁边的版本号。 然后会让你选择版本。 如果有多个 TypeScript ,错误使用其他版本的 TypeScript 会导致编译失败。项目中可以通过配置 vscode 的方式解决这问题。 具体地,大家可以在项目根目录的 .vscode 文件夹下新建一个 setting.json 然后进行如下配置。 123{ \"typescript.tsdk\": \"node_modules/typescript/lib/typescript.js\"} 更多用法参考官方文档:https://code.visualstudio.com/docs/typescript/typescript-compiling 2022-04-02[好文]Github 面试还会布置家庭作业? 家庭作业也通过 Github 进行。大概是给你一个仓库,然后你 fork 过去后进行编辑,完成后 pr 到原仓库进行 review。 via: https://github.blog/2022-03-31-how-github-does-take-home-technical-interviews/ 2022-04-01[网站]CS61A(Structure and Interpretation of Computer Programs)是伯克利所有计算机系学生必须要上的第一门编程课,前半部分以 Python 为主,后半部分以 Schema 为主。网站资源很丰富,作为一个普通游客最主要的就是课件,其提供了 html 和 pdf 两种格式。课件图文丰富,这和其他同级别课程差异很大,对新手比较友好。 via: https://cs61a.org/ 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2022-04","slug":"每日一荐/2022-04","permalink":"https://lucifer.ren/blog/categories/每日一荐/2022-04/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"西法的 2022 书单推荐","slug":"books-2022","date":"2022-04-25T16:00:00.000Z","updated":"2023-01-05T12:24:49.733Z","comments":true,"path":"2022/04/26/books-2022/","link":"","permalink":"https://lucifer.ren/blog/2022/04/26/books-2022/","excerpt":"去年推荐了基本技术书单,推荐的主要图书有: 你不知道的 JavaScript 淘宝地址 京东地址 算法图解 淘宝地址 京东地址 算法第四版 淘宝地址 京东地址 读者反响还不错,这次推荐了一些更专业的书单。","text":"去年推荐了基本技术书单,推荐的主要图书有: 你不知道的 JavaScript 淘宝地址 京东地址 算法图解 淘宝地址 京东地址 算法第四版 淘宝地址 京东地址 读者反响还不错,这次推荐了一些更专业的书单。 从一到无穷大第一本是大名鼎鼎的乔治.伽莫夫 的《从一到无穷大》,李永乐也非常推荐这本书,还针对这本书录制了视频,视频在 B 站上付费订阅的。 这本书里面的内容非常有趣,直接就点燃了我的学习欲望。比如文中提到所有的偶数和所有的整数是一样多的,还给出了证明方法。你可能会想这怎么可能呢? 然后就忍不住读下去。文中还穿插一些小故事,读起来没那么类,属于科普向的图书,没有很硬核的感觉。但是当你读完这本书的时候,会发现自己知识增加了。😄 淘宝地址 京东地址 时间简史这本书我还没有看完,就迫不及待和大家分享了。和《从一到无穷大》主题上有一点重叠,不过更加深入,会比较难懂一些。 如果你肯耐心看下去,可能就会发现其中的乐趣所在。等我看完了再给大家谈谈读后感。 淘宝地址 京东地址 黑客与画家这本书没有教你怎么写代码,似乎整本书又都在教你怎么写代码。 给你一种讲了,似乎又没讲的感觉。文中很多的观点倒现在都没有过时,要知道这本书出版过了几十年了(中译本都十几年了)。 摘一段黑客与画家的一个小节给大家感受一下。 《黑客与画家》摘录 - 14. 梦寐以求的编程语言让我们试着描述黑客心中梦寐以求的语言来为以上内容做个小结。 这种语言干净简练,具有最高层次的抽象和互动性,而且很容易装备,可以只用很少的代码就解决常见的问题。不管是什么程序,你真正要写的代码几乎都与你自己的特定设置有关,其他具有普遍性的问题都有现成的函数库可以调用。 这种语言的句法短到令人生疑。你输入的命令中,没有任何一个字是多余的,甚至用到 shift 键的机会也很少。 这种语言的抽象程度很高,甚至你可以快速写出一个程序的原型。然后,等到你开始优化的时候,它还提供一个真正出色的性能分析器,告诉你应该重点关注什么地方。你能让多重循环快得难以置信,并且在需要的地方还能直接嵌入字节码。 这种语言有大量优秀的范例可供学习,并且非常符合直觉,你只需要花几分钟阅读范例就能领会应该如何使用此种语言。你偶尔才需要查阅操作手册, 它很薄,里面关于限定条件和例外情况的警告寥寥无几。 这种语言内核很小,但很强大。各个函数库高度独立,并且和内核一样经过精心设计,它们都能很好地协同工作。语言的每个部分就像精密照相机的各种零件一样完美契合,不需要为了兼容性问题放弃或者保留某些功能。所有的函数库的源码都能很容易得到。这种语言能很轻松地与操作系统和其他语言开发的应用程序对话。 这种语言以层的方式构建。较高的抽象层透明地构建在较低的抽象层上。如果有需要的话,你可以直接使用较低的抽象层。 除了一些绝对必要隐藏的东西。这种语言的所有细节对使用者都是透明的。它提供的抽象能力只是为了方便你开发,而不是强迫你按照它的方式行事。事实上,它鼓励你参与它的设计,给你提供与语言创作者平等的权利。你能够对它的任何部分加以改变, 甚至包括它的语法。它尽可能让你自己定义的部分与它本身定义的部分处于同等地位,这种梦幻般的编程语言不仅开放源码,更开放自身的设计。 淘宝地址 京东地址 哲学家们都干了些什么?这本书和技术无关了,但个人认为也属于是科普读物。 哲学家这种在我们看来十分高大上的物种,在这本书中被扒拉地明明白白。这让我想起来呼兰讲过的一个段子: A: 你是干什么的? B(意味深长地说): 我是诗人。 A(弱弱的问): 那你是一毕业就当的诗人么? 哦,原来诗人就是无业游民啊。 这本书也是一样,让你在轻松愉快中笑一笑还把知识还顺便给学了,这上哪讲理去? 淘宝地址 京东地址 总结以上就是我强烈推荐给大家阅读的四本书。如果你有什么好书推荐的话,可以私信我哦~ 我最近缺好书看了,另外说不定下一期的书单就有它了。","categories":[{"name":"书单","slug":"书单","permalink":"https://lucifer.ren/blog/categories/书单/"}],"tags":[{"name":"书单","slug":"书单","permalink":"https://lucifer.ren/blog/tags/书单/"}]},{"title":"Git 中的算法第二弹-最近公共祖先","slug":"git-merge-base","date":"2022-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.757Z","comments":true,"path":"2022/04/06/git-merge-base/","link":"","permalink":"https://lucifer.ren/blog/2022/04/06/git-merge-base/","excerpt":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第二篇 - 《Git 中的最近公共祖先》,第一篇在 这里","text":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第二篇 - 《Git 中的最近公共祖先》,第一篇在 这里 git merge-basegit merge-base A B 可以查找 A 提交和 B 提交的最近公共祖先提交。而由于 分支和标签在 Git 中仅仅是提交的别名,因此 A 和 B 也可以是分支或者标签。 12345 o---o---o---B /---o---1---o---o---o---A 如上图的 Git 提交情况,那么 git merge-base A B 会直接返回提交 1 的哈希值。 更多关于 merge-base的用法细节可以参考 官方文档 如何查找公共祖先呢?我们知道 git 每次提交,实际上都是新建了一个提交对象,里面记录一些元信息。比如: 提交人 提交时间 哈希 上一次提交的引用 。 而上一次提交的引用导致了 git 提交是一个链表结构。而 git 支持创建分支,并基于分支进行开发,因此 git 提交本质上是有向无环图结构。 如上图,我们基于提交 2 创建了新分支 dev,dev 上开发后我们可以将其合并到主分支 master。 而当我们执行合并操作的时候,git 会先使用 merge-base 算法计算最近公共祖先。 如果最近公共祖先是被 merge 的 commit, 则可执行 fast-forward。如下图,我们将 dev 合并到 master 就可以 fast-forward,就好像没有创建过 dev 分支一样。 最后举一个更复杂的例子。如下图,我们在提交 3 上执行 git merge HEAD 提交 6。会发生什么? 答案是会找到提交 2。那怎么找到 2 呢? 如果从提交 6 出发不断找父节点,找到 1,并将其放到哈希表中。接下来再从提交 3 出发同样不断找父节点,如果父节点在哈希表中存在,那么我们就找到了公共祖先,由于是找到的第一个公共祖先,因此其是最近公共祖先,直接返回即可。 力扣中刚好有一个题目,我们来看下。 力扣真题题目地址 (236. 二叉树的最近公共祖先)https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ 题目描述1234567891011121314151617181920212223242526272829303132给定一个二叉树,找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 示例 1:输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1输出:3解释:节点 5 和节点 1 的最近公共祖先是节点 3 。示例 2:输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4输出:5解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。示例 3:输入:root = [1,2], p = 1, q = 2输出:1 提示:树中节点数目在范围 [2, 105] 内。-109 <= Node.val <= 109所有 Node.val 互不相同 。p != qp 和 q 均存在于给定的二叉树中。 前置知识 二叉树 树的遍历 哈希表 思路这道题是给你一个二叉树,让你从二叉树的根出发。 这和 Git 是不一样的,Git 中我们需要从两个提交节点出发往父节点找。 那是不是意味着上面方法不可以套用了? 也不是。我们可以在遍历二叉树的时候维护父子关系,然后问题就转化为了前面的问题。 代码 语言支持:Java Java Code: 123456789101112131415161718192021222324252627282930313233class Solution { HashMap<Integer, TreeNode> map = new HashMap<>(); // 关系为:key 的父亲是 value HashSet<TreeNode> set = new HashSet<>(); public void dfs(TreeNode root) { if (root.left != null) { map.put(root.left.val, root); dfs(root.left); } if (root.right != null) { map.put(root.right.val, root); dfs(root.right); } } public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { dfs(root); // 从 p 出发,找到 p 的所有祖先节点,将其放入 HashSet while (p != null) { set.add(p); p = map.get(p.val); } // 从 q 出发找到第一个能在 HashSet 中找到的节点,即为最近公共祖先 while (q != null) { if (set.contains(q)) { return q; } q = map.get(q.val); } return null; }} 复杂度分析 令 n 为链表长度。 时间复杂度:$O(n)$ 空间复杂度:$O(n)$ 优化实际上该算法效率并不高。如果我们仓库提交很多,也就是 N 非常大,也是会慢的。 有没有优化的可能? 当然可以。而且优化的角度有很多。 这不,这位同学就想到了预处理。 链接在这里。即第一次维护好了节点信息,将其存到文件里,那么以后执行 merge-base,就不需要对已经处理过的节点进行遍历了。理论上,不管 merge-base 多少次,我们都仅遍历一次节点。 真的这么理想么? 很可惜不是的。比如我执行了 rebase ,reset 等操作改变了节点怎么处理?这里的细节很多,我就不在这里展开了。感兴趣的可以加入我的力扣群讨论。 总结git merge-base 本质上就是一个寻找最近公共祖先的算法。 而这个算法最朴素的就是先从一个节点使用哈希表预处理,然后从另外一个节点开始遍历,找到的第一个在哈希表中出现的节点就是最近公共祖先。 这个算法也有优化空间,而优化后又需要考虑各种边界条件,即缓存失效问题。 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[],"tags":[{"name":"算法,最近公共祖先","slug":"算法,最近公共祖先","permalink":"https://lucifer.ren/blog/tags/算法,最近公共祖先/"}]},{"title":"程序员如何准备面试中的算法","slug":"interviewer","date":"2022-03-20T16:00:00.000Z","updated":"2023-01-05T12:24:50.091Z","comments":true,"path":"2022/03/21/interviewer/","link":"","permalink":"https://lucifer.ren/blog/2022/03/21/interviewer/","excerpt":"春招季来临,大家陆续已经开始准备面试斩获心仪 offer。 这次 lucifer 就从面试官角度给大家分享一些面试技巧,让大家面试时少走弯路。这次分享侧重算法面试。 我负责公司的面试已经有 5 年以上了,基本都是初面和二面,因此技术面试的层面比较深,更多的是了解候选人的技术能力是否达标。在这几年时间,我前前后后也面试了很多的候选人。这些人中有的技术能力不行,但也有些人很可惜,技术能力是可以的,但是却没能通过我的面试,为什么呢?","text":"春招季来临,大家陆续已经开始准备面试斩获心仪 offer。 这次 lucifer 就从面试官角度给大家分享一些面试技巧,让大家面试时少走弯路。这次分享侧重算法面试。 我负责公司的面试已经有 5 年以上了,基本都是初面和二面,因此技术面试的层面比较深,更多的是了解候选人的技术能力是否达标。在这几年时间,我前前后后也面试了很多的候选人。这些人中有的技术能力不行,但也有些人很可惜,技术能力是可以的,但是却没能通过我的面试,为什么呢? 面试考察什么?首先,通常判断候选人是否可以通过面试有如下几个标准: 技能能否胜任 沟通能力如何 工作热情如何 。。。 那么我面试的时候肯定也是围绕上面展开的,只不过侧重考察不同罢了。 算法题和编程题实际上能够很好地检验以上信息,而不仅仅是检验能力是否胜任。前提是面试官不能太死板,比如直接扔一个网上原题,有的甚至自己都不太会就拿出来考,这肯定是不行的。 有同学反馈算法题目做出来了,但是却挂了面试。这又是为什么呢? 除了想当然的那种做的很好,实际上 corner case 没考虑到或者不是最优解。还是有可能会被挂,为什么呢? 其实你题目做的很好,仅仅可以证明能力可以胜任,这不意味着其他也满足要求,比如上面提到的沟通能力,工作热情等等。 那么如何更好地展示自己,给面试官留下更好的印象从而通过面试呢?除了提高自己的技术实力之外,方法也很重要。这里我就总结了几个技巧给大家。 算法面试基本步骤 我在网上找到一份《Interview Cheat Sheet》,这个 PDF 列举了面试的模板步骤,详细指示了如何一步步完成面试。 这个 pdf 开头就提到了好的代码三个标准: 可读性 时间复杂度 空间复杂度 这写的太好了。 紧接着,列举了 15 算法面试的步骤。比如步骤一:当面试官提问完后,你需要先下来关键点(之后再下面写注释和代码) 看完我的感受就是,面试只要按照这个来做,成功率蹭蹭提升。 pdf 地址 多问几次,确保题目理解正确。 比如输入输出,corner case 等等。试想一个同事拿到需求不分青红皂白就去做,结果发现做出来的不对,多尴尬?你会想和这样的同事一起共事么? 比如你可以问: 需要考虑负数么? 结果的顺序重要么? 可以使用额外空间么? 。。。 先说思路,再写代码。 虽然题目理解没问题,但是可能思路根本不对,或者面试官不想要这个解法。 试想你是面试官, 对面写了半天代码。思路根本就不对或者不是你想要的解法,是不是有点失望? 所以尽量先说思路,面试官觉得没问题再写,不要浪费彼此的时间。 比如你可以说: 朴素的暴力的思路是:xxxx。而这么做的话时间复杂度是 xxxx。 朴素的暴力算法的瓶颈在于 xxx,我们可以使用 xxxx 算法进行优化,这样复杂度可以优化到 xxxx。 上一步骤给面试官讲思路的时候,代入几个例子。 corner case 和 normal case 都至少举一个来说明。这样不仅让面试官感觉你沟通能力强,而且可以帮助你进一步理解题目以及理清思路。 有点时候大家面试比较紧张,经过代入例子讲解紧张感会慢慢减少。就好像我做技术分享,往往紧张的就是前面几分钟,后面就不会有紧张的感觉了。 比如你可以说: 当输入为 [1,2,3,4] 时, 我们的先 xxxx, 这样就 xxxx,接下来计算出 xxxx ,最后xxxx 。 当输入为负数时,我们可以直接返回 xxx。 写代码要快,不要来来回回改,不然就会被扣上 coding 不行的帽子。 其实有前面的铺垫,写快不难。因为前面其实讲思路,通过例子讲解法你已经对算法很了解了才对。 但是思路没问题不代表可以完整写出来。同样可以完整写出来不代表不需要涂涂改改。这需要大家做题目前先勾勒出代码的大体框架。 一个简单的技巧就是:分模块写代码,一个功能一个函数。这样可以减少不断地涂涂抹抹,修修补补的可能性。 一个例子: 12345678910def solve(nums): def check(mid): # do something def another_func(): pass # ... l, r = 0, len(nums) - 1 while l <= r: mid = (l + r) // 2 check(mid) 其中 solve 为主体函数,而 check 和 another_func 则为拆分的函数。 我给大家的练习建议是:要先刷题,并且要重复地刷,一道题刷 3 遍胜过刷 3 道不同的题。 一般来说,一道题(包括纯换皮)刷个 3,5 遍是正常的。不知道刷什么题,怎么刷的可以在我公众号回复 91 参加 91 天打卡活动。 写完代码自己先写个测试。 这不仅体现了你代码习惯好,而且能帮你发现代码写的有没问题。 小技巧:你可以把前面你和面试官举的例子以及面试官给的例子代进去看对不对,由于有前面铺垫了,这个应该也很快。 一个例子: 1234567891011121314def solve(nums): def check(mid): # do something def another_func(): pass # ... l, r = 0, len(nums) - 1 while l <= r: mid = (l + r) // 2 check(mid)assert solve([1,2,3,4]) == Trueassert solve([]) == False# ... 这里我们使用 assert 进行了断言。类似于我们日常开发后对代码进行测试。 总结最后给大家整理一个流程图,方便大家记忆,大家可以把图存起来备用。 最后希望大家可以在春招季斩获自己信息的 offer。也欢迎大家进我的春招群。加我微信,回复春招即可进群。","categories":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"}],"tags":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第七期)","slug":"91algo-7","date":"2022-03-11T16:00:00.000Z","updated":"2023-02-07T04:15:30.327Z","comments":true,"path":"2022/03/12/91algo-7/","link":"","permalink":"https://lucifer.ren/blog/2022/03/12/91algo-7/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言前一期正式于 2022-03-12 结束,下一期正式开启的时间为 2022-04-01。今天开始正式报名,活动开始前大家可以先预习。 为什么要04-01 开始,这么久?原因在于最近力扣举办了2022 春招季活动。这个活动主要是三月份,有很多同学想参与。为了和这个活动分流,大家也刷地不那么累。决定 4 月再开启,希望大家可以理解。 力扣 2022 春招季活动我加入的是二进制战队。我还组建了一个春招群,大家想进的可以话可以加我个人微信私信我加群(微信号 DevelopeEngineer)。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第七期和往期的不同。 有的同学反应 91 题太少了,而讲义给的题目太多了。为此第七期我删除了部分讲义题目,然后增加推荐加练小节,里面对于一些强烈推荐的会给一个标识。这样大家可以根据自己的情况练习。学有余力的多刷刷,有一点时间的把我强烈推荐的做做即可。真没时间的就做做打卡题好了。 如果做打卡题的时间也没有的话就不推荐参与了。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-04-01 至 2022-06-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 注意上面的时间是上一期的。这期的具体时间会在活动正式开始前(2022-04-01 前)在官网中更新,敬请期待! 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第七期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第七期和前六内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"91 第七期和战队赛一起来了?","slug":"zhandui","date":"2022-03-03T16:00:00.000Z","updated":"2023-01-07T12:34:55.853Z","comments":true,"path":"2022/03/04/zhandui/","link":"","permalink":"https://lucifer.ren/blog/2022/03/04/zhandui/","excerpt":"有两个事情和大家同步。","text":"有两个事情和大家同步。 第七期《91 天学算法》第一个事情是最近问的比较多的问题:下一期(第七期)什么时候开始。 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 大概时间就是三月份, 具体时间需要等公众号正式通知,以防止大家提前蹲点抢票。老规矩,前 50 免费。 力扣春招季第二个事情是最近和力扣官方合作的一个福利活动。 力扣春招季上线了一个刷题活动,届时官网会有专门的活动入口。我准备进个战队,大家看要不要一起? 时间也是三月份, 大概三月中旬的样子。具体时间等公众号通知,大家点个关注和星标后耐心等待即可。 此外,我还邀请力扣的工作人员进群,来围观大家的刷题,并且在活动期间给大家送一些 buff。 buff 有很多。比如: 免费的春招直播课:算法笔试技巧和算法技术分享,技术有提升; 官方内容每日精选:春招季每日 1 道押题、站内用户总结的优质面经以及其他有趣的话题内容; 持续刷题彩蛋:算法资讯,面试资料、经典好书,努力有激励; 社群刷题氛围:志同道合的同学与助教老师,学习有交流。 。。。 ​","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"远程办公有多爽?","slug":"wfh2","date":"2022-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.579Z","comments":true,"path":"2022/02/20/wfh2/","link":"","permalink":"https://lucifer.ren/blog/2022/02/20/wfh2/","excerpt":"大家好,我是 lucifer! 上一次和大家聊我的工作,算起来有超过半年的时间了。很多同学对远程办公比较感兴趣,我们就继续聊聊这个话题。 如果用一句话来概括文章内容的话,那就是:工作有趣,待遇满意,时间充足。 以下是详细内容:","text":"大家好,我是 lucifer! 上一次和大家聊我的工作,算起来有超过半年的时间了。很多同学对远程办公比较感兴趣,我们就继续聊聊这个话题。 如果用一句话来概括文章内容的话,那就是:工作有趣,待遇满意,时间充足。 以下是详细内容: 工作有趣具体内容涉及公司机密,不方便透露。但是可以说的是,目前负责的内容是一些前端基础设施的建设,具体内容涉及到工程化,编译,跨端等。并且负责的内容和之前工作有一定差异,因此也学到了不少东西。 ”拥有高绩效远程团队的公司会投资于员工完成工作所需的技术“,往往远程办公的技术团队的技术能力更强! 节奏上,也是常见的 sprint 形式来组织,并且 task 不会压得特别紧,特别赶。 很多时候工作上有很大的自我发挥空间。即不是明确到位的一个方法让你填空,更像是问答题,让你自己发挥的成分会多一点。 待遇满意不方便透露具体数字。但是可以向大家说:我现在的工资福利待遇是原来的不止两倍。 和国内大公司(比如头条,阿里等)对比的话,普遍也比他们要高一点。 时间充足不仅仅是待遇丰厚,时间也很充裕。 由于没有通勤时间,因此时间上会有一些天然优势。有些人原本单程通勤就需花费 30 分钟,而在家办公每周可节省五个小时的时间,此外还能节省交通成本。 我们虽然没通勤成本,但是交通补助啥的还是有的 因此我时不时可以和朋友出去 high 一下,逛街打游戏的时间也比以前多。 这已经感觉自己相当自由了,而且我的年假还有 10 多天。如果把这年假用到旅游的话,去趟国外都没问题。一年一次长途旅行根本不是梦。 回答问题最后回答一些网友常见的问题。 工作会摸鱼么? 答案是不会刻意摸鱼。 对我而言,摸鱼是为了对抗明明工作都完成了,也到点了,可就是不能(或不好意思或为了蹭补助)走的局面。和坐班不同,不要求你一定在工位上,因此远程办公就没有这个问题。 会不会 007 待命? 我没有 007 待命。个人认为 007 待命和是否远程办公无关,而是和公司有关。如果一个公司的文化就是这样,那么无论是否远程,都需要随时 oncall,不需要我举例子了吧? 工作和生活没有界限? 其实还好。大多数情况干够 8 个小时,我就不会继续工作了,而是全心投入生活。除非某一个任务比较紧急或者由于自身问题没有完成,实际这些情况都很少。 那你英语肯定很好吧? 我英语不好,尤其是口语。 实际工作中,面对英语的情况大多数是文字以及例会。而例会虽然是口语英语,但是需要你说话的情况比较少。你想啊,一大堆人开会,一个人发言的时间会有多少? 因此口语在我们这里问题不大。 不过你的英语阅读能力和听力还是有点要求的,不然还是有点问题的。 面试肯定很难吧?我行么? 面试难度和国内大公司难度差不多,侧重点不同。我们更侧重思维,架构设计,算法等底层能力,对框架,语言等要求不高。 我举几个面试题的例子给大家感受一下。 如果我们的网站 QPS 突然变得特别大,以至于当前的单机服务器的模式顶不住了,你会怎么做? 给你一堆股票的价格,让你不断地求最近一小时的股票平均值。比如每秒计算一次最近一个小时的股票平均值。 。。。 系统设计的话可以参考这个 system-design-primer 仓库.这个仓库从大的方向讲述了系统设计中的常见场景,比如如何设计高可用系统。这个仓库 star 非常多,从侧面反应项目质量还行,掘金官方也参与了仓库的翻译工作,大概看了下,翻译质量不错。 算法的话可以参考下这个 leetcode,里面有几十个算法专题以及几百道经典算法面试题的解析,通俗易懂。掌握了里面所有内容后面试绝大多数公司至少算法这块是没问题的。 内推你如果也想要这样的远程办公(wfh),工作和生活平衡(work life balance) 的工作,可以找我内推,大家加我的微信就好(可以提供免费面试咨询哦) 目前的岗位仅支持 base 到国外,相应待遇会直接对标国外的薪资标准,不打算出国的同学就不用考虑了。不过这里还有一些别的远程办公公司可以给大家内推,比如微软, Gitlab 加好友请注明:内推","categories":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/categories/成长经历/"}],"tags":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/tags/成长经历/"}]},{"title":"Git 中的二分","slug":"git-bisect-bug","date":"2022-02-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.982Z","comments":true,"path":"2022/02/11/git-bisect-bug/","link":"","permalink":"https://lucifer.ren/blog/2022/02/11/git-bisect-bug/","excerpt":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第一篇 - 《Git 中的二分》","text":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第一篇 - 《Git 中的二分》 二分代码当我发现一个难以理解和发现的 bug 的时候,我的终极办法就是二分代码。 比如先删去文件 a 的一半代码(大约),然后测试问题是否还在。 如果问题不在了,那么我们就找到了出问题的代码。 如果问题还在,那么我们继续使用同样的办法,继续删去文件 a 的一半代码(大约),然后测试问题是否还在。 重复上述步骤,直到找到出问题的代码 这个方法在我一时没有思绪或者帮助别人定位问题的时候非常有用。由于这种做法时间复杂度大致是 logn,因此只需要短短几次我们就可以大致定位到问题代码。类似地,我们也可以在多个文件中同时进行二分。 二分提交更多的时候,我们是在两次发布之间发现了一个 bug。而这两个发布之间是有若干 commit的,并且 commit 还不少(几十个甚至上百个)。 我们也可以使用类似的方法。 先切换到两次发布之间的中间提交 x(即使得提交 x 相对于两次发布之间的距离差最小)。 实际上这个最小距离差要么是 0, 要么是 1 验证问题是否存在。如果不存在,我们不能确定就是这个提交的问题,不妨先标记当前提交 c 为 good。如果存在,不妨标记当前提交 c 为 bad。 经过上面的标记,我们就可以找到最早呈现 bad 的那次提交,并且最关键的是复杂度为logn,其中 n 为我们需要验证的提交的总次数。显然,这个工作量比逐个检查 commit要快很多。 不理解其中原理?稍后我们会讲。 Git 中的二分查找git 的开发者也想到了这一点,因此提供了 bisect 命令来帮助我们做上面这个事情。 使用 bisect 进行问题查找主要依赖于下面的三个命令: git bisect start 启动一个查找,start 后面可以加上 good 和 bad 的提交点: git bisect start [rev bad] [rev good] 如果不加 good 和 bad 的提交点,那么 git 将会等到我们使用 good 和 bad 命令指定对应的提交点后开始进行查找 git bisect good 用来标记一个提交点是正确的,后面可以加上 rev 来指定某个特定的提交点,如果不加,则默认标记当前的提交点。 git bisect bad 用来标记一个提交点是包含问题的,如果 bad 后可以加上 rev 来指定某个特定的提交点,如果不加,则默认标记当前的提交点。 背后原理我们来补前面的坑。为什么经过这样的标记,我们就可以找到第一个有问题(标记 bad)的提交?并且时间复杂度为 $O(logn)$。 正好力扣有一道原理,我们直接用它来讲吧。 题目地址(278. 第一个错误的版本)https://leetcode-cn.com/problems/first-bad-version/ 题目描述123456789101112131415161718192021222324252627282930你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 示例 1:输入:n = 5, bad = 4输出:4解释:调用 isBadVersion(3) -> false调用 isBadVersion(5) -> true调用 isBadVersion(4) -> true所以,4 是第一个错误的版本。示例 2:输入:n = 1, bad = 1输出:1 提示:1 <= bad <= n <= 231 - 1 思路可以看出,这个过程和我们上面的描述是一样的。并且我们的目标都是找到第一个出错的提交。 需要明确的是: 如果一个版本 x 是 good 的,那么 [1, x] 之间的所有提交肯定都是 good 的,因此待检测版本变为 [x+1,n] 如果一个版本 x 是 bad 的,那么 [x, n] 之间所有的提交肯定都是 bad 的。由于我们要找到的是第一个有问题的版本,因此待检测版本变为 [1,x-1] 因此无论我们检测的版本是 good 还是 bad,我们都可以将待检测的版本数量变为一半,也就是说我们可以在 $logn$ 次内找到第一个有问题的版本。 如果你看过我的二分专题,应该知道这就是我总结的最左二分。 二分专题(上) 二分专题(下) 代码 语言支持:Python3 Python3 Code: 123456789101112class Solution: def firstBadVersion(self, n): l, r = 1, n while l <= r: mid = (l + r) // 2 if isBadVersion(mid): # 收缩 r = mid - 1 else: l = mid + 1 return l 复杂度分析 时间复杂度:$O(logn)$ 空间复杂度:$O(1)$ 总结二分大法在日常工作中应用还是蛮多的,二分找 bug 是其中一个很实用的技巧。最简单的二分找 bug 可以通过删除文件内容的方式进行。而如果文件很多,就很不方便了,这个时候我们可以使用二分提交来完成。 其中的原理其实也很简单,就是一个朴素的最左二分。如果大家对此不熟悉,强烈建议看下文章中提到的二分专题,两万字总结绝对让你有所收获。","categories":[],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/tags/二分/"}]},{"title":"跨域了? 装个插件就够了!","slug":"cors-extension","date":"2022-02-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.566Z","comments":true,"path":"2022/02/06/cors-extension/","link":"","permalink":"https://lucifer.ren/blog/2022/02/06/cors-extension/","excerpt":"浏览器为了安全引入了同源策略,这直接导致默认情况下前后端域名如果不同,那么则功能会受限。 随着前后端分离的发展,前端和后端职责的分离,前端会有专门的本地开发服务器(local dev server)用于本地开发。这个时候和后端接口联调时就很可能会遇到跨域安全问题。 这本身是浏览器的一种安全策略,但是却对前端开发造成了影响。如何解决呢?","text":"浏览器为了安全引入了同源策略,这直接导致默认情况下前后端域名如果不同,那么则功能会受限。 随着前后端分离的发展,前端和后端职责的分离,前端会有专门的本地开发服务器(local dev server)用于本地开发。这个时候和后端接口联调时就很可能会遇到跨域安全问题。 这本身是浏览器的一种安全策略,但是却对前端开发造成了影响。如何解决呢? 之前我的解决方式是通过本地代理(node 或 nginx 等服务)解决。基本思路就是给请求响应头增加大概如下内容: 1234Access-Control-Allow-Origin: https://foo.exampleAccess-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: X-PINGOTHER, Content-TypeAccess-Control-Max-Age: 86400 后来我使用了更方便的工具: 浏览器扩展。之后跨域问题便可以一去不复返。 刚开始的时候用的是一个专门给请求加跨域头的插件 Allow CORS: Access-Control-Allow-Origin ,地址:https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf/related?hl=en-US。 这个插件使用起来非常简单,只需要点击切换 on 和 off 的状态即可。 on 的时候会自动给请求头增加跨域功能,off 则相当于禁用了插件。 后来我发现这个插件有些头不会加上,比如 access-control-expose-headers 。 因此一个通用的给请求增加头信息的插件就有必要了。于是我选择了 requestly 美中不足是每个规则只能免费改一个头。不过好消息是你可以新建多个规则,每个规则改一个头就可以白嫖了。 地址:https://app.requestly.io requestly 不仅仅可以增加跨域请求头,理论上可以对请求和响应做任意的修改。因此用来做 mock,统一加参数等等都是可以的。 基于此,使用浏览器扩展就可以彻底解决前端在本地开发时候遇到的跨域问题了。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"听说逆向思维能够降低时间复杂度?","slug":"backward","date":"2022-01-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.606Z","comments":true,"path":"2022/01/27/backward/","link":"","permalink":"https://lucifer.ren/blog/2022/01/27/backward/","excerpt":"以终为始在日常生活中指的是先确定目标,再做好计划。之前读管理学的书的时候,学到了这个概念。 而在算法中,以终为始指的是从结果反向推,直到问题的初始状态。 那么什么时候适合反向思考呢?简单的原则就是: 正向思考的情况比较多 代码比较难写或者算法复杂度过高 这个时候我们可以考虑反向操作。 我的习惯是如果直接求解很难,我会优先考虑使用能力检测二分,如果不行我则会考虑反向思考。 关于能力检测二分,可以在我的公众号中找到,大家可以在《力扣加加》回复二分获取。 今天西法通过三道题来给大家聊聊到底怎么在写算法题的时候运用以终为始思想。","text":"以终为始在日常生活中指的是先确定目标,再做好计划。之前读管理学的书的时候,学到了这个概念。 而在算法中,以终为始指的是从结果反向推,直到问题的初始状态。 那么什么时候适合反向思考呢?简单的原则就是: 正向思考的情况比较多 代码比较难写或者算法复杂度过高 这个时候我们可以考虑反向操作。 我的习惯是如果直接求解很难,我会优先考虑使用能力检测二分,如果不行我则会考虑反向思考。 关于能力检测二分,可以在我的公众号中找到,大家可以在《力扣加加》回复二分获取。 今天西法通过三道题来给大家聊聊到底怎么在写算法题的时候运用以终为始思想。 机器人跳跃问题这道题来自于牛客网。地址:nowcoder.com/question/next?pid=16516564&qid=362295&tid=36905981 题目描述1234567891011121314151617181920212223242526272829303132333435363738时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 32M,其他语言64M机器人正在玩一个古老的基于DOS的游戏。游戏中有N+1座建筑——从0到N编号,从左到右排列。编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位。起初, 机器人在编号为0的建筑处。每一步,它跳到下一个(右边)建筑。假设机器人在第k个建筑,且它现在的能量值是E, 下一步它将跳到第个k+1建筑。它将会得到或者失去正比于与H(k+1)与E之差的能量。如果 H(k+1) > E 那么机器人就失去 H(k+1) - E 的能量值,否则它将得到 E - H(k+1) 的能量值。游戏目标是到达第个N建筑,在这个过程中,能量值不能为负数个单位。现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?输入描述:第一行输入,表示一共有 N 组数据.第二个是 N 个空格分隔的整数,H1, H2, H3, ..., Hn 代表建筑物的高度输出描述:输出一个单独的数表示完成游戏所需的最少单位的初始能量输入例子1:53 4 3 2 4输出例子1:4输入例子2:34 4 4输出例子2:4输入例子3:31 6 4输出例子3:3 思路题目要求初始情况下至少需要多少能量。正向求解会比较困难,因此我的想法是: 能力检测二分。比如能量 x 是否可以,如果 x 可以,那么大于 x 的能量都可以。依此我们不难写出代码。 反向思考。 虽然我们不知道最开始的能量是多少,但是我们知道最后的能量是 0 才最优,基于此我们也可以写出代码。 这里我们使用第二种方法。 具体来说:我们从后往前思考。到达最后一级的能量最少是 0 。而由于: 1next = pre + (pre - H[i]) 因此: 1pre = (next + H[i]) / 2 由于除以 2 可能会出现小数的情况,因此需要 ceil。 你可以: 1pre = math.ceil((next + H[i]) / 2) 也可以: 1pre = (next + H[i] + 1) // 2 // 是地板除,即向下取整 代码(Python)123456n = input()H = input().split(\" \")ans = 0for i in range(len(H) - 1, -1, -1): ans = (ans + int(H[i]) + 1) // 2print(ans) 复杂度分析 时间复杂度:$O(n)$ 空间复杂度:$O(1)$ 这个题目的关键一句话总结就是:我们需要确定最少需要多少初始能量,因此我们是不确定最初的能量的,我们可以确定的是达到最后一个建筑能量是 0 才最优。 2139. 得到目标值的最少行动次数第二道题是 2022.01 月份的一场周赛的第二题,题目还是比较新的。 题目地址https://leetcode-cn.com/problems/minimum-moves-to-reach-target-score/ 题目描述1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950你正在玩一个整数游戏。从整数 1 开始,期望得到整数 target 。在一次行动中,你可以做下述两种操作之一:递增,将当前整数的值加 1(即, x = x + 1)。加倍,使当前整数的值翻倍(即,x = 2 * x)。在整个游戏过程中,你可以使用 递增 操作 任意 次数。但是只能使用 加倍 操作 至多 maxDoubles 次。给你两个整数 target 和 maxDoubles ,返回从 1 开始得到 target 需要的最少行动次数。 示例 1:输入:target = 5, maxDoubles = 0输出:4解释:一直递增 1 直到得到 target 。示例 2:输入:target = 19, maxDoubles = 2输出:7解释:最初,x = 1 。递增 3 次,x = 4 。加倍 1 次,x = 8 。递增 1 次,x = 9 。加倍 1 次,x = 18 。递增 1 次,x = 19 。示例 3:输入:target = 10, maxDoubles = 4输出:4解释:最初,x = 1 。递增 1 次,x = 2 。加倍 1 次,x = 4 。递增 1 次,x = 5 。加倍 1 次,x = 10 。 提示:1 <= target <= 1090 <= maxDoubles <= 100 思路由于刚开始数字为 1,最终状态为 target。因此正向思考和反向思考都是 ok 的。 而如果正向模拟的话,虽然很容易实现,但是时间复杂度太高。 这是因为从 1 开始我们有两个选择(如果仍然可以加倍),接下来仍然有两个选择(如果仍然可以加倍)。。。 因此时间复杂度大致为 $O(target * maxDoubles)$。代入题目给的数据范围显然是无法通过的。 而正向思考比较困难,我们不妨从反向进行思考。 从 target 开始,如果 target 是奇数,显然我们只能通过 + 1 而来,即使我们仍然可以加倍。这样时间复杂度就降低了。不过这还不够,进而我们发现如果 target 为偶数我们应该选择加倍到 target(如果仍然可以加倍),而不是 + 1 到 target。这是因为 我们是反向思考的,如果你现在不选择加倍而是后面再选择加倍那么加倍带来的收益会更低 加倍的收益一定大于 + 1,换句话说加倍可以更快达到 target。 基于此,不难写出如下代码。 代码 语言支持:Python3 Python3 Code: 12345678910111213class Solution: def minMoves(self, target: int, maxDoubles: int) -> int: ans = 0 while maxDoubles and target != 1: ans += 1 if target % 2 == 1: target -= 1 else: maxDoubles -= 1 target //= 2 ans += (target - 1) return ans 复杂度分析 如果 maxDoubles 无限大,那么时间大概是 $log target$。而如果 target 无限大,那么时间大概是 maxDoubles。因此时间复杂度为 $O(min(maxDouble, log target))$ 时间复杂度:$O(min(maxDouble, log target))$ 空间复杂度:$O(1)$ LCP 20. 快速公交最后一道题是力扣杯的一道题,难度为 hard,我们来看下。 题目地址(20. 快速公交)https://leetcode-cn.com/problems/meChtZ/ 题目描述12345678910111213141516171819202122232425262728293031323334353637383940414243444546小扣打算去秋日市集,由于游客较多,小扣的移动速度受到了人流影响:小扣从 x 号站点移动至 x + 1 号站点需要花费的时间为 inc;小扣从 x 号站点移动至 x - 1 号站点需要花费的时间为 dec。现有 m 辆公交车,编号为 0 到 m-1。小扣也可以通过搭乘编号为 i 的公交车,从 x 号站点移动至 jump[i]*x 号站点,耗时仅为 cost[i]。小扣可以搭乘任意编号的公交车且搭乘公交次数不限。假定小扣起始站点记作 0,秋日市集站点记作 target,请返回小扣抵达秋日市集最少需要花费多少时间。由于数字较大,最终答案需要对 1000000007 (1e9 + 7) 取模。注意:小扣可在移动过程中到达编号大于 target 的站点。示例 1:输入:target = 31, inc = 5, dec = 3, jump = [6], cost = [10]输出:33解释:小扣步行至 1 号站点,花费时间为 5;小扣从 1 号站台搭乘 0 号公交至 6 * 1 = 6 站台,花费时间为 10;小扣从 6 号站台步行至 5 号站台,花费时间为 3;小扣从 5 号站台搭乘 0 号公交至 6 * 5 = 30 站台,花费时间为 10;小扣从 30 号站台步行至 31 号站台,花费时间为 5;最终小扣花费总时间为 33。示例 2:输入:target = 612, inc = 4, dec = 5, jump = [3,6,8,11,5,10,4], cost = [4,7,6,3,7,6,4]输出:26解释:小扣步行至 1 号站点,花费时间为 4;小扣从 1 号站台搭乘 0 号公交至 3 * 1 = 3 站台,花费时间为 4;小扣从 3 号站台搭乘 3 号公交至 11 * 3 = 33 站台,花费时间为 3;小扣从 33 号站台步行至 34 站台,花费时间为 4;小扣从 34 号站台搭乘 0 号公交至 3 * 34 = 102 站台,花费时间为 4;小扣从 102 号站台搭乘 1 号公交至 6 * 102 = 612 站台,花费时间为 7;最终小扣花费总时间为 26。提示:1 <= target <= 10^91 <= jump.length, cost.length <= 102 <= jump[i] <= 10^61 <= inc, dec, cost[i] <= 10^6 思路由于起点是 0,终点是 target。和上面一样,正向思考和反向思考难度差不多。 那么我们可以正向思考么? 和上面一样正向思考情况太多,复杂度过高。 那么如何反向思考呢?反向思考如何优化复杂度的呢? 由于题目可以在移动过程中到达编号大于 target 的站点,因此正向思考过程中坐标大于 target 的很多点我们都需要考虑。 而如果反向思考,我们是不能在移动过程中到达编号大于 0 的站点的,因此情况就大大减少了。而达编号大于 target 的站点只需要思考向右移动后再乘坐公交返回 target 的情况即可(也就是说我们是做了公交然后往回走的情况) 对于每一个位置 pos,我们都思考: 全部走路 直接乘公交 走几步再乘公交 在这三种情况取最小值即可。 问题的关键是情况 3,我们如何计算是走几步再乘公交呢?如果反向思考,我们可以很简单地通过 pos % jump[i] 算出来,而开始乘公交的点则是 pos // jump。 代码 语言支持:Python3 Python3 Code: 123456789101112131415class Solution: def busRapidTransit(self, target: int, inc: int, dec: int, jumps: List[int], cost: List[int]) -> int: @lru_cache(None) def dfs(pos): if pos == 0: return 0 if pos == 1: return inc # 最坏的情况是全部走路,不乘公交,这种情况时间为 pos * inc ans = pos * inc for i, jump in enumerate(jumps): pre_pos, left = pos // jump, pos % jump if left == 0: ans = min(ans, cost[i] + dfs(pre_pos)) else: ans = min(ans, cost[i] + dfs(pre_pos) + inc * left, cost[i] + dfs(pre_pos + 1) + dec * (jump - left)) return ans return dfs(target) % 1000000007 复杂度分析 令 T 为 jump 数组的长度。 时间复杂度:$O(target * T)$ 空间复杂度:$O(target)$ 总结反向思考往往可以达到降维打击的效果。有时候可以使得求解思路更简单,代码更好写。有时候可以使得情况更少,复杂度降低。 回顾一下什么时候使用反向思考呢?一个很简单的原则就是: 正向思考的情况比较多 代码比较难写或者算法复杂度过高 我给大家举了三个例子来说明如何运用反向思考技巧。其中 第一题正向思考只能使用逐一枚举的方式,当然我们可以使用二分降低复杂度,但是复杂度仍然不及反向思考。 第二题反向思考情况大大减少,复杂度指数级降低,真的是降维打击了。 第三题利用无法超过 0 的位置这点,反向思考降低复杂度。 这些题还是冰山一角,实际做题过程中你会发现反向思考很常见,只是主流的算法划分没有对应的专题罢了 。我甚至还有想法将其加入91 天学算法中,就像后期加枚举章节一样,我认为反向思考也是一个基础的算法思考,请诸君务必掌握!","categories":[],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"逆向思维","slug":"逆向思维","permalink":"https://lucifer.ren/blog/tags/逆向思维/"}]},{"title":"技术面试原来不止考技术?","slug":"non-tech-skill","date":"2022-01-03T16:00:00.000Z","updated":"2023-01-07T12:34:34.366Z","comments":true,"path":"2022/01/04/non-tech-skill/","link":"","permalink":"https://lucifer.ren/blog/2022/01/04/non-tech-skill/","excerpt":"大家好呀,狗头又出现啦~ 我在 21 年的时候无相关背景转码上岸狗家,并且接到了一些中小厂的 offer。在我准备面试和面试的过程中,我总结了一些非技术相关的技巧和心得。这些技巧和心得在技术面试中往往能起到锦上添花的作用。这篇文章讨论的所有技巧都跟代码能力和工程能力无关~","text":"大家好呀,狗头又出现啦~ 我在 21 年的时候无相关背景转码上岸狗家,并且接到了一些中小厂的 offer。在我准备面试和面试的过程中,我总结了一些非技术相关的技巧和心得。这些技巧和心得在技术面试中往往能起到锦上添花的作用。这篇文章讨论的所有技巧都跟代码能力和工程能力无关~ 当然啦,在技术面试中,技术是第一位的。只是在面试同一个岗位的时候,如果几个面试者技术都足够好,更好的沟通能力和表达能力能让自己更加突出。我有一个转码培训班 (coding bootcamp) 的同学在毕业两个月之内就找到了工作,她就经常说自己『写代码很差,但是能让跟我一起工作的人很开心。』诚然,她写代码『很差』是自谦,但跟她的交流确实很开心。 那为什么『交流开心』在技术面试中也很重要呢?因为面试官的目的不是寻找一个做题机器,而是寻找一个好的同事和合作伙伴,好的技术、善于学习的能力和良好的品质都很重要。但在技术面试里如何用有限的时间给面试官留下一个好的印象呢? 我其实并不是很喜欢『面试官』这个称呼,因为『官』暗示了等级的差距;其实大家都应该是平等且合作的。 我总结了以下 6 点建议给大家。 自己的心态和状态要是积极和开心的,因为情绪是会传染的。 和我们在很难过的朋友身边很难开心,在听相声和脱口秀的厂子里很难流泪一样,面试中面试者的传达出的情绪也会影响面试官对面试者的评价。 第一,面试者如果在面试的过程中传达出正向的情绪,那么说明面试者大概率也喜欢这份工作。喜欢一份工作,就会有动力(be motivated),那么工作做的应该也不差。 第二,在面试的时候,如果面试者的心情是愉悦和开心的,那么面试官在回想面试表现的时候,大概率也会回想起开心的情绪。我个人感觉,有的时候面试官可能记不住面试者所有技术上犯的错误,但是却一定能记得面试官对面试者的『感觉』。(题外话:面试者往往能深刻的记住自己犯的每一个错误,啊哈哈哈哈…)一份正面 (positive) 的感觉往往能让面试官对面试者评价更高。那该如何准备呢?首先一定要多跟自己不熟悉的人模拟面试来消除面试的紧张感。其次,可以找一些让自己能瞬间开心起来的心理暗示,比如一些开心的视频或者音乐,或者对着镜子给自己加油打气。我很喜欢在面试之前听安全着陆的《个人简介》,并且告诉自己我就是酷炫吊炸天因为我真的好他妈酷 ୧(๑•̀◡•́๑)૭!所以我面试的时候一般在问好和交流思想环节都是微笑的,因为我真的很帅嘛! 在跟对方交流的时候,留意对方的反馈,并且在阐述思路的时候时不时询问对方自己是否表达清楚。 首先明确,作为面试者,面试中的沟通的服务对象是面试官。换句话说,在面试的时候,沟通的评判标准主观也简单 ── 面试官听懂了就是好的沟通,听不懂就是不好的沟通。在跟对方沟通的时候,如果对方表现出疑惑的表情,最好能及时询问对方『自己是否有哪里解释的不太清楚?』注意,这里的提问并不是『你哪里没听懂?』。为什么这两者在沟通上有差别呢?因为『自己是否解释的不清楚』暗示了如果不通畅是自己的问题,并且自己希望可以得到反馈并且改进;而『你哪里没听懂?』则更像是在暗示『听懂』是对方应该达到的期待。如果对方没有听懂,那么对方似乎就没有达到你的期待。让人失望总是一件不那么愉悦的事情。如果自己说了很长的一段话,也可以来问一下对方是否还有什么要问的。总体来说就是避免『讲课 (lecturing)』而更多是一来一回的『沟通 (communication)』。 如果遇到不太喜欢给反馈的面试官,这一条好像没什么用… 但是我没遇到过扑克脸的面试官。 在交流的时候,尽量把每段话的结尾变成积极、正向、令人感到愉悦的事。 『End on a high note.』这是我 bootcamp 职业指导 (behavior coach) 告诉我的一条面试交谈法则。就像主持人常常讲究『烘托气氛』和『暖场』一样,面试的交流也需要被『暖』。 在面试中什么气氛是『暖』的呢?其实就是在每段交谈(也就是说你来我往,而不是一个人一直说)中,双方慢慢的感觉到对彼此的认可和愉悦。 举个例子:面试官问到我不会的技能的时候,我会回答『我不会』 (负面的),『但是很乐意学习』 (正面的),如果有了解的话还可以补充说『听说{这个工具}可以让{这个工具的优点},所以我一直很感兴趣』(正面且具体的)。 当被质疑/指出错误的时候,要以合作且感谢的心态进行对话。为什么是合作呢?因为技术面试其实更像是同事和同事之间的『试工作』。在真正的工作当中,工程师会需要解释自己的代码/实现方式;作为新人,犯错也不可避免。如何好的接受反馈和建议是非常重要的,也很大程度上决定了一个人是否能成长。给一些真实的面试官问题作为例子: 非合作心态 面试官:你这个写法我看不太懂啊?它怎么就能解这道题呢? 面试者:???这不很明显吗 / (沉默) / 这你不懂吗? 合作心态 下面是在面试官说的不对的时候。是的,面试官说的也不全对哈哈~ 面试官:你这个循环没有检查第二个数组在这个引索情况下是否为空,会报错的 我:确实一般来说没有判空是会报错(首先肯定对方),但是对于 JS 来说,如果这个数组里不存在这个引索,那么其实是’undefined’,不会有(a === b)的情况,所以不会报错~(大概是有两个数组 A 和 B,A.length > B.length, 我以 A 的 index 来循环 A,并且检查 A[i] === B[i]) 如果面试官说的对的话,可以肯定对方,并且给与感谢,因为毕竟这是一次让自己成长的机会。 面试官如果提出一个问题,有可能是在试图引导面试者自己发现自己的错误,就是 giving hints;也有可能是面试官对面试者使用的技术栈或者语言不太熟悉。无论是哪种情况,面试者如何应对『提出的问题』都非常重要。即便提出的问题是错的,面试者应该以合作和积极的心态去应对,而不是有防御性的或者是消极的。如果对于某个细节无法达成一致,可以按照面试官的假设来解决问题。这个方法除了能在情绪上避免争执,遵循某个预设来解决问题也是有现实意义的。比如,有的公司的代码规范里不允许一些写算法题的常规操作。 在遇到自己卡壳的地方的时候,能准确的说出卡的点。 我在面狗家之前,问过狗家面试官『When the candidate is stuck, what can the candidate do to still give you a positive impression?』狗家面试官给的答案是『This candidate can clearly identity the block.』换句话说,当自己在面试中卡壳了,不要慌,毕竟真实工作中哪有人不需要查文档/不需要同事帮助呢?这时候比较推荐的做法是:『我这道题的思路是 abcd,但是我不知道怎么实现 c 这一步。』(注意,此处每一步应该尽可能的小、可实现。) 如果可以的话,尽可能给出多个解决问题的方案和他们的利弊 在我入职之后,我听很多经理都谈论过『能够给出多套方案并且权衡利弊』是他们衡量候选人重要的一个标志之一。在日常工作中,也经常有因为各种规定,而需要给出多套方案讨论的时候。例子:详细阐述思路的时候可以提到:『我现在有 2 个方案,方案一是 abcd,好处是…,坏处是…;方案二是 cdef,好处是…坏处是…』。 利用好提问环节,这里可以加塞给对方的称赞和肯定:D 一般技术面试的最后,面试官都会给一些可以提问的时间。如果不知道要问什么,推荐问跟对方公司、职业相关(但是尽量足够宽泛,不涉及到保密信息的),且答案非常有可能让对方联想起积极情绪的问题。什么是联想起积极情绪?比如『你最骄傲的项目是什么?』反面的例子比如『你在这份工作里最大的失误是什么?』,『你在这份工作中最绝望的时刻是什么时候?』,『你被老板骂哭过吗?为啥呢?』。 在这个时间里,其实面试者可以『夹带私货』。比如:『请问你们工作强度是怎么样的?』和『刚刚在面试的时候,我觉得你给我指出的几处错误特别厉害。我特别希望能跟你这样优秀的程序员在一起工作,所以我想问一下你们平时的工作强度是怎么样的?』 当然,这一部分如果夹带太多私货就会变得非常油腻,所以希望大家在给称赞的时候一定是真心的。 我最喜欢问的问题是『What can I do, for this position, to not only meet your expectation but also exceed it?』因为这个问题不仅暗示了我很想要争取这个职位,而且也试探了对方对应聘者的期待。我经常通过这个问题能收获到很多大佬们的指点,但是有一次差点『翻车』。在我问了这个问题之后,面试官跟我说『这个问题我看见过,是网上最推荐求职者问的问题之一吧。』(面试官在暗示我他识破了我的套路。)我回答『是的,但是这个问题是我从一长串问题中选出来的。我选它是因为这个问题能帮我进一步了解同事对一个新入职的程序员的期待,毕竟这是我的第一份程序员工作嘛。』(我没有否认/抵抗/狡辩,我承认了,并且真诚的给与了原因。)面试官满意的点了点头。 其实说了这么多,都是一些并非原创的、没有什么新意的技巧。在这些『技巧』的背后,最重要的是真诚。当一个面试者发自内心的喜欢这个岗位、喜欢这个公司,这份激动 (excitement) 和动机 (motivation) 往往是难以掩饰的,而这份热情肯定也会打动面试官的。 快速 Q & A Q: 怎么样寻找『积极情绪的』感觉? A: 多观察服务业的人员。在北美的朋友们可以看看地道的北美餐厅服务员、护士、房地产中介/manager。在国内的朋友们…要不点个特别会聊天的陪玩试试?(手动狗头保命,我是开玩笑的) Q: 什么是『正向情绪』? A:开心、高兴等让人感到愉悦和美好的情绪。 Q:什么是『负面情绪』? A:有防御性的、生气、不合作等。 Q:写代码的时候没办法看到面试官的表情怎么办? A:(答案可能只适用于北美的技术面试)我敲代码的时候也不看面试官的表情,因为确实很难分神。但是在技术面试的开头,就是开场和沟通的部分会尽量观察面试官的反馈。一般来说在讨论好大致思路之后真正写码的速度应该是很快的,所以真正写码的时间其实并不会特别多。 最后本人在这方面没有任何学术背景,如果说的不对,还请指正。本人不适用于所有人,仅仅是个人总结,希望能作为大家的参考。每个人实际情况不同,请选择适合自己的方式和方法。","categories":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"}],"tags":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"}],"author":{"name":"易潇","avatar":"https://tva1.sinaimg.cn/large/008i3skNly1gxzfkhprmpj30cs0csaar.jpg","url":"https://github.com/lilyzhaoyilu"}},{"title":"刷题插件可以隐藏测试用例啦","slug":"leetcode-cheat-hide-cases","date":"2021-12-21T16:00:00.000Z","updated":"2023-01-07T12:34:34.314Z","comments":true,"path":"2021/12/22/leetcode-cheat-hide-cases/","link":"","permalink":"https://lucifer.ren/blog/2021/12/22/leetcode-cheat-hide-cases/","excerpt":"一切都来源于一个 issue今日有粉丝提意想增加隐藏测试用例的功能。issue 地址:https://github.com/azl397985856/blog/issues/91 当天我就完成了这个功能的开发,并上架商店进行审核了。","text":"一切都来源于一个 issue今日有粉丝提意想增加隐藏测试用例的功能。issue 地址:https://github.com/azl397985856/blog/issues/91 当天我就完成了这个功能的开发,并上架商店进行审核了。 功能介绍简单介绍一个这个功能,有需要的同学可以更新一下最新的版本。 当我们提交力扣代码,并且无法通过时,力扣会直接显示出错误的测试用例以及期望的输出。 有些时候,我们不想立马看到错误的具体细节,而只想知道有错误就够了。这样可以起到很好的练习作用。试想你正在参加面试,面试官告诉你代码有问题,而不告诉你具体哪个 case 有问题,这是很正常的吧?因此锻炼自己的这种自己分析问题的能力也很有必要。 比如题目直接显示错误的用例输入是空字符,那你直接就知道自己忘记处理边界了。但是如果这个空字符串是自己想出来的,那么效果肯定比力扣告诉你要好。 于是我开发了这个提交错误后默认隐藏出错细节的功能。 只有你点击显示的时候才可以看到详细信息。避免你自己不想看,却被弹到脸上的尴尬。 值得注意的是这个功能目前默认开启,无法关闭。 如何使用大家首先要更新到 v0.10.0 及以上版本。其次找到力扣中的任意一道题,点击提交即可。如果提交出错即可看到西法给你的贴心提示啦! 插件的安装和使用方法请到我的公众号力扣加加回复插件获取 后话为了测试这个功能,我可以故意做错了很多题,满屏的红色好扎心。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"},{"name":"刷题","slug":"刷题","permalink":"https://lucifer.ren/blog/tags/刷题/"}]},{"title":"区间算法题用线段树可以秒解?","slug":"segment-tree","date":"2021-12-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.787Z","comments":true,"path":"2021/12/16/segment-tree/","link":"","permalink":"https://lucifer.ren/blog/2021/12/16/segment-tree/","excerpt":"背景给一个两个数组,其中一个数组是 A [1,2,3,4],另外一个数组是 B [5,6,7,8]。让你求两个数组合并后的大数组的: 最大值 最小值 总和 这题是不是很简单?我们直接可以很轻松地在 $O(m+n)$ 的时间解决,其中 m 和 n 分别为数组 A 和 B 的大小。 那如果我可以修改 A 和 B 的某些值,并且我要求很多次最大值,最小值和总和呢? 朴素的思路是原地修改数组,然后 $O(m+n)$ 的时间重新计算。显然这并没有利用之前计算好的结果,效率是不高的。 那有没有效率更高的做法? 有!线段树就可以解决。 ​","text":"背景给一个两个数组,其中一个数组是 A [1,2,3,4],另外一个数组是 B [5,6,7,8]。让你求两个数组合并后的大数组的: 最大值 最小值 总和 这题是不是很简单?我们直接可以很轻松地在 $O(m+n)$ 的时间解决,其中 m 和 n 分别为数组 A 和 B 的大小。 那如果我可以修改 A 和 B 的某些值,并且我要求很多次最大值,最小值和总和呢? 朴素的思路是原地修改数组,然后 $O(m+n)$ 的时间重新计算。显然这并没有利用之前计算好的结果,效率是不高的。 那有没有效率更高的做法? 有!线段树就可以解决。 ​ 线段树是什么线段树本质上就是一棵树。更准确地说,它是一颗二叉树,而且它是一颗平衡二叉树。关于为什么是平衡二叉树,我们后面会讲,这里大家先有这样一个认识。 虽然是一棵二叉树,但是线段树我们通常使用数组来模拟树结构,而不是传统的定义 TreeNode 。 一方面是因为实现起来容易,另外一方面是因为线段树其实是一颗完全二叉树,因此使用数组直接模拟会很高效。这里的原因我已经在之前写的堆专题中的二叉堆实现的时候中讲过了,大家可以在我的公众号《力扣加加》回复堆获取。 线段树解决什么问题正如它的名字,线段树和线段(区间)有关。线段树的每一个树节点其实都存储了一个区间(段)的信息。然后这些区间的信息如果满足一定的性质就可以用线段树来提高性能。 那: 究竟是什么样的性质? 如何提高的性能呢? 究竟是什么样的性质?比如前面我们提到的最大值,最小值以及求和就满足这个一定性质。即我可以根据若干个(这里是两个)子集推导出子集的并集的某一指标。 以上面的例子来说,我们可以将数组 A 和 数组 B 看成两个集合。那么:集合 A 的最大值和集合 B 的最大值已知,我们可以直接通过 max(Amax, Bmax) 求得集合 A 与集合 B 的并集的最大值。其中 Amax 和 Bmax 分别为集合 A 和集合 B 的最大值。最小值和总和也是一样的,不再赘述。因此如果统计信息满足这种性质,我们就可以可以使用线段树。但是要不要使用,还是要看用了线段树后,是否能提高性能。 如何提高的性能呢?关于提高性能,我先卖一个关子,等后面讲完实现的时候,我们再聊。 线段树实现以文章开头的求和为例。 我们可以将区间 A 和 区间 B 分别作为一个树的左右节点,并将 A 的区间和与 B 的区间和分别存储到左右子节点中。 接下来,将 A 的区间分为左右两部分,同理 B 也分为左右两部分。不断执行此过程直到无法继续分。 总结一下就是将区间不断一分为二,并将区间信息分别存储到左右节点。如果是求和,那么区间信息就是区间的和。这个时候的线段树大概是这样的: 蓝色字体表示的区间和。 注意,这棵树的所有叶子节点一共有 n 个(n 为原数组长度),并且每一个都对应到原数组某一个值。 体现到代码上也很容易。 直接使用后续遍历即可解决。这是因为,我们需要知道左右节点的统计信息,才能计算出当前节点的统计信息。 不熟悉后序遍历的可以看下我之前的树专题,公众号力扣加加回复树即可获取 和二叉堆的表示方式一样,我们可以用数组表示树,用 2 * i + 1 和 2 * i + 2 来表示左右节点的索引,其中 i 为当前节点对应在 tree 上的索引。 tree 是用来构建线段树的数组,和二叉堆类似。只不过 tree[i] 目前存的是区间信息罢了。 上面我描述建树的时候有明显的递归性,因此我们可以递归的建树。具体来说,可以定义一个 build(tree_index, l, r) 方法 来建树。其中 l 和 r 就是对应区间的左右端点,这样 l 和 r 就可以唯一确定一个区间。 tree_index 其实是用来标记当前的区间信息应该被更新到 tree 数组的哪个位置。 我们在 tree 上存储区间信息,那么最终就可以用 tree[tree_index] = …. 来更新区间信息啦。 核心代码: 12345678910111213141516def build(self, tree_index:int, l:int, r:int): ''' 递归创建线段树 tree_index : 线段树节点在数组中位置 l, r : 该节点表示的区间的左,右边界 ''' if l == r: self.tree[tree_index] = self.data[l] return mid = (l+r) // 2 # 区间中点,对应左孩子区间结束,右孩子区间开头 left, right = 2 * tree_index + 1, 2 * tree_index + 2 # tree_index 的左右子树索引 self.build(left, l, mid) self.build(right, mid+1, r) # 典型的后序遍历 # 区间和使用加法即可,如果不是区间和要改下面这行代码 self.tree[tree_index] = self.tree[left] + self.tree[right] 上面代码的数组 self.tree[i] 其实就是用来存类似上图中蓝色字体的区间和。每一个区间都在 tree 上存有它一个位置,存它的区间和。 复杂度分析 时间复杂度:由递推关系式 T(n) = 2*T(n/2) + 1,因此时间复杂度为 $O(n)$ 不知道怎么得出的 $O(n)$? 可以看下我的《算法通关之路》的第一章内容。 https://leetcode-solution.cn/book-intro 空间复杂度:tree 的大小和 n 同阶,因此空间复杂度为 $O(n)$ 终于把树建好了,但是知道现在一点都没有高效起来。我们要做的是高效处理频繁更新情况下的区间查询。 那基于这种线段树的方法,如果更新和查询区间信息如何做呢? 区间查询先回答简单的问题区间查询原理是什么。 如果查询一个区间的信息。这里也是使用后序遍历就 ok 了。比如我要找一个区间 [l,r] 的区间和。 那么如果当前左节点: 完整地落在 [l,r] 内。比如 [2,3] 完整地落在 [1,4] 内。 我们直接将 tree 中左节点对于的区间和取出来备用,不妨极为 lsum。 部分落在 [l,r] 内。比如 [1,3] 部分落在 [2,4]。这个时候我们继续递归,直到完整地落在区间内(上面的那种情况),这个时候我们直接将 tree 中左节点对于的区间和取出来备用 将前面所有取出来备用的值加起来就是答案 右节点的处理也是一样的,不再赘述。 复杂度分析 时间复杂度:查询不需要在每个时刻都处理两个叶子节点,实际上处理的次数大致和树的高度一致。而树是平衡的,因此复杂度为 $O(logn)$ 或者由递推关系式 T(n) = T(n/2) + 1,因此时间复杂度为 $O(logn)$ 不知道怎么得出的 $O(logn)$? 可以看下我的《算法通关之路》的第一章内容。 https://leetcode-solution.cn/book-intro 大家可以结合后面的代码理解这个复杂度。 区间修改那么如果我修改了 A[1] 为 1 呢? 如果不修改 tree,那么显然查询的区间只要包含了 A[1] 就一定是错的,比如查询区间 [1,3] 的和 就会得到错误的答案。因此我们要在修改了 A[1] 的时候同时去修改 tree。 问题在于我们要修改哪些 tree 的值,修改为多少呢? 首先回答第一个问题,修改哪些 tree 的值呢? 我们知道,线段树的叶子节点都是原数组上的值,也是说,线段树的 n 个叶子节点对应的就是原数组。因此我们首先要找到我们修改的位置对应的那个叶子节点,将其值修改掉。 这就完了么? 没有完。实际上,我们修改的叶子节点的所有父节点以及祖父节点(如果有的话)都需要改。也就是说我们需要从这个叶子节点不断冒泡到根节点,并修改沿途的区间信息 这个过程和浏览器的事件模型是类似的 接下来回答最后一个问题,具体修改为多少? 对于求和,我们需要首先将叶子节点改为修改后的值,另外所有叶子节点到根节点路径上的点的区间和都加上 delta,其中 delta 就是改变前后的差值。 求最大最小值如何更新?大家自己思考一下。 修改哪些节点,修改为多少的问题都解决了,那么代码实现就容易了。 复杂度分析 时间复杂度:修改不需要在每个时刻都处理两个叶子节点,实际上处理的次数大致和树的高度一致。而树是平衡的,因此复杂度为 $O(logn)$ 或者由递推关系式 T(n) = T(n/2) + 1,因此时间复杂度为 $O(logn)$ 不知道怎么得出的 $O(logn)$? 可以看下我的《算法通关之路》的第一章内容。 https://leetcode-solution.cn/book-intro 大家可以结合后面的代码理解这个复杂度。 线段树模板线段树代码已经放在刷题插件上了,公众号《力扣加加》回复插件即可获取。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485class SegmentTree: def __init__(self, data:List[int]): ''' data: 传入的数组 ''' self.data = data self.n = len(data) # 申请 4 倍 data 长度的空间来存线段树节点 self.tree = [None] * (4 * self.n) # 索引 i 的左孩子索引为 2i+1,右孩子为 2i+2 if self.n: self.build(0, 0, self.n-1) # 本质就是一个自底向上的更新过程 # 因此可以使用后序遍历,即在函数返回的时候更新父节点。 # index 是原数组索引 # tree_index 是对应我们构造的二叉树数组的索引 def update(self, tree_index, l, r, index): ''' tree_index: 某个根节点索引 l, r : 此根节点代表区间的左右边界 index : 更新的值的索引 ''' if l == r==index : self.tree[tree_index] = self.data[index] return mid = (l+r)//2 left, right = 2 * tree_index + 1, 2 * tree_index + 2 if index > mid: # 要更新的区间在右子树 self.update(right, mid+1, r, index) else: # 要更新的区间在左子树 index<=mid self.update(left, l, mid, index) # 查询区间一部分在左子树一部分在右子树 # 区间和使用加法即可,如果不是区间和要改下面这行代码 self.tree[tree_index] = self.tree[left] + self.tree[right] def updateSum(self,index:int,value:int): self.data[index] = value self.update(0, 0, self.n-1, index) def query(self, tree_index:int, l:int, r:int, ql:int, qr:int) -> int: ''' 递归查询区间 [ql,..,qr] 的值 tree_index : 某个根节点的索引 l, r : 该节点表示的区间的左右边界 ql, qr: 待查询区间的左右边界 ''' if l == ql and r == qr: return self.tree[tree_index] # 区间中点,对应左孩子区间结束,右孩子区间开头 mid = (l+r) // 2 left, right = tree_index * 2 + 1, tree_index * 2 + 2 if qr <= mid: # 查询区间全在左子树 return self.query(left, l, mid, ql, qr) elif ql > mid: # 查询区间全在右子树 return self.query(right, mid+1, r, ql, qr) # 查询区间一部分在左子树一部分在右子树 # 区间和使用加法即可,如果不是区间和要改下面这行代码 return self.query(left, l, mid, ql, mid) + self.query(right, mid+1, r, mid+1, qr) def querySum(self, ql:int, qr:int) -> int: ''' 返回区间 [ql,..,qr] 的和 ''' return self.query(0, 0, self.n-1, ql, qr) def build(self, tree_index:int, l:int, r:int): ''' 递归创建线段树 tree_index : 线段树节点在数组中位置 l, r : 该节点表示的区间的左,右边界 ''' if l == r: self.tree[tree_index] = self.data[l] return mid = (l+r) // 2 # 区间中点,对应左孩子区间结束,右孩子区间开头 left, right = 2 * tree_index + 1, 2 * tree_index + 2 # tree_index 的左右子树索引 self.build(left, l, mid) self.build(right, mid+1, r) # 区间和使用加法即可,如果不是区间和要改下面这行代码 self.tree[tree_index] = self.tree[left] + self.tree[right] 使用的方式很简单: 初始化 SegmentTree 并直接传入一个你想计算指标的数组即可。 如何更新原数组的某一项? 只要调用 updateSum(index, value) 即可,其中 index 和 value 为你想修改的原数组的索引和值。 如何查询原数组的区间和?只要调用 querySum(ql, qr) 即可,其中 ql 和 qr 为你想查询的区间的左右端点。 相关专题 堆 大家可以看下我之前写的堆的专题的二叉堆实现。然后对比学习,顺便还学了堆,岂不美哉? 树状数组 树状数组和线段树类似,难度比线段树稍微高一点点。有机会给大家写一篇树状数组的文章。 immutablejs 前端的小伙伴应该知道 immutable 吧? 而 immutablejs 就是非常有名的实现 immutable 的工具库。西法之前写过一篇 immutable 原理解析文章,感兴趣的可以看下 https://lucifer.ren/blog/2020/06/13/immutable-js/ 回答前面的问题为啥是平衡二叉树?前面的时间复杂度其实也是基于树是平衡二叉树这一前提。那么线段树一定是平衡二叉树么?是的。这是因为线段树是完全二叉树,而完全二叉树是平衡的。 当然还有另外一个前提,那就是树的总的节点数和原数组长度同阶,也就是 n 的量级。关于为啥是同阶的,也容易计算,只需要根据递归公式即可得出。 为啥线段树能提高性能?只要你理解了我实现部分的时间复杂度,那么就不难明白这个问题。因为修改和查询的时间复杂度都是 $logn$,而不使用线段树的暴力做法查询的复杂度高达 $O(n)$。相应的代价就是建树的 $O(n)$ 的空间,因此线段树也是一种典型的空间换时间算法。 最后点一下题。区间算法题是否可以用线段树秒解?这其实文章中已经回答过了,其取决于是否满足两点: 是否可以根据若干个(这里是两个)子集推导出子集的并集的某一指标。 是否能提高性能(相比于朴素的暴力解法)。通常面临频繁查询或者修改的场景都可以考虑使用线段树优化修改后的查询时间消耗。","categories":[{"name":"线段树","slug":"线段树","permalink":"https://lucifer.ren/blog/categories/线段树/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"线段树","slug":"线段树","permalink":"https://lucifer.ren/blog/tags/线段树/"}]},{"title":"腾讯校招还考抛物线?","slug":"tencent-2021","date":"2021-12-12T16:00:00.000Z","updated":"2023-01-07T12:34:34.411Z","comments":true,"path":"2021/12/13/tencent-2021/","link":"","permalink":"https://lucifer.ren/blog/2021/12/13/tencent-2021/","excerpt":"昨天翻了一下牛客网的腾讯 2021 校园招聘技术类编程题汇总,一共有五道题目。难度大不大?我们来看下! 具体代码我就不贴了,大家如果看了我的思路还是写不出来可以来我的校招群来问,大家可以在公众号后台回复校招进群。 题目地址:https://www.nowcoder.com/test/30545524/summary","text":"昨天翻了一下牛客网的腾讯 2021 校园招聘技术类编程题汇总,一共有五道题目。难度大不大?我们来看下! 具体代码我就不贴了,大家如果看了我的思路还是写不出来可以来我的校招群来问,大家可以在公众号后台回复校招进群。 题目地址:https://www.nowcoder.com/test/30545524/summary 第一题题目描述现在有 $10^5$ 个用户,编号为 1-$10^5$,现在已知有 m 对关系,每一对关系给你两个数 x 和 y ,代表编号为 x 的用户和编号为 y 的用户是在一个圈子中,例如: A 和 B 在一个圈子中, B 和 C 在一个圈子中,那么 A , B , C 就在一个圈子中。现在想知道最多的一个圈子内有多少个用户。 思路我们可以将用户抽象为点,用户的关系抽象为边。那么问题转化为最大联通子图。我们可以使用并查集来解决,直接套用下我的并查集模板即可解决。模板可以在我的刷题插件中找到,插件说明:https://github.com/azl397985856/leetcode-cheat 第二题题目描述输入一个字符串 s,s 由小写英文字母组成,保证 s 长度小于等于 5000 并且大于等于 1。在 s 的所有不同的子串中,输出字典序第 k 小的字符串。字符串中任意个连续的字符组成的子序列称为该字符串的子串。字母序表示英文单词在字典中的先后顺序,即先比较第一个字母,若第一个字母相同,则比较第二个字母的字典序,依次类推,则可比较出该字符串的字典序大小。 思路我们可以枚举所有的子串,这需要 $n^2$的时间复杂度。 接下来,我们排序取 第 k 个,或者使用堆来取第 k 个都是可以的。 有一点需要注意: 由于子串可能重复,因此我们需要去重。以题目中的 aabb 来说, a 这个子串其实有两个,但是算答案的时候仅仅算一个。 第三题题目描述 思路。。。 非常郁闷。为什么会考这种题? 这道题可以用微积分的知识来解。具体来说,我们可以根据 A,B,C 的值求出交点(两个交点),然后对 y 做积分,求出定积分的值即为所求的面积。 91 天学算法群里的数学大佬 @空识 给了一个计算方法,大家可以参考一下。 第四题题目描述数据结构基础之一——队列队列有五种基本操作,插入队尾、取出队首、删除队首、队列大小、清空队列。 现在让你模拟一个队列的操作,具体格式参考输入。 注意本题有多组输入。 思路由于时间复杂度需要 $O(1)$,因此无法使用数组来模拟,我们需要使用链表来模拟。代码可以参考一下双端队列的源码。 如果大家实现有困难,可以先尝试使用数组来模拟,然后改为链表。由于链表和数组其实都是线性数据结构,只是具体 api 不一样,因此改起来只要掌握方法很容易。 第五题题目描述界面中存在 id=jsContainer 的节点 A,系统会随机生成 id 为 jsLayout 的 m 行 x n 列 表格(m >= 3, n >= 3),并随机选中一个 td 节点,请按照如下需求实现 bind 函数1、bind 函数为每个 td 节点绑定 click 事件,当某个 td 节点被点击时 class 变为 current,同时以该 td 为中心的同一行和同一列 td 节点 class 变为 wrap,具体效果参考以下图片2、每次 click 后,请清空所有不需要变动的 td 节点的 class3、请不要手动调用 bind 函数4、当前界面为系统生成 $9 * 9$ 表格,执行 bind 函数,并点击其中 td 后的效果5、请不要手动修改 html 和 css6、不要使用第三方插件 题目预设代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105<div id=\"jsContainer\"> <table class=\"game\"> <tbody> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"current\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> </tbody> </table></div> 123456789101112131415161718table.game { font-size: 14px; border-collapse: collapse; width: 100%; table-layout: fixed;}table.game td { border: 1px solid #e1e1e1; padding: 0; height: 30px; text-align: center;}table.game td.current { background: #1890ff;}table.game td.wrap { background: #f5f5f5;} 1function bind() {} 思路这是一个前端题目。 由于只可以修改 js,因此我们可以: 使用事件代理在最外层绑定 click 事件 click 处理函数判断 event target 是第几行,第几列,从而给格子添加不同 class。比如 event target 是第 x 行,第 y 列,那么只需要给 x 行 y 列设置 current,给 x 行 其他 以及 y 列 其他 设置 wrap,最后清空其他单元格 class 即可。 总结五道题难度除了积分求面试这道题有点不太常规之外,其他都是很常见的题目,难度也不大。 第一题是常规的并查集的题目,基本属于最简单的并查集了。 第二题纯暴力其实也可以解决。 第三题,emmmm 第四题,实现一个队列。很常规的一个题目了,唯一需要注意的是使用链表来模拟。 第五题,是一个前端题目。考察事件代理,以及基本的 dom 操作。","categories":[{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"}],"tags":[{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第六期)","slug":"91algo-6","date":"2021-12-02T16:00:00.000Z","updated":"2023-02-07T04:15:30.326Z","comments":true,"path":"2021/12/03/91algo-6/","link":"","permalink":"https://lucifer.ren/blog/2021/12/03/91algo-6/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第六期和往期的不同。 基础篇增加排序专题,该专题为自习专题。进阶篇增加线段树专题,该专题目前暂定为自习,后面根据当前面试中考察频率决定是否改为非自习。 自习指的是不给每日一题的做题时间,需要自己找时间学习和练习。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-12-12 至 2022-03-12 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第六期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 本期有共五位讲师,每个专题由一位具体的讲师负责,大家有不会的问题可以进行提问。如果讲师来不及回答,大家可以在仓库中提 issue。 关于每一个专题的负责讲义,我们会在 91 官网中的“讲师”模块给出,如上图所示。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡一张哦 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第六期和前五内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"今年秋招大家都在讨论的话题,薪资只排第3","slug":"qiuzhao2021","date":"2021-11-27T16:00:00.000Z","updated":"2023-01-07T12:34:34.407Z","comments":true,"path":"2021/11/28/qiuzhao2021/","link":"","permalink":"https://lucifer.ren/blog/2021/11/28/qiuzhao2021/","excerpt":"有人发布了知乎统计数据,时间范围是2021年6月到10月,话题是秋招。下面我们来看下这些数据。","text":"有人发布了知乎统计数据,时间范围是2021年6月到10月,话题是秋招。下面我们来看下这些数据。 秋招概况今天看了一眼知乎的关键词统计报告,被提及最多的关键词中薪资排第三。那么前两位是什么? 其实第二个和薪资也是紧密相关的。第二就是前段时间传的大家热议的腾讯校招 40w 白菜年薪事件。这个我当时也蛮震惊的,心想不知道又有多少老员工被倒挂了。别说老员工,就算是前段时间刚刚接了 offer 的朋友都给我反馈说感觉自己被倒挂。 其实不仅仅是腾讯这样的大型互联网公司,很多公司(比如一些传统银行,物流行业公司)年薪普遍变得很高,反正比我当时高多了。只不过互联网公司显得尤为严重。 另外腾讯还公布了 养老制度,平均月薪8w+ 等新闻,可谓风靡一时。另外腾讯在所有公司中被提及次数最多,排名前三的分别是腾讯,字节和阿里。 据我说知,快手给的薪资也是异常的高,高到无法让人拒绝😄。另外还有一些没有那么 算法面试如何?很奇怪关键字竟然没有如何准备面试? 后来看到浏览量 top10 问题终于看到了,原来在这里。 排名第五的问题就是互联网公司常见算法面试题,我顺便搜了一下这个知乎的问题。发现是一个很老的问题了,每年春招秋招对会被拉出来“鞭尸”。排名靠前的还是那几个熟悉的面孔。 说实话,西法一点都不建议大家看这些答案。你想啊,靠前的大多数都是老答案,而这些题目有些都已经过时了,大家都不太考了。而如果你按照时间排序,有参考价值的回答有几个?所以我不推荐大家去刷这些知乎回答。 记得之前我搞的《91天学算法》活动里面也有高频面试题板块,后来在第五期被我取消了。我觉得这个题目的意义不大,有着时间倒不如刷刷你心仪公司的面经中的题目。当然很多公司有保密制度,不允许公开这些题目。因此去一些相对不那么公开的场合就显得很重要了。比如私聊,交流群或者一些知识星球之类的地方。顺便一题,我的群里就是不是有人拿一些网上的面经问答案,大家交流交流效果甚好。当然我强烈建议大家先讲讲自己的思路,以及做过什么样的尝试。不要做伸手党! 关于面经,我更推荐大家去牛客,一亩三分地这样的社区看,然后和其他同学交流题 解,这样的效果往往更好。很多拿了 sp 甚至 ssp 的同学都是这样的套路。 最后如果大家在准备下一界秋招,也可以加入我的交流群或者直接报名参与《91天学算法》。 交流群在我的公众号力扣加加回复 leetcode 加群,《91天学算法》回复 91 即可。 《91天学算法》十二月中旬结束,届时可能会开启第六期,感兴趣的可以蹲一波,说不定还会有买《算法通关之路》免费参与的活动哦~,图书试读以及介绍:https://leetcode-solution.cn/book-intro","categories":[],"tags":[{"name":"秋招","slug":"秋招","permalink":"https://lucifer.ren/blog/tags/秋招/"}]},{"title":"或许是一本可以彻底改变你刷 LeetCode 效率的题解书","slug":"leetcode-book.intro","date":"2021-11-19T16:00:00.000Z","updated":"2023-02-07T04:16:09.194Z","comments":true,"path":"2021/11/20/leetcode-book.intro/","link":"","permalink":"https://lucifer.ren/blog/2021/11/20/leetcode-book.intro/","excerpt":"经过了半年时间打磨,投入诸多人力,这本 LeetCode 题解书终于要和大家见面了。💐💐💐💐💐。 实体版购书链接:https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAN4JK1olXwUFU1xcAUoRA18IGFMXXgQDUG4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYBXFxeCkoTHDZNRwYlQ1J3BB0EWEl0QhkIH1xMBXBlDyQ1TkcbM244G1oUXQ4HU1tbDXsnA2g4STXN67Da8e9B3OGY1uefK1olXQEEUFhYCkgSAWwOHmsSXQ8yDwszD0sSUDtbGAlCDVJVAW5tOEgnBG8BD11nHFQWUixtOEsnAF9KdV5AWQcDB1cPDktEAWpfSwhFXwUDUllVDkMVATxbHVwWbQQDVVpUOHs 电子版购书链接:https://union-click.jd.com/jdc?e=&p=JF8BAL0JK1olXDYAVVhfD04UAl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFkkWBW0PHlgUQl9HCANtcS0SdTFvWVt1X3BkVV4Kc0JxYRtPe1cZbQcyVF9cCEMSBGoOHmslXQEyHzBcOEonA2gKE1oVWwEKXV5cAXsQA2Y4QA57WgYHBwoOCxlAUztfTmslbQUyZG5dOEgnQQFaSQ5FWQYFB1cODhgSVDpaS1hFDwQLUlwJAU5DAWcJHWsXXAcGXW4 在线试读","text":"经过了半年时间打磨,投入诸多人力,这本 LeetCode 题解书终于要和大家见面了。💐💐💐💐💐。 实体版购书链接:https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAN4JK1olXwUFU1xcAUoRA18IGFMXXgQDUG4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYBXFxeCkoTHDZNRwYlQ1J3BB0EWEl0QhkIH1xMBXBlDyQ1TkcbM244G1oUXQ4HU1tbDXsnA2g4STXN67Da8e9B3OGY1uefK1olXQEEUFhYCkgSAWwOHmsSXQ8yDwszD0sSUDtbGAlCDVJVAW5tOEgnBG8BD11nHFQWUixtOEsnAF9KdV5AWQcDB1cPDktEAWpfSwhFXwUDUllVDkMVATxbHVwWbQQDVVpUOHs 电子版购书链接:https://union-click.jd.com/jdc?e=&p=JF8BAL0JK1olXDYAVVhfD04UAl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFkkWBW0PHlgUQl9HCANtcS0SdTFvWVt1X3BkVV4Kc0JxYRtPe1cZbQcyVF9cCEMSBGoOHmslXQEyHzBcOEonA2gKE1oVWwEKXV5cAXsQA2Y4QA57WgYHBwoOCxlAUztfTmslbQUyZG5dOEgnQQFaSQ5FWQYFB1cODhgSVDpaS1hFDwQLUlwJAU5DAWcJHWsXXAcGXW4 在线试读 背景自 LeetCode 题解 (现在已经接近 45k star 了)项目被大家开始关注,就有不少出版社开始联系我写书。刚开始后的时候,我并没有这个打算,觉得写这个相对于博客形式的题解要耗费时间,且并不一定效果比博客形式的效果好。后来当我向大家提及“出版社找我写书”这件事情的时候,很多人表示“想要买书,于是我就开始打算写这样一本书。但是一个完全没有写书经验的人,独立完成一本书工作量还是蛮大的,因此我打算寻求其他志同道合人士的帮助。 团队介绍团队成员大都来自 985, 211 学校计算机系,大家经常参加算法竞赛,也坚持参加 LeetCode 周赛。在这个过程中,我们积累了很多经验,希望将这些经验分享给大家,以减少大家在刷题过程中的阻碍,让大家更有效率的刷题。 本书尤其适合那些刚刚开始刷题的人,如果你刚开始刷题,或者刷了很多题面对新题还是无法很好的解决,那么这本书肯定很适合你。最后欢迎大家加入我们的读者群和作者进行交流。 作者 - xing 作者 - lucifer 作者 - BY 作者 - fanlu 作者 - lazybing 样张这里给大家开放部分章节内容给大家,让大家尝尝鲜。当然也欢迎大家提出宝贵的建议,帮助我们写出更好的内容。 我们开放了第八章第五小节给大家看,以下是具体内容: 8.5 1206. 设计跳表题目描述不使用任何库函数,设计一个跳表。 跳表是在 $O(logN)$ 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。 跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 $O(N)$。跳表的每一个操作的平均时间复杂度是 $O(logN)$,空间复杂度是 $O(N)$。 在本题中,你的设计应该要包含这些函数: bool search(int target) : 返回 target 是否存在于跳表中。 void add(int num): 插入一个元素到跳表。 bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回 false. 如果存在多个 num ,删除其中任意一个即可。 注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。 样例: 1234567891011Skiplist skiplist = new Skiplist();skiplist.add(1);skiplist.add(2);skiplist.add(3);skiplist.search(0); // 返回 falseskiplist.add(4);skiplist.search(1); // 返回 trueskiplist.erase(0); // 返回 false,0 不在跳表中skiplist.erase(1); // 返回 trueskiplist.search(1); // 返回 false,1 已被擦除 约束条件:0 <= num, target <= 20000最多调用 50000 次 search, add, 以及 erase 操作。 思路首先,使用跳表会将数据存储成有序的。在数据结构当中,我们通常有两种基本的线性结构,结合有序数据,表达如下: 有序链表,我们有三种基本操作: 查找指定的数据:时间复杂度为 $O(N)$, $N$ 为数据位于链表的位置。 插入指定的数据:时间复杂度为 $O(N)$, $N$ 为数据位于链表的位置。因为插入数据之前,需要先查找到可以插入的位置。 删除指定的数据:时间复杂度为 $O(N)$, $N$ 为数据位于链表的位置。因为删除数据之前,需要先查找到可以插入的位置。 有序数组: 查找指定的数据:如果使用二分查找,时间复杂度为 $O(logN)$, $N$ 为数据的个数。 插入指定的数据:时间复杂度为 $O(N)$, 因为数组是顺序存储,插入新的数据时,我们需要向后移动指定位置后面的数据,这里 $N$ 为数据的个数。 删除指定的数据:时间复杂度为 $O(N)$, 因为数组是顺序存储,删除数据时,我们需要向前移动指定位置后面的数据,这里 $N$ 为数据的个数。 而神奇的跳表能够在 $O(logN)$ 时间内完成增加、删除、搜索操作。下面我们分别分析增加、删除和搜索这 3 个三个基本操作。 跳表的查找现在我们通过一个简单的例子来描述跳表是如何实现的。假设我们有一个有序链表如下图:原始方法中,查找的时间复杂度为 $O(N)$。那么如何来提高链表的查询效率呢?如下图所示,我们可以从原始链表中每两个元素抽出来一个元素,加上一级索引,并且一级索引指向原始链表:如果我们想要查找 9 ,在原始链表中查找路径是 1->3->4->7->9, 而在添加了一级索引的查找路径是 1->4->9,很明显,查找效率提升了。按照这样的思路,我们在第 1 级索引上再加第 2 级索引,再加第 3 级索引,以此类推,这样在数据量非常大的时候,使得我们查找数据的时间复杂度为 $O(logN)$。这就是跳表的思想,也就是我们通常所说的“空间换时间”。 跳表的插入跳表插入数据看起来很简单,我们需要保持数据有序,因此,第一步我们需要像查找元素一样,找到新元素应该插入的位置,然后再插入。 但是这样会存在一个问题,如果我们一直往原始链表中插入数据,但是不更新索引,那么会导致两个索引结点之间的数据非常多,在极端情况下,跳表会退化成单链表,从而导致查找效率由 $O(logN)$ 退化为 $O(N)$。因此,我们需要在插入数据的同时,增加相应的索引或者重建索引。 方案 1:每次插入数据后,将跳表的索引全部删除后重建,我们知道索引的结点个数为 $N$(在空间复杂度分析时会有明确的数学推导),那么每次重建索引,重建的时间复杂度至少是 $O(N)$ 级别,很明显不可取。 方案 2:通过随机性来维护索引。假设跳表的每一层的提升概率为 $\\frac{1}{2}$ ,最理想的情况就是每两个元素提升一个元素做索引。而通常意义上,只要元素的数量足够多,且抽取足够随机的话,我们得到的索引将会是比较均匀的。尽管不是每两个抽取一个,但是对于查找效率来讲,影响并不很大。我们要知道,设计良好的数据结构往往都是用来应对大数据量的场景的。因此,我们这样维护索引:随机抽取 $\\frac{N}{2}$ 个元素作为 1 级索引,随机抽取 $\\frac{N}{4}$ 作为 2 级索引,以此类推,一直到最顶层索引。 那么具体代码该如何实现,才能够让跳表在每次插入新元素时,尽量让该元素有 $\\frac{1}{2}$ 的概率建立一级索引、$\\frac{1}{4}$ 的概率建立二级索引、$\\frac{1}{8}$ 的概率建立三级索引,以此类推。因此,我们需要一个概率算法。 在通常的跳表实现当中,我们会设计一个 randomLevel() 方法,该方法会随机生成 1~MAX_LEVEL 之间的数 (MAX_LEVEL 表示索引的最高层数) randomLevel() 方法返回 1 表示当前插入的元素不需要建立索引,只需要存储数据到原始链表即可(概率 1/2) randomLevel() 方法返回 2 表示当前插入的元素需要建立一级索引(概率 1/4) randomLevel() 方法返回 3 表示当前插入的元素需要建立二级索引(概率 1/8) randomLevel() 方法返回 4 表示当前插入的元素需要建立三级索引(概率 1/16) …… 可能有的同学会有疑问,我们需要一级索引中元素的个数时原始链表的一半,但是我们 randomLevel() 方法返回 2(建立一级索引)的概率是 $\\frac{1}{4}$, 这样是不是有问题呢?实际上,只要randomLevel()方法返回的数大于 1,我们都会建立一级索引,而返回值为 1 的概率是 $\\frac{1}{2}$。所以,建立一级索引的概率其实是$1- \\frac{1}{2} = \\frac{1}{2}$。同上,当 randomLevel() 方法返回值 >2 时,我们会建立二级或二级以上的索引,都会在二级索引中添加元素。而在二级索引中添加元素的概率是 $1- \\frac{1}{2} - \\frac{1}{4} = \\frac{1}{4}$。以此类推,我们推导出 randomLevel() 符合我们的设计要求。 下面我们通过仿照 redis zset.c 的 randomLevel 的代码: 12345678### 1. SKIPLIST_P 为提升的概率,本案例中我们设置为 1/2, 如果我们想要节省空间利用效率,可以适当的降低该值,从而减少索引元素个数。在 redis 中 SKIPLIST_P 被设定为 0.25。# 2. redis 中通过使用位运算来提升浮点数比较的效率,在本案例中被简化def randomLevel(): level = 1 while random() < SKIPLIST_P and level < MAX_LEVEL: level += 1 return level 跳表的删除跳表的删除相对来讲稍微简单一些。我们在删除数据的同时,需要删除对应的索引结点。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071from typing import Optionalimport randomclass ListNode: def __init__(self, data: Optional[int] = None): self._data = data # 链表结点的数据域,可以为空(目的是方便创建头节点) self._forwards = [] # 存储各个索引层级中该结点的后驱索引结点class Skiplist: _MAX_LEVEL = 16 # 允许的最大索引高度,该值根据实际需求设置 def __init__(self): self._level_count = 1 # 初始化当前层级为 1 self._head = ListNode() self._head._forwards = [None] * self._MAX_LEVEL def search(self, target: int) -> bool: p = self._head for i in range(self._level_count - 1, -1, -1): # 从最高索引层级不断搜索,如果当前层级没有,则下沉到低一级的层级 while p._forwards[i] and p._forwards[i]._data < target: p = p._forwards[i] if p._forwards[0] and p._forwards[0]._data == target: return True return False def add(self, num: int) -> None: level = self._random_level() # 随机生成索引层级 if self._level_count < level: # 如果当前层级小于 level, 则更新当前最高层级 self._level_count = level new_node = ListNode(num) # 生成新结点 new_node._forwards = [None] * level update = [self._head] * self._level_count # 用来保存各个索引层级插入的位置,也就是新结点的前驱结点 p = self._head for i in range(self._level_count - 1, -1, -1): # 整段代码获取新插入结点在各个索引层级的前驱节点,需要注意的是这里是使用的当前最高层级来循环。 while p._forwards[i] and p._forwards[i]._data < num: p = p._forwards[i] update[i] = p for i in range(level): # 更新需要更新的各个索引层级 new_node._forwards[i] = update[i]._forwards[i] update[i]._forwards[i] = new_node def erase(self, num: int) -> bool: update = [None] * self._level_count p = self._head for i in range(self._level_count - 1, -1, -1): while p._forwards[i] and p._forwards[i]._data < num: p = p._forwards[i] update[i] = p if p._forwards[0] and p._forwards[0]._data == num: for i in range(self._level_count - 1, -1, -1): if update[i]._forwards[i] and update[i]._forwards[i]._data == num: update[i]._forwards[i] = update[i]._forwards[i]._forwards[i] return True while self._level_count > 1 and not self._head._forwards[self._level_count]: self._level_count -= 1 return False def _random_level(self, p: float = 0.5) -> int: level = 1 while random.random() < p and level < self._MAX_LEVEL: level += 1 return level 复杂度分析空间复杂度跳表通过建立索引提高查找的效率,是典型的“空间换时间”的思想,那么空间复杂度到底是多少呢?我们假设原始链表有 $N$ 个元素,一级索引有 $\\frac{N}{2}$,二级索引有 $\\frac{N}{4}$,k 级索引有 $\\frac{N}{2^k}$ 个元素,而最高级索引一般有 $2$ 个元素。所以,索引结点的总和是 $\\frac{N}{2} + \\frac{N}{2^2} + \\frac{N}{2^3}+…+ 2 \\approx N-2$ ,因此可以得出空间复杂度是 $O(N)$, $N$ 是原始链表的长度。 上面的假设前提是每两个结点抽出一个结点到上层索引。那么如果我们每三个结点抽出一个结点到上层索引,那么索引总和就是 $\\frac{N}{3} + \\frac{N}{3^2} + \\frac{N}{3^3} + 9 + 3 + 1 \\approx \\frac{N}{2}$, 额外空间减少了一半。因此我们可以通过减少索引的数量来减少空间复杂度,但是相应的会带来查找效率一定的下降。而具体这个阈值该如何选择,则要看具体的应用场景。 另外需要注意的是,在实际的应用当中,索引结点往往不需要存储完整的对象,只需要存储对象的 key 和对应的指针即可。因此当对象比索引结点占用空间大很多时,索引结点所占的额外空间(相对原始数据来讲)又可以忽略不计了。 时间复杂度查找的时间复杂度来看看时间复杂度 $O(logN)$ 是如何推导出来的,首先我们看下图: 如上图所示,此处我们假设每两个结点会抽出一个结点来作为上一级索引的结点。也就是说,原始链表有 $N$ 个元素,一级索引有 $\\frac{N}{2}$,二级索引有 $\\frac{N}{4}$,k 级索引有 $\\frac{N}{2^k}$ 个元素,而最高级索引一般有 $2$ 个元素。 也就是说:最高级索引 $x$ 满足 $2 = N/2^x$, 由此公式可以得出 $x = \\log_2(N)-1$ , 加上原始数据这一层, 跳表的总高度为 $h = \\log_2(N)$。那么,我们在查找过程中每一层索引最多遍历几个元素呢?从图中我们可以看出来每一层最多需要遍历 3 个结点。因此,由公式 时间复杂度 = 索引高度*每层索引遍历元素个数, 可以得出跳表中查找一个元素的时间复杂度为 $O(3 \\times \\log(N))$,省略常数即为 $O(\\log(N))$。 插入的时间复杂度跳表的插入分为两部分操作: 寻找到对应的位置,时间复杂度为 $O(logN)$, $N$ 为链表长度。 插入数据。我们在前面已经推导出跳表索引的高度为 $logN$。 因此,我们将数据插入到各层索引中的最坏时间复杂度为 $O(logN)$。 综上所述,插入操作的时间复杂度为 $O(logN)$ 删除的时间复杂度跳表的删除操作和查找类似,只是需要在查找后删除对应的元素。查找操作的时间复杂度是 $logN$。那么后面删除部分代码的时间复杂度是多少呢?我们知道在跳表中,每一层索引都是一个有序的单链表,而删除单个元素的复杂度为 $O(1)$, 索引层数为 $logN$,因此删除部分代码的时间复杂度为$logN$。那么删除操作的总时间复杂度为- $O(logN) + O(logN) = 2O(logN)$。我们忽略常数部分,删除元素的时间复杂度为 $O(logN)$。 扩展在工业上,使用跳表的场景很多,下面做些简单的介绍,有兴趣的可以深入了解: redis 当中 zset 使用了跳表 HBase MemStore 当中使用了跳表 LevelDB 和 RocksDB 都是 LSM Tree 结构的数据库,内部的 MemTable 当中都使用了跳表 配套网站官网开辟了一个区域,大家可以直接访问查看本书配套的配套代码,包括 JavaScript,Java,Python 和 C++。 也欢迎大家留言给我们自己想要支持的语言,我们会郑重考虑大家的意见。 效果大概是这样的: 实体版购书链接 电子版购书链接","categories":[],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法通关之路","slug":"算法通关之路","permalink":"https://lucifer.ren/blog/tags/算法通关之路/"}]},{"title":"如何准备算法竞赛?","slug":"cses","date":"2021-11-16T16:00:00.000Z","updated":"2023-01-05T12:24:49.553Z","comments":true,"path":"2021/11/17/cses/","link":"","permalink":"https://lucifer.ren/blog/2021/11/17/cses/","excerpt":"如果你想参加算法竞赛的建议越早越好。大一或者更早就需要准备起来了。如果你已经快毕业了,那就没有必要准备了,当做兴趣参加一些力扣的比赛也是不错的。","text":"如果你想参加算法竞赛的建议越早越好。大一或者更早就需要准备起来了。如果你已经快毕业了,那就没有必要准备了,当做兴趣参加一些力扣的比赛也是不错的。 题库算法面试的考察内容相比算法面试更多,难度也更大。比如数位 dp,倍增,乘法逆元都需要掌握。而这些内容在算法面试中出现的却不多。 题库的话有很多 OJ 网站。但是题目都太多了。 这里推荐两个网站,一个适合竞赛选手,一个适合普通求职者。 CSES 这个网站比较适合竞赛选手,题目有 300 道,刷完还是比较快的(相对于其他老牌 OJ 平台),地址:https://cses.fi/problemset/ 另外一个是 BinarySearch。用户体验做的很好,题目难度和力扣差不多,难度波动感觉比力扣小一点点,地址:binarysearch.com/ 练手比赛姑且先推荐三个平台吧。 其中一个是 codeforces, 这个参与竞赛的人知道的比较多, codeforces 是全球范围内每次比赛参加人数最多的竞赛平台。普通人可能不太知道。这个网站的比较难度偏大一点,质量也更高。打过的人都知道。 第二个是 Leetcode。这个大家可能听说过。目前力扣有两种赛事。 周赛:每周日早上 10:30 双周赛:每两周一次,北京时间周六的晚上 10:30开始 力扣的难度比较入门,适合新手。不过难度波动其实也不小,难度低的时不时是手速场,难度高的话只要能做出来(即使是卡点做出来)都能进前 100。 最后推荐一个是 Google 比赛,有三个等级。 Kick Start:新手入门级,也是Google面试的敲门砖,每年举办八轮。 Code Jam :Google的王牌赛事,也是最重要的赛事,分为资格赛、A轮、B轮、C轮和决赛。决赛每年有25个名额。 Hash Code:一项团队赛,与一般编程竞赛不同,赛题一般为没有最优答案的优化问题。分为资格赛和决赛两轮。 学习网站OI wiki 是一个内容比较全,深度也覆盖比赛的网站。里面的内容大概看了下,大部分不错,少部分是从网上抄的,质量也一般。如果你有一定的鉴别能力,这个网站是非常不错的。地址:https://oi-wiki.org/ 比如图论的 OI-wiki 目录是这样的: CP-Wiki 是个人的 Competitive Programming 学习资料总结。里面不仅有各个知识点的总结。 知识总结是大纲性质的,比较简略,适合拿来查缺补漏。 而且还有各个比赛的题解,强烈建议学习竞赛的你收藏。 学习图书《算法竞赛进阶指南》推荐阅读李煜东的《算法竞赛进阶指南》。他有丰富的参与竞赛以及培训竞赛的经验,同时他也是 Google 的工程师。 李煜东曾为NOI系列竞赛、NOI导刊培训基地以及全国各地多所学校的选手授课,并在网络上组织模拟赛数十场,经验丰富、讲解透彻、广受好评。多次协助石家庄市第二中学的信息学竞赛集训工作,参与北京大学“数据结构与算法”、“算法设计与分析”的课程教学、考试命题工作。 豆瓣评分 9.1 ,群众的眼睛还是雪亮的! 这种书在算法竞赛中知名度还是很高的。你如果准备算法面试的话可能听说过。 如果没有听说过,现在不妨买来看看。 附上这种书的目录给大家: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778790x00 基本算法0x01 位运算0x02 枚举、模拟、递推0x03 递归0x04 二分0x05 排序0x06 倍增0x07 贪心0x08 总结与练习0x10 基本数据结构0x11 栈0x12 队列0x13 链表与邻接表0x14 Hash0x15 字符串0x16 Trie0x17 二叉堆0x18 总结与练习0x20 搜索0x21 树与图的遍历0x22 深度优先捜索0x23 剪枝0x24 迭代加深0x25 广度优先捜索0x26 广捜变形0x27 A*0x28 IDA*0x29 总结与练习0x30 数学知识0x31 质数0x32 约数0x33 同余0x34 矩阵乘法0x35 高斯消元与线性空间0x36 组合计数0x37 容斥原理与Möbius函数0x38 概率与数学期望0x39 0/1分数规划0x3A 博弈论之SG函数0x3B 总结与练习0x40 数据结构进阶0x41 并査集0x42 树状数组0x43 线段树0x44 分块0x45 点分治0x46 二叉査找树与平衡树初步0x47 总结与练习0x50 动态规划0x51 线性DP0x52 背包0x53 区间DP0x54 树形DP0x55 环形与后效性处理0x56 状态压缩DP0x57 倍增优化DP0x58 数据结构优化DP0x59 单调队列优化DP0x5A 斜率优化0x5B 四边形不等式0x5C 计数类DP0x5D 数位统计DP0x5E 总结与练习0x60 图论0x61 最短路0x62 最小生成树0x63 树的直径与最近公共祖先0x64 基环树0x65 负环与差分约束0x66 Tarjan算法与无向图连通性0x67 Tarjan算法与有向图连通性0x68 二分图的匹配0x69 二分图的覆盖与独立集0x6A 网络流初步0x6B 总结与练习0x70 综合技巧与实践0x71 C++ STL0x72 随机数据生成与对拍0x7F 附录 如果你不打算参与算法竞赛,我也建议你买过来看看,不过内容可以选择性看看即可。如果你也不知道应该看哪部分,可以在我的交流群中进行讨论,公众号力扣加加回复 leetcode 进群。 《Guide to Competitive Programming》《Guide to Competitive Programming》这种书我自己没有读过。不过听朋友说内容不错,大家可以试读一下,看看目录是否适合自己。 BTW,前面提到的题库网站 CSES 也推荐了这本书哦。 总结大家如何想参与算法竞赛尽量趁早,建议至少从大一就开始学习。 建议大家卖一本书系统性学习,然后找个题库跟着刷题。题库刷完之后再去参与比赛。比赛完成后可以看下大家的题解,不管自己有没有做出来,做出来也看看别人的做法是不是更好。经过这样的一条完整路线,我相信大学期间拿个名次,进个大公司还是不成问题的。当然进大公司还要其他方面也不差才行 😄","categories":[{"name":"算法比赛","slug":"算法比赛","permalink":"https://lucifer.ren/blog/categories/算法比赛/"}],"tags":[{"name":"算法比赛","slug":"算法比赛","permalink":"https://lucifer.ren/blog/tags/算法比赛/"}]},{"title":"Chrome 新功能 - 录制小视频","slug":"chrome-recorder","date":"2021-11-09T16:00:00.000Z","updated":"2023-01-05T12:24:49.961Z","comments":true,"path":"2021/11/10/chrome-recorder/","link":"","permalink":"https://lucifer.ren/blog/2021/11/10/chrome-recorder/","excerpt":"Chrome 97 推出了一个预览功能 - Recorder。它允许你录制 Web 页面的操作并支持回放,编辑,测量性能 等诸多功能。","text":"Chrome 97 推出了一个预览功能 - Recorder。它允许你录制 Web 页面的操作并支持回放,编辑,测量性能 等诸多功能。 它长什么样你可以直接在 chrome devtool 中看到一个 Recorder 面板,点击它就可以体验。 如果没有找到,可以尝试 cmd + shift + p 调出命令面板搜索 Recorder。当然如果该功能未发布是搜不到的 它有什么用?通过它,你可以实现一些有趣的功能。 比如: 测试同学录制一段“视频”, 然后发送给开发,开发根据这段视频定位问题。 测试某一个业务流程在各种不同的网络和硬件环境下的表现,甚至你可以看其在不同平台的表现(比如 PC,手机,平板等)。 自动化测试。你可以录制一段视频,然后通过修改其中部分参数的形式来自动化生成很多测试用例。 。。。 由于是预览版,因此最终是什么样可能还不确定。 大招对于我来说,我想要到一个比较有意思的功能。 我们知道 Chrome 的 devtool frontend(就是你看到的开发者工具) 是开源的,代码托管在 Github:https://github.com/ChromeDevTools/devtools-frontend 因此你可以直接集成它到你的项目中。比如你可以开发一个调试工具,这个工具 fork 一下 devtool frontend,然后修改 Recoreder 部分的源码,使得用户可以手动上报自己的录像,然后你将用户的录像数据,网络数据等其他数据发送到你的后端进行分析。 Recorder 的源码到时候应该在这个文件夹下 https://github.com/ChromeDevTools/devtools-frontend/tree/main/front_end/panels 这个功能我在之前的公司做过,不过做的并不好。而如果依托于 Chrome 团队,那些棘手的问题都不需要你解决了,比如性能问题就很棘手。 如果你的公司有做用户错误上报或者信息收集的需求,不妨考虑一下是否可以为你所用。 更多介绍:https://developer.chrome.com/docs/devtools/recorder/","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"Chrome","slug":"工具/Chrome","permalink":"https://lucifer.ren/blog/categories/工具/Chrome/"}],"tags":[{"name":"Chrome","slug":"Chrome","permalink":"https://lucifer.ren/blog/tags/Chrome/"}]},{"title":"面试中图论都考什么?这篇文章告诉你!","slug":"grapth","date":"2021-11-08T16:00:00.000Z","updated":"2023-01-05T12:24:49.691Z","comments":true,"path":"2021/11/09/grapth/","link":"","permalink":"https://lucifer.ren/blog/2021/11/09/grapth/","excerpt":"图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。 \b 如下就是一种逻辑上的图结构: 图是一种最复杂的数据结构,前面讲的数据结构都可以看成是图的特例。那为什么不都用图就好了,还要分那么多种数据结构呢? 这是因为很多时候不需要用到那么复杂的功能,图的很多特性都不具备,如果笼统地都称为图那么非常不利于沟通。你想你和别人沟通总不至于说这道题是考察一种特殊的图,这种图。。。。 这未免太啰嗦了,因此给其他图的特殊的图起了特殊的名字,这样就方便沟通了。直到遇到了非常复杂的情况,我们才会用到 ”真正“的图。 前面章节提到了数据结构就是为了算法服务的,数据结构就是存储数据用的,目的是为了更高效。 那么什么时候需要用图来存储数据,在这种情况图高效在哪里呢?答案很简单,那就是如果你用其他简单的数据结构无法很好地进行存储,就应该使用图了。 比如我们需要存储一种双向的朋友关系,并且这种朋友关系是多对多的,那就一定要用到图,因为其他数据结构无法模拟。","text":"图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。 \b 如下就是一种逻辑上的图结构: 图是一种最复杂的数据结构,前面讲的数据结构都可以看成是图的特例。那为什么不都用图就好了,还要分那么多种数据结构呢? 这是因为很多时候不需要用到那么复杂的功能,图的很多特性都不具备,如果笼统地都称为图那么非常不利于沟通。你想你和别人沟通总不至于说这道题是考察一种特殊的图,这种图。。。。 这未免太啰嗦了,因此给其他图的特殊的图起了特殊的名字,这样就方便沟通了。直到遇到了非常复杂的情况,我们才会用到 ”真正“的图。 前面章节提到了数据结构就是为了算法服务的,数据结构就是存储数据用的,目的是为了更高效。 那么什么时候需要用图来存储数据,在这种情况图高效在哪里呢?答案很简单,那就是如果你用其他简单的数据结构无法很好地进行存储,就应该使用图了。 比如我们需要存储一种双向的朋友关系,并且这种朋友关系是多对多的,那就一定要用到图,因为其他数据结构无法模拟。 基本概念无向图 & 有向图〔Undirected Graph & Deriected Graph〕前面提到了二叉树完全可以实现其他树结构,类似地,有向图也完全可以实现无向图和混合图,因此有向图的研究一直是重点考察对象。 本文讲的所有图都是有向图。 前面提到了我们用连接两点的线表示相应两个事物间具有这种关系。因此如果两个事物间的关系是有方向的,就是有向图,否则就是无向图。比如:A 认识 B,那么 B 不一定认识 A。那么关系就是单向的,我们需要用有向图来表示。因为如果用无向图表示,我们无法区分 A 和 B 的边表示的是 A 认识 B 还是 B 认识 A。 习惯上,我们画图的时候用带箭头的表示有向图,不带箭头的表示无向图。 有权图 & 无权图〔Weighted Graph & Unweighted Graph〕如果边是有权重的是有权图(或者带权图),否则是无权图(或不带权图)。那么什么是有权重呢?比如汇率就是一种有权重的逻辑图。1 货币 A 兑换 5 货币 B,那么我们 A 和 B 的边的权重就是 5。而像朋友这种关系,就可以看做一种不带权的图。 入度 & 出度〔Indegree & Outdegree〕有多少边指向节点 A,那么节点 A 的入度就是多少。同样地,有多少边从 A 发出,那么节点 A 的出度就是多少。 仍然以上面的图为例,这幅图的所有节点的入度和出度都为 1。 路径 & 环〔路径:Path〕 有环图〔Cyclic Graph〕 上面的图就是一个有环图,因为我们从图中的某一个点触发,能够重新回到起点。这和现实中的环是一样的。 无环图〔Acyclic Graph〕 我可以将上面的图稍加改造就变成了无环图,此时没有任何一个环路。 连通图 & 强连通图在无向图中,若任意两个顶点 i 与 j 都有路径相通,则称该无向图为连通图。 在有向图中,若任意两个顶点 i 与 j 都有路径相通,则称该有向图为强连通图。 生成树一个连通图的生成树是指一个连通子图,它含有图中全部 n 个顶点,但只有足以构成一棵树的 n-1 条边。一颗有 n 个顶点的生成树有且仅有 n-1 条边,如果生成树中再添加一条边,则必定成环。在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树,其中代价和指的是所有边的权重和。 图的建立一般图的题目都不会给你一个现成的图的数据结构。当你知道这是一个图的题目的时候,解题的第一步通常就是建图。 上面讲的都是图的逻辑结构,那么计算机中的图如何存储呢? 我们知道图是有点和边组成的。理论上,我们只要存储图中的所有的边关系即可,因为边中已经包含了两个点的关系。 这里我简单介绍两种常见的建图方式:邻接矩阵(常用,重要)和邻接表。 邻接矩阵(常见)〔Adjacency Matrixs〕第一种方式是使用数组或者哈希表来存储图,这里我们用二维数组来存储。 使用一个 n * n 的矩阵来描述图 graph,其就是一个二维的矩阵,其中 graph[i][j] 描述边的关系。 一般而言,对于无权图我都用 graph[i][j] = 1 来表示 顶点 i 和顶点 j 之间有一条边,并且边的指向是从 i 到 j。用 graph[i][j] = 0 来表示 顶点 i 和顶点 j 之间不存在一条边。 对于有权图来说,我们可以存储其他数字,表示的是权重。 可以看出上图是对角线对称的,这样我们只需看一半就好了,这就造成了一半的空间浪费。 这种存储方式的空间复杂度为 O(n ^ 2),其中 n 为顶点个数。如果是稀疏图(图的边的数目远小于顶点的数目),那么会很浪费空间。并且如果图是无向图,始终至少会有 50 % 的空间浪费。下面的图也直观地反应了这一点。 邻接矩阵的优点主要有: 直观,简单。 判断两个顶点是否连接,获取入度和出度以及更新度数,时间复杂度都是 O(1) 由于使用起来比较简单, 因此我的所有的需要建图的题目基本都用这种方式。 比如力扣 743. 网络延迟时间。 题目描述: 12345678910111213141516171819有 N 个网络节点,标记为 1 到 N。给定一个列表 times,表示信号经过有向边的传递时间。 times[i] = (u, v, w),其中 u 是源节点,v 是目标节点, w 是一个信号从源节点传递到目标节点的时间。现在,我们从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1。示例:输入:times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2输出:2 注意:N 的范围在 [1, 100] 之间。K 的范围在 [1, N] 之间。times 的长度在 [1, 6000] 之间。所有的边 times[i] = (u, v, w) 都有 1 <= u, v <= N 且 0 <= w <= 100。 这是一个典型的图的题目,对于这道题,我们如何用邻接矩阵建图呢? 一个典型的建图代码: 使用哈希表构建邻接矩阵: 123graph = collections.defaultdict(list)for fr, to, w in times: graph[fr - 1].append((to - 1, w)) 使用二维数组构建邻接矩阵: 1234graph = [[0]*n for _ in range(m)] # 新建一个 m * n 的二维矩阵for fr, to, w in times: graph[fr-1][to-1] = w 这就构造了一个临界矩阵,之后我们基于这个邻接矩阵遍历图即可。 邻接表〔Adjacency List〕对于每个点,存储着一个链表,用来指向所有与该点直接相连的点。对于有权图来说,链表中元素值对应着权重。 例如在无向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 可以看出在无向图中,邻接矩阵关于对角线对称,而邻接链表总有两条对称的边。 而在有向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 由于邻接表使用起来稍微麻烦一点,另外也不常用。为了减少初学者的认知负担,我就不贴代码了。 图的遍历图建立好了,接下来就是要遍历了。 不管你是什么算法,肯定都要遍历的,一般有这两种方法:深度优先搜索,广度优先搜索(其他奇葩的遍历方式实际意义不大,没有必要学习)。 不管是哪一种遍历, 如果图有环,就一定要记录节点的访问情况,防止死循环。当然你可能不需要真正地使用一个集合记录节点的访问情况,比如使用一个数据范围外的数据原地标记,这样的空间复杂度会是 $O(1)$。 这里以有向图为例, 有向图也是类似,这里不再赘述。 关于图的搜索,后面的搜索专题也会做详细的介绍,因此这里就点到为止。 深度优先遍历〔Depth First Search, DFS〕深度优先遍历图的方法是,从图中某顶点 v 出发, 不断访问邻居, 邻居的邻居直到访问完毕。 如上图, 如果我们使用 DFS,并且从 A 节点开始的话, 一个可能的的访问顺序是: A -> C -> B -> D -> F -> G -> E,当然也可能是 A -> D -> C -> B -> F -> G -> E 等,具体取决于你的代码,但他们都是深度优先的。 广度优先搜索〔Breadth First Search, BFS〕广度优先搜索,可以被形象地描述为 “浅尝辄止”,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。 如上图, 如果我们使用 BFS,并且从 A 节点开始的话, 一个可能的的访问顺序是: A -> B -> C -> F -> E -> G -> D,当然也可能是 A -> B -> F -> E -> C -> G -> D 等,具体取决于你的代码,但他们都是广度优先的。 需要注意的是 DFS 和 BFS 只是一种算法思想,不是一种具体的算法。 因此其有着很强的适应性,而不是局限于特点的数据结构的,本文讲的图可以用,前面讲的树也可以用。实际上, 只要是非线性的数据结构都可以用。 常见算法图的题目的算法比较适合套模板。 这里介绍几种常见的板子题。主要有: Dijkstra Floyd-Warshall 最小生成树(Kruskal & Prim) 目前此小节已经删除,觉得自己写的不够详细,之后补充完成会再次开放。 A 星寻路算法 二分图(染色法)〔Bipartitie〕 拓扑排序〔Topological Sort〕 下面列举常见算法的模板。 以下所有的模板都是基于邻接矩阵建图。 强烈建议大家学习完专题篇的搜索之后再来学习下面经典算法。大家可以拿几道普通的搜索题目测试下,如果能够做出来再往下学习。推荐题目:最大化一张图中的路径价值 最短距离,最短路径Dijkstra 算法DIJKSTRA 基本思想是广度优先遍历。实际上搜索的最短路算法基本思想都是广度优先,只不过具体的扩展策略不同而已。 DIJKSTRA 算法主要解决的是图中任意一点到图中另外任意一个点的最短距离,即单源最短路径。 Dijkstra 这个名字比较难记,大家可以简单记为DJ 算法,有没有好记很多? 比如给你几个城市,以及城市之间的距离。让你规划一条最短的从城市 a 到城市 b 的路线。 这个问题,我们就可以先将城市间的距离用图建立出来,然后使用 dijkstra 来做。那么 dijkstra 究竟如何计算最短路径的呢? dj 算法的基本思想是贪心。从起点 start 开始,每次都遍历所有邻居,并从中找到距离最小的,本质上是一种广度优先遍历。这里我们借助堆这种数据结构,使得可以在 $logN$ 的时间内找到 cost 最小的点。 而如果使用普通的队列的话,其实是图中所有边权值都相同的特殊情况。 比如我们要找从点 start 到点 end 的最短距离。我们期望 dj 算法是这样被使用的。 比如一个图是这样的: 1234E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F \\ /\\ \\ || -------- 2 ---------> G ------- 1 ------ 我们使用邻接矩阵来构造: 1234567891011G = { \"B\": [[\"C\", 1]], \"C\": [[\"D\", 1]], \"D\": [[\"F\", 1]], \"E\": [[\"B\", 1], [\"G\", 2]], \"F\": [], \"G\": [[\"F\", 1]],}shortDistance = dijkstra(G, \"E\", \"C\")print(shortDistance) # E -- 3 --> F -- 3 --> C == 6 具体算法: 初始化堆。堆里的数据都是 (cost, v) 的二元祖,其含义是“从 start 走到 v 的距离是 cost”。因此初始情况,堆中存放元组 (0, start) 从堆中 pop 出来一个 (cost, v),第一次 pop 出来的一定是 (0, start)。 如果 v 被访问过了,那么跳过,防止环的产生。 如果 v 是 我们要找的终点,直接返回 cost,此时的 cost 就是从 start 到 该点的最短距离 否则,将 v 的邻居入堆,即将 (neibor, cost + c) 加入堆。其中 neibor 为 v 的邻居, c 为 v 到 neibor 的距离(也就是转移的代价)。 重复执行 2 - 4 步 代码模板: Python 1234567891011121314151617181920import heapqdef dijkstra(graph, start, end): # 堆里的数据都是 (cost, i) 的二元祖,其含义是“从 start 走到 i 的距离是 cost”。 heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 JavaScript 1234567891011121314151617181920212223242526272829const dijkstra = (graph, start, end) => { const visited = new Set() const minHeap = new MinPriorityQueue(); //注:此处new MinPriorityQueue()用了LC的内置API,它的enqueue由两个部分组成: //element 和 priority。 //堆会按照priority排序,可以用element记录一些内容。 minHeap.enqueue(startPoint, 0) while(!minHeap.isEmpty()){ const {element, priority} = minHeap.dequeue(); //下面这两个变量不是必须的,只是便于理解 const curPoint = element; const curCost = priority; if(curPoint === end) return curCost; if(visited.has(curPoint)) continue; visited.add(curPoint); if(!graph[curPoint]) continue; for(const [nextPoint, nextCost] of graph[curPoint]){ if(visited.has(nextPoint)) continue; //注意heap里面的一定是从startPoint到某个点的距离; //curPoint到nextPoint的距离是nextCost;但curPoint不一定是startPoint。 const accumulatedCost = nextCost + curCost; minHeap.enqueue(nextPoint, accumulatedCost); } } return -1} 会了这个算法模板, 你就可以去 AC 743. 网络延迟时间 了。 这里提供完整代码供大家参考: Python 123456789101112131415161718192021222324252627class Solution: def dijkstra(self, graph, start, end): heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, w in times: graph[fr - 1].append((to - 1, w)) ans = -1 for to in range(N): dist = self.dijkstra(graph, K - 1, to) if dist == -1: return -1 ans = max(ans, dist) return ans JavaScript 123456789101112131415161718192021222324252627282930313233343536373839404142434445const networkDelayTime = (times, n, k) => { //咳咳这个解法并不是Dijkstra在本题的最佳解法 const graph = {}; for(const [from, to, weight] of times){ if(!graph[from]) graph[from] = []; graph[from].push([to, weight]); } let ans = -1; for(let to = 1; to <= n; to++){ let dist = dikstra(graph, k, to) if(dist === -1) return -1; ans = Math.max(ans, dist); } return ans;};const dijkstra = (graph, startPoint, endPoint) => { const visited = new Set() const minHeap = new MinPriorityQueue(); //注:此处new MinPriorityQueue()用了LC的内置API,它的enqueue由两个部分组成: //element 和 priority。 //堆会按照priority排序,可以用element记录一些内容。 minHeap.enqueue(startPoint, 0) while(!minHeap.isEmpty()){ const {element, priority} = minHeap.dequeue(); //下面这两个变量不是必须的,只是便于理解 const curPoint = element; const curCost = priority; if(visited.has(curPoint)) continue; visited.add(curPoint) if(curPoint === endPoint) return curCost; if(!graph[curPoint]) continue; for(const [nextPoint, nextCost] of graph[curPoint]){ if(visited.has(nextPoint)) continue; //注意heap里面的一定是从startPoint到某个点的距离; //curPoint到nextPoint的距离是nextCost;但curPoint不一定是startPoint。 const accumulatedCost = nextCost + curCost; minHeap.enqueue(nextPoint, accumulatedCost); } } return -1} DJ 算法的时间复杂度为 $vlogv+e$,其中 v 和 e 分别为图中的点和边的个数。 最后给大家留一个思考题:如果是计算一个点到图中所有点的距离呢?我们的算法会有什么样的调整? 提示:你可以使用一个 dist 哈希表记录开始点到每个点的最短距离来完成。想出来的话,可以用力扣 882 题去验证一下哦~ 值得注意的是, Dijkstra 无法处理边权值为负的情况。即如果出现负权值的边,那么答案可能不正确。而基于动态规划算法的最短路(下文会讲)则可以处理这种情况。 Floyd-Warshall 算法Floyd-Warshall 可以解决任意两个点距离,即多源最短路径,这点和 dj 算法不一样。 除此之外,贝尔曼-福特算法也是解决最短路径的经典动态规划算法,这点和 dj 也是不一样的,dj 是基于贪心的。 相比上面的 dijkstra 算法, 由于其计算过程会把中间运算结果保存起来防止重复计算,因此其特别适合求图中任意两点的距离,比如力扣的 1462. 课程安排 IV。除了这个优点。下文要讲的贝尔曼-福特算法相比于此算法最大的区别在于本算法是多源最短路径,而贝尔曼-福特则是单源最短路径。不管是复杂度和写法, 贝尔曼-福特算法都更简单,我们后面给大家介绍。 当然就不是说贝尔曼算法以及上面的 dijkstra 就不支持多源最短路径,你只需要加一个 for 循环枚举所有的起点罢了。 还有一个非常重要的点是 Floyd-Warshall 算法由于使用了动态规划的思想而不是贪心,因此其可以处理负权重的情况,这点需要大家尤为注意。 动态规划的详细内容请参考之后的动态规划专题和背包问题。 算法也不难理解,简单来说就是: i 到 j 的最短路径 = i 到 k 的最短路径 + k 到 j 的最短路径的最小值。如下图: u 到 v 的最短距离是 u 到 x 的最短距离 + x 到 v 的最短距离。上图 x 是 u 到 v 的必经之路,如果不是的话,我们需要多个中间节点的值,并取最小的。 算法的正确性不言而喻,因为从 i 到 j,要么直接到,要么经过图中的另外一个点 k,中间节点 k 可能有多个,经过中间点的情况取出最小的,自然就是 i 到 j 的最短距离。 思考题: 最长无环路径可以用动态规划来解么? 该算法的时间复杂度是 $O(N^3)$,空间复杂度是 $O(N^2)$,其中 N 为顶点个数。 代码模板: Python 1234567891011121314151617181920212223# graph 是邻接矩阵,n 是顶点个数# graph 形如: graph[u][v] = wdef floyd_warshall(graph, n): dist = [[float(\"inf\") for _ in range(n)] for _ in range(n)] for i in range(n): for j in range(n): dist[i][j] = graph[i][j] # check vertex k against all other vertices (i, j) for k in range(n): # looping through rows of graph array for i in range(n): # looping through columns of graph array for j in range(n): if ( dist[i][k] != float(\"inf\") and dist[k][j] != float(\"inf\") and dist[i][k] + dist[k][j] < dist[i][j] ): dist[i][j] = dist[i][k] + dist[k][j] return dist JavaScript 123456789101112131415161718192021222324const floydWarshall = (graph, v)=>{ const dist = new Array(v).fill(0).map(() => new Array(v).fill(Number.MAX_SAFE_INTEGER)) for(let i = 0; i < v; i++){ for(let j = 0; j < v; j++){ //两个点相同,距离为0 if(i === j) dist[i][j] = 0; //i 和 j 的距离已知 else if(graph[i][j]) dist[i][j] = graph[i][j]; //i 和 j 的距离未知,默认是最大值 else dist[i][j] = Number.MAX_SAFE_INTEGER; } } //检查是否有一个点 k 使得 i 和 j 之间距离更短,如果有,则更新最短距离 for(let k = 0; k < v; k++){ for(let i = 0; i < v; i++){ for(let j = 0; j < v; j++){ dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]) } } } return 看需要} 我们回过头来看下如何套模板解决 力扣的 1462. 课程安排 IV,题目描述: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152你总共需要上 n 门课,课程编号依次为 0 到 n-1 。有的课会有直接的先修课程,比如如果想上课程 0 ,你必须先上课程 1 ,那么会以 [1,0] 数对的形式给出先修课程数对。给你课程总数 n 和一个直接先修课程数对列表 prerequisite 和一个查询对列表 queries 。对于每个查询对 queries[i] ,请判断 queries[i][0] 是否是 queries[i][1] 的先修课程。请返回一个布尔值列表,列表中每个元素依次分别对应 queries 每个查询对的判断结果。注意:如果课程 a 是课程 b 的先修课程且课程 b 是课程 c 的先修课程,那么课程 a 也是课程 c 的先修课程。 示例 1:输入:n = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]输出:[false,true]解释:课程 0 不是课程 1 的先修课程,但课程 1 是课程 0 的先修课程。示例 2:输入:n = 2, prerequisites = [], queries = [[1,0],[0,1]]输出:[false,false]解释:没有先修课程对,所以每门课程之间是独立的。示例 3:输入:n = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]输出:[true,true]示例 4:输入:n = 3, prerequisites = [[1,0],[2,0]], queries = [[0,1],[2,0]]输出:[false,true]示例 5:输入:n = 5, prerequisites = [[0,1],[1,2],[2,3],[3,4]], queries = [[0,4],[4,0],[1,3],[3,0]]输出:[true,false,true,false] 提示:2 <= n <= 1000 <= prerequisite.length <= (n * (n - 1) / 2)0 <= prerequisite[i][0], prerequisite[i][1] < nprerequisite[i][0] != prerequisite[i][1]先修课程图中没有环。先修课程图中没有重复的边。1 <= queries.length <= 10^4queries[i][0] != queries[i][1] 这道题也可以使用 Floyd-Warshall 来做。 你可以这么想, 如果从 i 到 j 的距离大于 0,那不就是先修课么。而这道题数据范围 queries 大概是 10 ^ 4 , 用上面的 dijkstra 算法肯定超时,,因此 Floyd-Warshall 算法是明智的选择。 我这里直接套模板,稍微改下就过了。完整代码:Python 12345678910111213141516171819class Solution: def Floyd-Warshall(self, dist, v): for k in range(v): for i in range(v): for j in range(v): dist[i][j] = dist[i][j] or (dist[i][k] and dist[k][j]) return dist def checkIfPrerequisite(self, n: int, prerequisites: List[List[int]], queries: List[List[int]]) -> List[bool]: graph = [[False] * n for _ in range(n)] ans = [] for to, fr in prerequisites: graph[fr][to] = True dist = self.Floyd-Warshall(graph, n) for to, fr in queries: ans.append(bool(dist[fr][to])) return ans JavaScript 12345678910111213141516171819202122232425262728293031323334//咳咳这个写法不是本题最优var checkIfPrerequisite = function(numCourses, prerequisites, queries) { const graph = {} for(const [course, pre] of prerequisites){ if(!graph[pre]) graph[pre] = {} graph[pre][course] = true } const ans = [] const dist = Floyd-Warshall(graph, numCourses) for(const [course, pre] of queries){ ans.push(dist[pre][course]) } return ans};var Floyd-Warshall = function(graph, n){ dist = Array.from({length: n + 1}).map(() => Array.from({length: n + 1}).fill(false)) for(let k = 0; k < n; k++){ for(let i = 0; i < n; i++){ for(let j = 0; j < n; j++){ if(graph[i] && graph[i][j]) dist[i][j] = true if(graph[i] && graph[k]){ dist[i][j] = (dist[i][j])|| (dist[i][k] && dist[k][j]) }else if(graph[i]){ dist[i][j] = dist[i][j] } } } } return dist} 如果这道题你可以解决了,我再推荐一道题给你 1617. 统计子树中城市之间最大距离,国际版有一个题解代码挺清晰,挺好理解的,只不过没有使用状态压缩性能不是很好罢了,地址:https://leetcode.com/problems/count-subtrees-with-max-distance-between-cities/discuss/1136596/Python-Floyd-Warshall-and-check-all-subtrees 图上的动态规划算法大家还可以拿这个题目来练习一下。 787. K 站中转内最便宜的航班 贝尔曼-福特算法和上面的算法类似。这种解法主要解决单源最短路径,即图中某一点到其他点的最短距离。 其基本思想也是动态规划。 核心算法为: 初始化起点距离为 0 对图中的所有边进行若干次处理,直到稳定。处理的依据是:对于每一个有向边 (u,v),如果 dist[u] + w 小于 dist[v],那么意味着我们找到了一条到达 v 更近的路,更新之。 上面的若干次的上限是顶点 V 的个数,因此不妨直接进行 n 次处理。 最后检查一下是否存在负边引起的环。(注意) 举个例子。对于如下的一个图,存在一个 B -> C -> D -> B,这样 B 到 C 和 D 的距离理论上可以无限小。我们需要检测到这一种情况,并退出。 此算法时间复杂度:$O(V*E)$, 空间复杂度:$O(V)$。 代码示例:Python 123456789101112131415# return -1 for not exsit# else return dis map where dis[v] means for point s the least cost to point vdef bell_man(edges, s): dis = defaultdict(lambda: math.inf) dis[s] = 0 for _ in range(n): for u, v, w in edges: if dis[u] + w < dis[v]: dis[v] = dis[u] + w for u, v, w in edges: if dis[u] + w < dis[v]: return -1 return dis JavaScript 12345678910111213141516171819const BellmanFord = (edges, startPoint)=>{ const n = edges.length; const dist = new Array(n).fill(Number.MAX_SAFE_INTEGER); dist[startPoint] = 0; for(let i = 0; i < n; i++){ for(const [u, v, w] of edges){ if(dist[u] + w < dist[v]){ dist[v] = dist[u] + w; } } } for(const [u, v, w] of edges){ if(dist[u] + w < dist[v]) return -1; } return dist} 推荐阅读: bellman-ford-algorithm 题目推荐: Best Currency Path 拓扑排序在计算机科学领域,有向图的拓扑排序是对其顶点的一种线性排序,使得对于从顶点 u 到顶点 v 的每个有向边 uv, u 在排序中都在之前。当且仅当图中没有定向环时(即有向无环图),才有可能进行拓扑排序。 典型的题目就是给你一堆课程,课程之间有先修关系,让你给出一种可行的学习路径方式,要求先修的课程要先学。任何有向无环图至少有一个拓扑排序。已知有算法可以在线性时间内,构建任何有向无环图的拓扑排序。 Kahn 算法简单来说,假设 L 是存放结果的列表,先找到那些入度为零的节点,把这些节点放到 L 中,因为这些节点没有任何的父节点。然后把与这些节点相连的边从图中去掉,再寻找图中的入度为零的节点。对于新找到的这些入度为零的节点来说,他们的父节点已经都在 L 中了,所以也可以放入 L。重复上述操作,直到找不到入度为零的节点。如果此时 L 中的元素个数和节点总数相同,说明排序完成;如果 L 中的元素个数和节点总数不同,说明原图中存在环,无法进行拓扑排序。 123456789101112131415161718192021222324252627282930313233343536def topologicalSort(graph): \"\"\" Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph using BFS \"\"\" indegree = [0] * len(graph) queue = collections.deque() topo = [] cnt = 0 for key, values in graph.items(): for i in values: indegree[i] += 1 for i in range(len(indegree)): if indegree[i] == 0: queue.append(i) while queue: vertex = queue.popleft() cnt += 1 topo.append(vertex) for x in graph[vertex]: indegree[x] -= 1 if indegree[x] == 0: queue.append(x) if cnt != len(graph): print(\"Cycle exists\") else: print(topo)# Adjacency List of Graphgraph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []}topologicalSort(graph) 最小生成树首先我们来看下什么是生成树。 首先生成树是原图的一个子图,它本质是一棵树,这也是为什么叫做生成树,而不是生成图的原因。其次生成树应该包括图中所有的顶点。 如下图由于没有包含所有顶点,换句话说所有顶点没有在同一个联通域,因此不是一个生成树。 黄色顶点没有包括在内 你可以将生成树看成是根节点不确定的多叉树,由于是一棵树,那么一定不包含环。如下图就不是生成树。 因此不难得出,最小生成树的边的个数是 n - 1,其中 n 为顶点个数。 接下来我们看下什么是最小生成树。 最小生成树是在生成树的基础上加了最小关键字,是最小权重生成树的简称。从这句话也可以看出,最小生成树处理正是有权图。生成树的权重是其所有边的权重和,那么最小生成树就是权重和最小的生成树,由此可看出,不管是生成树还是最小生成树都可能不唯一。 最小生成树在实际生活中有很强的价值。比如我要修建一个地铁,并覆盖 n 个站,这 n 个站要互相都可以到达(同一个联通域),如果建造才能使得花费最小?由于每个站之间的路线不同,因此造价也不一样,因此这就是一个最小生成树的实际使用场景,类似的例子还有很多。 (图来自维基百科) 不难看出,计算最小生成树就是从边集合中挑选 n - 1 个边,使得其满足生成树,并且权值和最小。 Kruskal 和 Prim 是两个经典的求最小生成树的算法,这两个算法又是如何计算最小生成树的呢?本节我们就来了解一下它们。 KruskalKruskal 相对比较容易理解,推荐掌握。 Kruskal 算法也被形象地称为加边法,每前进一次都选择权重最小的边,加入到结果集。为了防止环的产生(增加环是无意义的,只要权重是正数,一定会使结果更差),我们需要检查下当前选择的边是否和已经选择的边联通了。如果联通了,是没有必要选取的,因为这会使得环产生。因此算法上,我们可使用并查集辅助完成。关于并查集,我们会在之后的进阶篇进行讲解。 下面代码中的 find_parent 部分,实际上就是并查集的核心代码,只是我们没有将其封装并使用罢了。 Kruskal 具体算法: 对边按照权值从小到大进行排序。 将 n 个顶点初始化为 n 个联通域 按照权值从小到大选择边加入到结果集,每次贪心地选择最小边。如果当前选择的边是否和已经选择的边联通了(如果强行加就有环了),则放弃选择,否则进行选择,加入到结果集。 重复 3 直到我们找到了一个联通域大小为 n 的子图 代码模板: 其中 edge 是一个数组,数组每一项都形如: (cost, fr, to),含义是 从 fr 到 to 有一条权值为 cost的边。 12345678910111213141516171819202122232425262728293031323334353637383940class DisjointSetUnion: def __init__(self, n): self.n = n self.rank = [1] * n self.f = list(range(n)) def find(self, x: int) -> int: if self.f[x] == x: return x self.f[x] = self.find(self.f[x]) return self.f[x] def unionSet(self, x: int, y: int) -> bool: fx, fy = self.find(x), self.find(y) if fx == fy: return False if self.rank[fx] < self.rank[fy]: fx, fy = fy, fx self.rank[fx] += self.rank[fy] self.f[fy] = fx return Trueclass Solution: def Kruskal(self, edges) -> int: n = len(points) dsu = DisjointSetUnion(n) edges.sort() ret, num = 0, 1 for length, x, y in edges: if dsu.unionSet(x, y): ret += length num += 1 if num == n: break return ret PrimPrim 算法也被形象地称为加点法,每前进一次都选择权重最小的点,加入到结果集。形象地看就像一个不断生长的真实世界的树。 Prim 具体算法: 初始化最小生成树点集 MV 为图中任意一个顶点,最小生成树边集 ME 为空。我们的目标是将 MV 填充到 和 V 一样,而边集则根据 MV 的产生自动计算。 在集合 E 中 (集合 E 为原始图的边集)选取最小的边 其中 u 为 MV 中已有的元素,而 v 为 MV 中不存在的元素(像不像上面说的不断生长的真实世界的树),将 v 加入到 MV,将 加到 ME。 重复 2 直到我们找到了一个联通域大小为 n 的子图 代码模板: 其中 dist 是二维数组,dist[i][j] = x 表示顶点 i 到顶点 j 有一条权值为 x 的边。 1234567891011121314151617181920class Solution: def Prim(self, dist) -> int: n = len(dist) d = [float(\"inf\")] * n # 表示各个顶点与加入最小生成树的顶点之间的最小距离. vis = [False] * n # 表示是否已经加入到了最小生成树里面 d[0] = 0 ans = 0 for _ in range(n): # 寻找目前这轮的最小d M = float(\"inf\") for i in range(n): if not vis[i] and d[i] < M: node = i M = d[i] vis[node] = True ans += M for i in range(n): if not vis[i]: d[i] = min(d[i], dist[i][node]) return ans 两种算法比较为了后面描述方便,我们令 V 为图中的顶点数, E 为图中的边数。那么 KruKal 的算法复杂度是 $O(ElogE)$,Prim 的算法时间复杂度为 $E + VlogV$。因此 Prim 适合适用于稠密图,而 KruKal 则适合稀疏图。 大家也可以参考一下 维基百科 - 最小生成树 的资料作为补充。 另外这里有一份视频学习资料,其中的动画做的不错,大家可以作为参考,地址:https://www.bilibili.com/video/BV1Eb41177d1/ 大家可以使用 LeetCode 的 1584. 连接所有点的最小费用 来练习该算法。 其他算法A 星寻路算法A 星寻路解决的问题是在一个二维的表格中找出任意两点的最短距离或者最短路径。常用于游戏中的 NPC 的移动计算,是一种常用启发式算法。一般这种题目都会有障碍物。除了障碍物,力扣的题目还会增加一些限制,使得题目难度增加。 这种题目一般都是力扣的困难难度。理解起来不难, 但是要完整地没有 bug 地写出来却不那么容易。 在该算法中,我们从起点开始,检查其相邻的四个方格并尝试扩展,直至找到目标。A 星寻路算法的寻路方式不止一种,感兴趣的可以自行了解一下。 公式表示为: f(n)=g(n)+h(n)。 其中: f(n) 是从初始状态经由状态 n 到目标状态的估计代价, g(n) 是在状态空间中从初始状态到状态 n 的实际代价, h(n) 是从状态 n 到目标状态的最佳路径的估计代价。 如果 g(n)为 0,即只计算任意顶点 n 到目标的评估函数 h(n),而不计算起点到顶点 n 的距离,则算法转化为使用贪心策略的最良优先搜索,速度最快,但可能得不出最优解;如果 h(n)不大于顶点 n 到目标顶点的实际距离,则一定可以求出最优解,而且 h(n)越小,需要计算的节点越多,算法效率越低,常见的评估函数有——欧几里得距离、曼哈顿距离、切比雪夫距离;如果 h(n)为 0,即只需求出起点到任意顶点 n 的最短路径 g(n),而不计算任何评估函数 h(n),则转化为单源最短路径问题,即 Dijkstra 算法,此时需要计算最多的顶点; 这里有一个重要的概念是估价算法,一般我们使用 曼哈顿距离来进行估价,即 H(n) = D * (abs ( n.x – goal.x ) + abs ( n.y – goal.y ) )。 (图来自维基百科 https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95 ) 一个完整的代码模板: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100grid = [ [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 1, 0], [0, 0, 0, 0, 1, 0],]\"\"\"heuristic = [[9, 8, 7, 6, 5, 4], [8, 7, 6, 5, 4, 3], [7, 6, 5, 4, 3, 2], [6, 5, 4, 3, 2, 1], [5, 4, 3, 2, 1, 0]]\"\"\"init = [0, 0]goal = [len(grid) - 1, len(grid[0]) - 1] # all coordinates are given in format [y,x]cost = 1# the cost map which pushes the path closer to the goalheuristic = [[0 for row in range(len(grid[0]))] for col in range(len(grid))]for i in range(len(grid)): for j in range(len(grid[0])): heuristic[i][j] = abs(i - goal[0]) + abs(j - goal[1]) if grid[i][j] == 1: heuristic[i][j] = 99 # added extra penalty in the heuristic map# the actions we can takedelta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # go up # go left # go down # go right# function to search the pathdef search(grid, init, goal, cost, heuristic): closed = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) ] # the reference grid closed[init[0]][init[1]] = 1 action = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) ] # the action grid x = init[0] y = init[1] g = 0 f = g + heuristic[init[0]][init[0]] cell = [[f, g, x, y]] found = False # flag that is set when search is complete resign = False # flag set if we can't find expand while not found and not resign: if len(cell) == 0: return \"FAIL\" else: # to choose the least costliest action so as to move closer to the goal cell.sort() cell.reverse() next = cell.pop() x = next[2] y = next[3] g = next[1] if x == goal[0] and y == goal[1]: found = True else: for i in range(len(delta)): # to try out different valid actions x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >= 0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost f2 = g2 + heuristic[x2][y2] cell.append([f2, g2, x2, y2]) closed[x2][y2] = 1 action[x2][y2] = i invpath = [] x = goal[0] y = goal[1] invpath.append([x, y]) # we get the reverse path from here while x != init[0] or y != init[1]: x2 = x - delta[action[x][y]][0] y2 = y - delta[action[x][y]][1] x = x2 y = y2 invpath.append([x, y]) path = [] for i in range(len(invpath)): path.append(invpath[len(invpath) - 1 - i]) print(\"ACTION MAP\") for i in range(len(action)): print(action[i]) return patha = search(grid, init, goal, cost, heuristic)for i in range(len(a)): print(a[i]) 典型题目1263. 推箱子 二分图二分图我在这两道题中讲过了,大家看一下之后把这两道题做一下就行了。其实这两道题和一道题没啥区别。 0886. 可能的二分法 0785. 判断二分图 推荐顺序为: 先看 886 再看 785。 总结理解图的常见概念,我们就算入门了。接下来,我们就可以做题了。 一般的图题目有两种,一种是搜索题目,一种是动态规划题目。 对于搜索类题目,我们可以: 第一步都是建图 第二步都是基于第一步的图进行遍历以寻找可行解 如果题目说明了是无环图,我们可以不使用 visited 数组,否则大多数都需要 visited 数组。当然也可以选择原地算法减少空间复杂度,具体的搜索技巧会在专题篇的搜索篇进行讨论。 图的题目相对而言比较难,尤其是代码书写层面。但是就面试题目而言, 图的题目类型却不多。 就搜索题目来说,很多题目都是套模板就可以解决。因此建议大家多练习模板,并自己多手敲,确保可以自己敲出来。 而对于动态规划题目,一个经典的例子就是Floyd-Warshall 算法,理解好了之后大家不妨拿 787. K 站中转内最便宜的航班 练习一下。当然这要求大家应该先学习动态规划,关于动态规划,我们会在后面的《动态规划》以及《背包问题》中进行深度讲解。 \b 常见的图的板子题有以下几种: 最短路。算法有 DJ 算法, floyd 算法 和 bellman 算法。这其中有的是单源算法,有的是多源算法,有的是贪心算法,有的是动态规划。 拓扑排序。拓扑排序可以使用 bfs ,也可以使用 dfs。相比于最短路,这种题目属于知道了就简单的类型。 最小生成树。最小生成树是这三种题型中出现频率最低的,可以最后突破。 A 星寻路和二分图题目比例非常低,大家可以根据自己的情况选择性掌握。","categories":[{"name":"图","slug":"图","permalink":"https://lucifer.ren/blog/categories/图/"}],"tags":[{"name":"图","slug":"图","permalink":"https://lucifer.ren/blog/tags/图/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"《算法通关之路》邀请你来试读","slug":"new-book","date":"2021-10-23T16:00:00.000Z","updated":"2023-01-05T12:24:49.539Z","comments":true,"path":"2021/10/24/new-book/","link":"","permalink":"https://lucifer.ren/blog/2021/10/24/new-book/","excerpt":"新书出版已经有一段时间了,也陆续收到了一些读者的反馈。今天咱就回答一些读者常见的问题以及《算法通关之路》一些内容剧透。 其实出版后已经有不少读者看完了并且给了非常优质的读后感。下面我挑选几个章节的优质留言给大家。","text":"新书出版已经有一段时间了,也陆续收到了一些读者的反馈。今天咱就回答一些读者常见的问题以及《算法通关之路》一些内容剧透。 其实出版后已经有不少读者看完了并且给了非常优质的读后感。下面我挑选几个章节的优质留言给大家。 读者留言 第 6 章二分法,虽然二分法是一个比较经典的算法,但对于大部分人一直是个头痛的问题。我每次刷 LeetCode 的时候,常常看不出来要使用二分法,或者知道要用二分法,但花了很长的时间在调试二分法的边界问题。通过学习此章很大程度上解决了我的烦恼,让我对二分法有了全方面的了解。它从二分法的经典问题开始讲起,再到后面的二分法的变种问题。其中详细介绍了什么时候用二分法,以及编写二分法的过程中所需要注意的边界问题。如果你也对二分法感到烦恼,十分推荐你阅读它,相信可以从这一章节学习到二分法的精髓。 第 7 章位运算,与很多经典算法相比位运算的算法普适性并不高,很多程序员对此并不熟悉,这一章节由一位音视频架构师主笔,音视频的处理里,充斥着大量对二进制数据的处理。听他的说法,每次看到位运算的问题,都有亲切的感觉,让人忍不住想看一看他的见解。人们习惯了使用十进制的计算规则,但如果能够有二进制的思维,能够将数据二进制话,然后运用位运算进行处理,一定能打开新的思路。 非常喜欢第 8 章设计这一章节,让我更深入的了解了高级数据结构的设计。从我入行开始,在经历的面试当中,算法(algos)和系统设计(system design) 基本是必考的两个方向,尤其是在面北美、欧洲的公司的时候。而且这两年发现,国内的巨头也开始使用算法题和系统设计题来作为面试的内容。对于要做“卷中王者”的我们来讲,这是必须要掌握的知识。这一章由浅入深地讲解了常见的几种高级数据结构。个人尤其喜欢对于 LRU、LFU 和 跳表的讲解,一步步让你去了解设计的缘由和取舍。其中的每个知识点,既可以出在算法的面试题里面,也可以作为 system design 的基础考点。另外值得一说的是,这一章节的时间复杂度的推理非常缜密,并且在延展部分给出了相关领域的论文。总而言之,很值得一看。 双指针和滑动窗口在 LeetCode 中是两个 tag,但本质上可以将滑动窗口看做双指针的特殊应用。本质上,可以将双指针看做对数组、链表、字符串的两个索引,依照这个思路,甚至可以出现多个索引的情况。而滑动窗口是利用两个索引,来完成两索引内的一系列操作。考虑到窗口的大小是否固定、窗口的起始位置等,可以对这类问题进行很多优化。本书的这两节,也都给出了不同的解题思路。非常推荐研究一下里面的题目。 说说公认最难的动态规划题目,这本书中不仅有专门的章节带你循序渐进的学习,还通过游戏、博弈和股票系列专题带你巩固基础,触类旁通。一直懵懵懂懂关于动态规划与其他几种算法思想的异同和关联,通过读完此书获得了不少答案。 在刷 leetcode 当中,以 分治法 作为 tag 的题目并不多,并且总和 dp、dfs 等算法知识混起来,增加理解难度。非常喜欢这本书对于分治的讲解,尤其是开头总结了在笔试、面试中可能面临的大部分分治类型题目,替我省了很多的精力。比如对于“合并 k 个排序列表”的题目,一步步从暴力法到最优解法,学习坡度变得平缓,而不是难度的陡增,非常推荐一观。 贪心法是一个最让我摸不着头脑的算法,每道题的题解都相差较大,很难找到一个共性的东西。 第十五章讲解了很多常见的贪心策略,例如问题拆解,限制条件等等,这些贪心策略让我面对贪心题目有了更加清晰的思路,以及更多的选择方向。此外,章节末尾还有对解题技巧的总结,这些技巧比较精炼,启发了我以后如何提高贪心题目的解题能力,非常贴心,推荐大家好好阅读这一章节。相比于其它算法,回溯法的算法思想比较固定,但怎么理解回溯法,快速应用回溯法是一个较大的难点。第十六章开篇就详细介绍了回溯法的解题模板,并在一道经典的组合问题上应用,让我对回溯法有了很直观的理解。同时,后文还讲解了不同背景下回溯法的应用题目,在提高应用模板能力的同时,学习到各种场景下的回溯技巧,相信能够在以后更加灵活地应用回溯模板,推荐大家阅读。 第十八章是我比较喜欢的一个章节,它总结了常见的解题模板,帮助我快速地学习各种套路,加深解题模板的理解。这些解题模板可以作为复习的材料,在面试或者笔试前重新理顺套路题目的思路。同时,解题模板来自于前面章节的内容,个人认为可以遮住代码,尝试根据套路背景自己编写代码,以此检验前面内容是否掌握。 非常感谢大家的认可,也希望大家拿到自己满意的 offer! 最后来回答几个读者感兴趣的问题。 1. 需要按照顺序看么?完全不需要。实际上,我也非常建议大家跳着看。优先看自己正在学习的内容。 比如你正在学习动态规划,可以直接看动态规划章节,股票章节以及游戏章节。比如你对复杂度分析不太懂就可以先看第一章。 我做了一个”脑图“给大家快速预览书的主要内容。如果大家实在不知道阅读顺序,推荐用这个脑图中的顺序从上到下看。 2. 我已经看了你的 Github 了,还需要这本书么?不管你是看了我的 Github(地址:https://github.com/azl397985856/leetcode),还是参加了我的 《91 天学算法》活动。我都强烈建议你购买一本。 原因有三: 虽然知识点还是那么多,几乎没有新增知识点。但是相同的主题,书的讲述方式和风格完全不同。书内容更加严谨,搭配 Github 和《91 天学算法》讲义进行学习效果更好。 比如书中二分章节中《153. 寻找旋转排序数组中的最小值》,这道题是一个很简单的二分。但是在证明复杂度的时候就使用了两种方法来证明。 这种严谨的态度贯穿整本书。通常来说,复杂的时间复杂度我也会给出分析,而不是直接贴出一个答案给大家。虽然不一定会像这个二分一样给出多种证明方式,但是也力求完整和准确。 简单来说,这本书在很大程度上都是和 Github 以及我的其他算法资料的补充,具体内容需要大家可以买到书后自己翻翻来体会啦。 很多题目由于我在书中做了讲解,因此就没有公开到 Github 等资料。因此如果你想看这些题目的题解就要通过书来看。 书的代码比较全。很多同学反馈想增加 xx 语言,但是确实没有太多精力增加语言。但是我给这本书所有的题目都增加了三种主流语言的代码,包括 Python,CPP 和 Java,另外部分题目也提供了 JS 版本。 最后强烈建议大家搭配《算法通关之路》来学习,尤其是那些参加《91 天学算法》的同学。 哦,对了!《91 天学算法》每个月都会在坚持每天打卡的人中抽取 3 人免费获取我们的《算法通关之路》! 《如何参加 91 天学算法(第五期)?》 https://lucifer.ren/blog/2021/08/21/91algo-5/ 3. 有试读版么?有的。https://leetcode-solution.cn/book-intro 这里有: amazon 官方提供的复杂度分析的部分试读 西法提供的设计章节的部分试读。 大家赶紧点 ta,感受书的全面和严谨吧~ (_^▽^_) 4. 有购书活动吗?西法打算增加一个活动。 规则是凡在活动期间购买《算法通关之路》并好评的同学截图给我,验证后可以免费参加当前一期《91 天学算法》。 注意只能免费参加购书时间当前一期哦,比如你是在第五期之后第六期结束前购买的,那就只能免费参加第六期的。 不过由于这一期已经开始了,现在搞这个活动会损害一部分已经参加了 91 活动并且买了我的书的朋友,因此西法打算下期再开启这个活动。 下期什么时候?大约在冬季(12 月份) 勘误首先,一些读者很热心地给我反应印刷错误。比如复杂度分析部分单调栈的代码是错误的,少个部分代码,应该是印刷问题,之前校验 word 的时候没有这个问题。大家可以访问官方网站的配套代码查看正确的代码。 官方网站地址:https://leetcode-solution.cn/book 如果大家发现其他问题也可以反馈到我这里,或者直接在读者交流群众反馈。","categories":[{"name":"《算法通关之路》","slug":"《算法通关之路》","permalink":"https://lucifer.ren/blog/categories/《算法通关之路》/"}],"tags":[{"name":"《算法通关之路》","slug":"《算法通关之路》","permalink":"https://lucifer.ren/blog/tags/《算法通关之路》/"}]},{"title":"聊聊刷题中的**顿悟**时刻","slug":"algo-fakers","date":"2021-10-15T16:00:00.000Z","updated":"2023-01-05T12:24:50.075Z","comments":true,"path":"2021/10/16/algo-fakers/","link":"","permalink":"https://lucifer.ren/blog/2021/10/16/algo-fakers/","excerpt":"和几位一直坚持刷题好朋友讨论了一下刷题的顿悟时刻,他们几位大都取得了不错的 offer,比如 Google 微软,Amazon,BAT 等。 通过和他们的沟通,我发现了大家顿悟时刻都是类似的。那具体他们有哪些相似点呢?我们一起来看下。","text":"和几位一直坚持刷题好朋友讨论了一下刷题的顿悟时刻,他们几位大都取得了不错的 offer,比如 Google 微软,Amazon,BAT 等。 通过和他们的沟通,我发现了大家顿悟时刻都是类似的。那具体他们有哪些相似点呢?我们一起来看下。 1. 同样的类型要刷很多才能顿悟比如你想顿悟二分,那么首先你需要做足够多的二分题。 而由于二分其实是一个大的分类。因此理论上你如果想对二分大类顿悟,那么必不可少的是先做足够多的二分题,并且这些题目可以覆盖所有的二分类型。 比如西法总结的基础二分,最左最右二分以及能力检测二分,其中大部分有点困难的题目都是能力检测二分。 对二分不熟悉的可以看下西法之前总结的《二分专题》: 几乎刷完了力扣所有的二分题,我发现了这些东西(上) 几乎刷完了力扣所有的二分题,我发现了这些东西(下) 那么推而广之,如果你想对刷算法题整体进行顿悟,那么就不得不先做足够多的题目,且这些题目能覆盖所有你想顿悟的考点。 这也就是说为什么你看的大佬中的大佬都刷了上千道题的原因。因为没有上千道题目的积累,你很难对所有题目类型都顿悟的。当然你如果只是应付大多数的考点并且不参与竞赛的话,也许小几百道也是 ok 的。 2. 回顾做过的题目有的同学比较直接,他们就是直接复习做过的题目。而有的同学则是通过做新的题目回想到之前做过的某些题,从而达到复习的作用。 不管是哪种类型。他们都必须经过一个阶段,那就是和已经做过的题目建立联系。如果你只是盲目做题的话,效率肯定上不去。 最开始刷题的时候,我会建立一些 anki 卡片。这其实就是为了强制回顾做过的题目。另外做新的题目的时候,我会强迫自己思考: 这道题考察了什么知识? 和之前做过的哪些题可以建立联系? 是否可以用之前刷题的解法套? corner case 有哪些? 。。。 经过这些思考,慢慢就会把做过的题目有机地结合起来,而不是让这些题目变成彼此的信息孤岛。 3. 对做过的题目进行抽象这个是我要讲的最后一点,但是这点却尤为重要,说它是最重要也不过分。 一方面,如果一道题目没有经过抽象,那么我们很难记住,很难在未来回忆起来。另一方面,如果一道题目能够抽象为纯粹的题目,那么说明你对这个题目看的比较透彻了。将来碰到换皮题,你一抽象,就会发现: 这不就是之前 xxxx 的换皮题么? 经常看我题解和文章的同学知道我之前写过不少换皮题的扒皮解析,这就是我做题和写文章风格。 在这里,我再举个例子。 注意:下面举的三道题例子都需要你掌握二分法的能力检测二分,如果不了解建议先看下我上面的文章。 Shopee 的零食柜这是 shopee 的校招编程题。 题目描述1234567891011121314151617shopee的零食柜,有着各式各样的零食,但是因为贪吃,小虾同学体重日益增加,终于被人叫为小胖了,他终于下定决心减肥了,他决定每天晚上去操场跑两圈,但是跑步太累人了,他想转移注意力,忘记痛苦,正在听着音乐的他,突然有个想法,他想跟着音乐的节奏来跑步,音乐有7种音符,对应的是1到7,那么他对应的步长就可以是1-7分米,这样的话他就可以转移注意力了,但是他想保持自己跑步的速度,在规定时间m分钟跑完。为了避免被累死,他需要规划他每分钟需要跑过的音符,这些音符的步长总和要尽量小。下面是小虾同学听的歌曲的音符,以及规定的时间,你能告诉他每分钟他应该跑多少步长?输入描述:输入的第一行输入 n(1 ≤ n ≤ 1000000,表示音符数),m(1<=m< 1000000, m <= n)组成,第二行有 n 个数,表示每个音符(1<= f <= 7)输出描述:输出每分钟应该跑的步长示例1输入8 5 6 5 6 7 6 6 3 1输出11 链接:https://www.nowcoder.com/questionTerminal/24a1bb82b3784f86babec24e4a5c93e0?answerType=1&f=discussion来源:牛客网 思路经过抽象,这道题本质上就是给你一个数组(数组值范围是 1 到 7 的整数),让你将数组分为最多 m 子数组,求 m 个子数组和的最小值。 直接回答子数组和最小值比较困难,但是回答某一个具体的值是否可以达到相对容易。 比如回答子数组和最小值为 100 可以不可以相对容易。因为我们只需要遍历一次数组,如果连续子数组大于 100 就切分新的一块,这样最后切分的块数小于等于 m 就意味着 100 可以。 另外一个关键点是这种检测具有单调性。比如 100 可以,那么任何大于 100 的数(比如 101)肯定都是可以的。如果你看过我上面的《二分专题》或者做过不少能力检测二分的话, 不难想到可以利用这种单调性做能力检测二分得到答案。并且我们要找到满足条件的最小的数,因此可以套用最左能力检测二分得到答案。 代码暂时不写,因为这道题和后面的一道题是一样的。 410. 分割数组的最大值题目描述12345678910111213141516171819202122232425262728给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。 示例 1:输入:nums = [7,2,5,10,8], m = 2输出:18解释:一共有四种方法将 nums 分割为 2 个子数组。 其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。示例 2:输入:nums = [1,2,3,4,5], m = 2输出:9示例 3:输入:nums = [1,4,4], m = 3输出:4 提示:1 <= nums.length <= 10000 <= nums[i] <= 1061 <= m <= min(50, nums.length) 来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/split-array-largest-sum 思路这道题官方难度是 hard。和前面题抽象后一模一样,不用我多解释了吧? 你看经过这样的抽象,是不是有种殊途同归的顿悟感觉? 代码代码支持:Python3 Python3 Code: 123456789101112131415161718192021class Solution: def splitArray(self, nums: List[int], m: int) -> int: lo, hi = max(nums), sum(nums) def test(mid): cnt = acc = 0 for num in nums: if acc + num > mid: cnt += 1 acc = num else: acc += num return cnt + 1 <= m while lo <= hi: mid = (lo + hi) // 2 if test(mid): hi = mid - 1 else: lo = mid + 1 return lo 你以为这就完了么? 类似的题目简直不要太多了。西法再给你举个例子。 LCP 12. 小张刷题计划题目描述1234567891011121314151617181920212223242526272829为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。示例 1:输入:time = [1,2,3,3], m = 2输出:3解释:第一天小张完成前三题,其中第三题找小杨帮忙;第二天完成第四题,并且找小杨帮忙。这样做题时间最多的一天花费了 3 的时间,并且这个值是最小的。示例 2:输入:time = [999,999,999], m = 4输出:0解释:在前三天中,小张每天求助小杨一次,这样他可以在三天内完成所有的题目并不花任何时间。 限制:1 <= time.length <= 10^51 <= time[i] <= 100001 <= m <= 1000 来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/xiao-zhang-shua-ti-ji-hua 思路和前面的题目类似。经过抽象,这道题本质上就是给你一个数组(数组值范围是 1 到 10000 的整数),让你将数组分为最多 m 子数组,每个子数组可以删除最多一个数,求 m 个子数组和的最小值。 和上面题目唯一的不同是,这道题允许我们在子数组中删除一个数。显然,我们应该贪心地删除子数组中最大的数。 因此我的思路就是能力检测部分维护子数组的最大值,并在每次遍历过程中增加判断:如果删除子数组最大值后以后可以满足子数组和小于检测值(也就是 mid)。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122232425class Solution: def minTime(self, time: List[int], m: int) -> int: def can(mid): k = 1 # 需要多少天 t = 0 # 当前块的总时间 max_time = time[0] for a in time[1:]: if t + min(max_time, a) > mid: t = 0 k += 1 max_time = a else: t += min(max_time, a) max_time = max(max_time, a) return k <= m l, r = 0, sum(time) while l <= r: mid = (l+r)//2 if can(mid): r = mid - 1 else: l = mid + 1 return l 时间复杂度的话三道题都是一样的,我们来分析一下。 我们知道,时间复杂度分析就看执行次数最多的代码即可,显然这道题就是能力检测函数中的代码。由于能力检测部分我们需要遍历一次数组,因此时间为 $O(n)$,而能力检测函数执行的次数是 $logm$。因此时间复杂度都是 $nlogm$,其中 n 为数组长度,m 为数组和。 总结顿悟真的是一种非常美妙的感觉,我通过采访几位大佬发现大家顿悟的经历都是类似的,那就是: 同样的类型要刷很多才能顿悟 回顾做过的题目 对做过的题目进行抽象 对第三点西法通过三道题给大家做了细致的讲解,希望大家做题的时候也能掌握好节奏,举一反三。最后祝大家刷题快乐,offer 多多。","categories":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"用什么语言刷题最有排面?","slug":"programming-idioms","date":"2021-10-09T16:00:00.000Z","updated":"2023-01-05T12:24:49.892Z","comments":true,"path":"2021/10/10/programming-idioms/","link":"","permalink":"https://lucifer.ren/blog/2021/10/10/programming-idioms/","excerpt":"很多读者向西法反应:“能不能在专题和题解里面增加 xx 语言”。 我的回答一直都是:我会尽量添加多种编程语言,但也请你不要依赖于某种具体的编程语言来学算法。 熟悉我的朋友都知道,我刚开始的时候,我使用 JS 比较多,之后使用 Python 比较多,现在也在陆续更新题解的 C++ 语言,之后会考虑其他的语言。接下来的话应该是优先考虑 Java,让大家学习曲线更平滑。 但是另一方面我也强烈建议大家不要依赖于某一种具体地编程语言。我当然知道学习和切换编程语言需要时间,也体会到使用自己熟悉的编程语言效率更高。","text":"很多读者向西法反应:“能不能在专题和题解里面增加 xx 语言”。 我的回答一直都是:我会尽量添加多种编程语言,但也请你不要依赖于某种具体的编程语言来学算法。 熟悉我的朋友都知道,我刚开始的时候,我使用 JS 比较多,之后使用 Python 比较多,现在也在陆续更新题解的 C++ 语言,之后会考虑其他的语言。接下来的话应该是优先考虑 Java,让大家学习曲线更平滑。 但是另一方面我也强烈建议大家不要依赖于某一种具体地编程语言。我当然知道学习和切换编程语言需要时间,也体会到使用自己熟悉的编程语言效率更高。 很多读者像我反应:“能不能在专题和题解里面增加 xx 语言”。 我的回答一直都是:我会尽量添加多种编程语言,但也请你不要依赖于某种具体的编程语言来学算法。 熟悉我的朋友都知道,我刚开始的时候,我使用 JS 比较多,之后使用 Python 比较多,现在也在陆续更新题解的 C++ 语言,之后会考虑其他的语言。接下来的话应该是优先考虑 Java,让大家学习曲线更平滑。 但是另一方面我也强烈建议大家不要依赖于某一种具体地编程语言。我当然知道学习和切换编程语言需要时间,也体会到使用自己熟悉的编程语言效率更高。 但问题是我们无法控制大环境。据我所知,经常更新文章的大佬使用的编程语言语法么都有,C++,Java,Python 都有的。 如果因为语言看不懂跳过,那么肯定会错过很多优秀的文章和题解。你说是不是很可惜? 那问题是我真的看不懂怎么办?一个超级有效的方式就是使用不同的编程语言刷题。比如你就定一个小目标比如用 C++刷 100 道题,这样慢慢你就对 C++ 的最最基础的特性了解了,这样下次看到别人的 C++ 题解你在看看,是不是能看懂了?这是因为大家刷题很少用一些高深的语言特性,尤其是那些大佬的题解,它们会注意这些的。因此你稍微练习一下,基本上以后看其他语言的题解就不成问题的。 很多人刚开始切换到其他编程语言会深深地感到不适应。比如怎么创建一个数组呢?怎么反转一个数组?怎么新建一个哈希表?等等等 一个个查真的是效率很低,以至于很多人都坚持不下来。 我其实刚刚在用新语言的时候也是一样的,今天介绍的网站就整理了很多常见操作的不同语言对比实现 以 C++ 的 reverse 为例: 你可以点击上面的编程语言查看其他语言的 reverse 是如何实现的,目前该网站已经提供了 277 个语言特性,这个工具网站对那些刚开始学习新语言的人非常有用。 我们甚至可以直接开启对比模式,以 Python 和 C++ 对比为例: 地址:https://programming-idioms.org/idiom/19/reverse-a-list 最后回答一个相关的问题,读者问的也比较多。那就是:有没有推荐的刷题语言? 其实这个问题之前回答过,今天再讲一次。一句话回答就是:建议选择一门动态语言和一门静态语言,比如选择 Python 和 C++。 原因是什么呢? 刷题以及打比赛都讲究速度,天下武功唯快不破。 这个快,一方面是运行速度快,另一方面是编码速度快。你可以看出很多人刷题,打比赛都会不断切换语言的。我们要承认不同语言效率是不一样的,这个效率可能是执行,也可能是编码。具体使用哪种语言,看你的需求。 论编码速度,那肯定动态语言快,论执行速度那肯定静态语言快。 所以我的建议是大家至少掌握一静一动,即掌握一个动态语言,一个静态语言。 我个人动态语言用的 Python 和 JS,静态语言用的 Java 和 CPP,大家可以作为参考。 一个小建议是你选择的语言要是题解比较热门的。那什么语言是热门的?其实很容易。力扣题解区,语言排名高的基本就是了,如下图: 掌握语言不仅能帮助你在效率中运用自如,并且还容易看懂别人的题解。除此之外还有一个用,那就是回头复习的时候用。拿我来说, 我会不固定回去刷以前做过的题,但是一道题做过了就没新鲜感了,这个时候我就换个语言继续刷,又是一番滋味。","categories":[],"tags":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"写注释就能自动出代码?copilot 尝鲜","slug":"copilot","date":"2021-10-04T16:00:00.000Z","updated":"2023-01-07T12:20:39.959Z","comments":true,"path":"2021/10/05/copilot/","link":"","permalink":"https://lucifer.ren/blog/2021/10/05/copilot/","excerpt":"copilot 是一个基于 AI 的编程辅助工具。目前已经集成在了 vscode 中,后续可能集成到更多平台和工具,目前还是测试阶段。官网地址:https://copilot.github.com/","text":"copilot 是一个基于 AI 的编程辅助工具。目前已经集成在了 vscode 中,后续可能集成到更多平台和工具,目前还是测试阶段。官网地址:https://copilot.github.com/ 支持所有语言copilot 是利用网络中现有的公开数据,尤其是开源在 Github 上的代码, 然后基于机器学习算法训练出来的。因此 copilot 理论上支持所有编程语言。 目前我测试了 JS 和 Python,效果都还蛮不错的。官方提供了 ts,go,py 和 rb 语言的示例。 注释即代码你可以通过编写注释然后一路根据 copilot 的提示编写出完整的程序。 比如我想根据 Github 用户名获取用户信息。我只需要写下这样一行注释。以 JS 为例: 1// 根据 Github 用户名获取用户信息 copilot 是如何一步步引导你完成完整功能的呢?我们来看下。 第一步: 注意:注释下面的代码颜色是浅色的,是 copilot 提示出来的。下同,不再解释。 按下 tab 键就会浅色的代码就会被填充,并提示接下来的代码。 第二步: 再次按下 tab 键,整体的代码就生成了。 类似的例子还有很多,等待大家来探索。 代码补全IDE 的一个很重要的功能就是代码补全。 copilot 增强了 IDE 的补全功能。 copilot 可以根据你的代码仓库以及世界上公开的代码仓库提示你可能的输入,从而减少你敲击键盘的次数,在更短的时间写出更多的代码,获取更多的摸鱼时间。 举个例子,仍然以 JS 为例。我想发送一个 fetch 请求。 12fetch('https://www.leetcode-solution.cn', { 它就提示我: 接下来按照它的提示,只按 tab 不写代码的情况就可以写出如下代码。 12345678910111213fetch(\"https://leetcode-solution.cn\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\", }, body: JSON.stringify({ question_id: \"1\", lang: \"javascript\", code: \"console.log(1)\", }),}).then((res) => { console.log(res);}); 对我的仓库功能来说, 上面代码有一小部分是有问题的。 不过我只需要稍微改改就行了。效率提升还是不错的。 如何使用?在 vscode 插件市场搜索 github copilot,点击 install,然后按照提示安装即可。 安装好了就可以体验了! 写写注释?敲敲代码?按按 tab?代码 duang 的一下就生成了。 总结copilot 是一个类似 tabnine 的 ai 编程辅助工具,目前以 vscode 插件的形式提供免费服务,目前是测试阶段,还没有最终发行。它有自动提示,根据注释写代码等诸多激动人心的功能。 更多功能以及最新动态请访问官方网站:https://copilot.github.com/","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"VSCODE","slug":"工具/VSCODE","permalink":"https://lucifer.ren/blog/categories/工具/VSCODE/"}],"tags":[{"name":"AI","slug":"AI","permalink":"https://lucifer.ren/blog/tags/AI/"},{"name":"VSCODE","slug":"VSCODE","permalink":"https://lucifer.ren/blog/tags/VSCODE/"}]},{"title":"有了这个可视化插件,刷题调试更轻松","slug":"algo-vis","date":"2021-09-25T16:00:00.000Z","updated":"2023-01-05T12:24:49.527Z","comments":true,"path":"2021/09/26/algo-vis/","link":"","permalink":"https://lucifer.ren/blog/2021/09/26/algo-vis/","excerpt":"如何有效学习算法学习算法的基本思路就是:先学习算法思想,然后通过做题消化思想,并在做题过程中慢慢学习,掌握一些小技巧。这其中算法思想就是道,而经典题目以及做题技巧就是术。做题是通过术来完善道。 但是很多人都反应看讲义和做题之间断层严重,也就是一看就会,一些就废。这怎么办呢?","text":"如何有效学习算法学习算法的基本思路就是:先学习算法思想,然后通过做题消化思想,并在做题过程中慢慢学习,掌握一些小技巧。这其中算法思想就是道,而经典题目以及做题技巧就是术。做题是通过术来完善道。 但是很多人都反应看讲义和做题之间断层严重,也就是一看就会,一些就废。这怎么办呢? 除了多写,多练习之外,我认为以下两点可以帮助你: 做题的时候和讲义(学习资料)进行结合 这是一个很重要的也容易被忽略的点。拿《91 天学算法》来说:看讲义就是学思想,每日一题就是巩固消化思想。做每日一题的时候,要多往讲义上靠靠,比如想一下这道题对应讲义哪一部分,考察的是讲义中提到的哪一个知识点。 看讲义(学习资料)的时候将例题用可视化的方式自己跑一遍 我刚开始学习算法的时候,基本上也是这种思路。学习完思想做题的时候对例题都在电脑或者纸上画一下代码执行流程,然后和学习的算法思想进行结合。这样不仅算法思想容易吸收,而且也收效缓解了一看就会,一写就废的尴尬境地。 但是毕竟自己画图还是有点成本的,不是所有的人都有动力自己画图的。程序员都很懒,其实我刚开始刷题的时候一直有一个想法, 如果做题有可视化显示该有多好?最好是和我讲义图类似的那种, 这样无疑对新手来说吸收思想效率肯定高。 可视化调试插件无巧不成书,前几天《91 天学算法》群里有人提到 LeetCode 刷题调试。大家有的用 IDE 调试,有的用会员的调试功能在网页调试。 其实前一阵子我分享刷题技巧的时候也分享了调试插件,没有看过的同学可以看下 力扣刷题的正确姿势是什么?。 今天再分享一个适合新手的调试工具,简单易用,直观方便。更关键的是,其已经内置到我的刷题插件 leetcode-cheatsheet 中,直接开箱即用,插件版本大于等于 0.9.0 即可。虽然它暂时还无法自动生成像我讲义里面那么完整的图和动画,但是比文字要直观太多了。后期考虑集成更多的语言以及更多的语法特性以及更好的展示效果。 该使用方式非常简单,完全满足了大家偷懒的需求。你只需要: 安装刷题插件 leetcode-cheatsheet 插件如何下载与安装可以在公众号回复插件获取 打开 leetcode 中任意一道题目,写代码。 目前支持 Python3,CPP,JavaScript 点击下方的可视化调试 按提示修改代码后点击Visualize Execution按钮 如果无法修改代码,可以先点击 edit code 这里我就想吐槽一下 leetcode 了。干嘛每一道题函数名字都不一样,真没这个必要。比如都叫 solve 不好么?希望力扣可以考虑一下这个建议。 通过控制区域控制代码执行,右侧会自动同步的可视化地显示变量信息 最后友情提示一下。可视化调试推荐在看资料(比如 91 天学算法的讲义)的时候把其中的例题用可视化的方式调试一遍,填平思路到代码的鸿沟。 之后大家做题不要依赖调试功能,而是先在大脑中调试一下,然后用工具验证。也就是说这个工具,我仅推荐你在两种情况下使用: 看算法思想资料,做其中的例子的时候一步步调试学习。 代码有 case 跑不通,先在脑子中过一下,猜测大概出问题的点,然后用工具直接定位到附近通过可视化的方式帮助你分析。 最后大家有什么想要的 feature 可以给我公众号后台或交流群里留言。","categories":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"},{"name":"算法","slug":"插件/算法","permalink":"https://lucifer.ren/blog/categories/插件/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"}]},{"title":"五天100+题,怒进谷歌!","slug":"91algo-interview-kongshi","date":"2021-09-23T16:00:00.000Z","updated":"2023-01-05T12:24:49.831Z","comments":true,"path":"2021/09/24/91algo-interview-kongshi/","link":"","permalink":"https://lucifer.ren/blog/2021/09/24/91algo-interview-kongshi/","excerpt":"背景空识拿到了Google 的 offer,和狗头做起了同事。 他一个礼拜刷了 100 多道题,然后面试 Google 终于通过了。这个经历还是蛮具有代表性的,分享给正在准备面试的你。 ​","text":"背景空识拿到了Google 的 offer,和狗头做起了同事。 他一个礼拜刷了 100 多道题,然后面试 Google 终于通过了。这个经历还是蛮具有代表性的,分享给正在准备面试的你。 ​ 以下 Q 为 lucifer,A 为 空识。 采访Q: 你是什么时候开始接触数据结构与算法(以下简称算法)的? A: 大概是十年前读本科的时候……但是没有系统性的学习,只了解过一些特殊用途的算法(X 算法,RSA 算法,…)打算转码的时候才开始重头系统性的学习。 Q: 你是什么时候接触 91 天学算法(以下简称 91 天)的?从什么途径得知的? A: Github 上的题解->西法的公众号,在公众号里了解到的。 Q: 91 天有给你带来了什么样的变化么? A: 我变秃了,也变强了。从一开始看到 two sum 觉得精妙,周赛做不出 easy 到现在能 稳定写 2-3 题。一开始 oa 撞原题都写不好到现在不少 oa 直接做也能拿到面试机会了。 尤其印象深刻的是 一开始自己刷题,easy 可能都要磨蹭个 2-3 小时研究相关的 topic 搞明白题解,最后一周刚好要准备狗厂面试,五天就刷了 100+题…… 真的变秃了。 Q: 学习算法过程中有“顿悟”的时刻么? A: 有呀,正如六祖慧能在《坛经》里说到的,“迷闻经累劫,悟则刹那间”,在各种重复思考卡壳之后再去看题解的点悟往往会有那种开了一扇门的感觉,然后就悟了哈哈。 Q: 你比较擅长的算法是什么?可以给大家简单分享一下么? A: 其实是数学法,因为本身是学数学相关方向的,以前经常被同学分享这种很巧的题,所以练习得比较多。 Q: 有没有什么想和刚入坑算法的同学分享的? A: 一定一定一定一定要坚持不要放弃,每天再难至少要刷 1 ~ 3 题(具体看是否脱产),每天发掘一个小技巧,91 天就能学会很多很多东西。而且尤其是一开始,easy/medium 的题都刷的很艰难需要很多时间,这个时候很容易会怀疑自己的能力/天赋,但是千万不要绝望不要放弃,因为绝望/放弃其实没有任何作用,咬紧牙关坚持下去是最重要的,超越了觉悟,就能看到希望。 另外还有就是万事从简,我最早转码纠结于以后到底该做什么纠结了很久,其实这个回头看,其实都要学的,直接捡一个比较主流找得到不少题解的语言开始最重要。 Q: 相对而言,你觉得 91 天哪里做的还不够好?应该如何改进? A: 其实我觉得挺好的……感觉可能硬性要求开始的时候多打点卡比较好……?因为感觉一开始人比较容易有惰性,不想走出舒适区,可能需要一点 push。 Q: 有没有什么面试和刷题技巧或建议给大家 A: 我觉得很重要的几个点: 力扣官方有很好的 explore 卡片,那个可以很好的作为基础篇的补充,针对性的刷题可以帮你形成你自己的理解,有理解了刷题就快了。 力扣的公司 tag 是可以按照频率选题的,刷高频题通常能够给你更直观的一个关于“这个公司到底是在考什么”的体验。 打周赛,其实多少分不重要,能不能涨分也不重要,重要的是思路。 所以你可以随便打开任何一期周赛,然后用力扣网站的模拟面试功能打。然后设立个目标,目标应该是差不多一个小时做二望三。 Think out loud。面试的时候沟通很重要,所以一定要学会自言自语,写完主动手动跑一遍,想一次 edge case,然后算一遍复杂度,可以的话甚至可以想一想这个题可以怎么改编怎么 followup。 注:北美的小伙伴我推荐用 interviewing.io,我觉得练习很有效…… 一定一定一定一定要自信,振作起来,生活不容易,你是最棒的。 lucifer 总结空识最后给的五个建议都非常的实用,手动点赞。另外空识的建议已经收到了。我们后期会加大 push 力度,努力营造一个积极向上的刷题氛围,fighting! 此外,空识提到了很多次打模拟面试或者周赛。其实我也建议大家过一遍 tag 后就先打 20 次周赛,做几道题都不重要,就是锻炼自己的做题能力以及给定压力下的思考反应等。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"《算法通关之路》出版啦!","slug":"leetcode-solution-book","date":"2021-09-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.839Z","comments":true,"path":"2021/09/04/leetcode-solution-book/","link":"","permalink":"https://lucifer.ren/blog/2021/09/04/leetcode-solution-book/","excerpt":"其实很多朋友私底下问我: 新书什么时候出版? 可以预定么? 等等 其实我比大家更着急,只不过出版图书真的是一个非常严谨的过程。不比专栏,小册等电子读物可以一边上架一边修改。传统的纸质图书的要求和流程都是严格把控的。因此只能耐心等待和配合出版社。 而现在《算法通关之路》终于要和大家见面了!🌹🌹🌹","text":"其实很多朋友私底下问我: 新书什么时候出版? 可以预定么? 等等 其实我比大家更着急,只不过出版图书真的是一个非常严谨的过程。不比专栏,小册等电子读物可以一边上架一边修改。传统的纸质图书的要求和流程都是严格把控的。因此只能耐心等待和配合出版社。 而现在《算法通关之路》终于要和大家见面了!🌹🌹🌹 不少同学都早就已经预定了,甚至有的预定了几十倍,真的是让西法感动啊! 以下是部分给我发邮件预定的同学: 虽然内容早就已经定得差不多了,但是中间的过程异常曲折,总之就是各种事情,不过好在现在已经出版了。感谢大家的鼓励和支持,不然我恐怕很难坚持下来 来秀一张新书的封面给大家看看。 那这本书里面都讲了什么干货呢?这里西法给大家做一个简单的介绍。详细目录大家可以扫描文章末尾的二维码查看。 第 1 章对一些基础的数据结构和算法进行了总结与梳理,同时介绍了常见的衡量程序性能好坏的方法——大 O 表示法。 第 2 章是数学专题。很多算法题目需要你具备一些数学知识才能解决,那么需要的数学知识有哪些,难度如何?本章将会告诉你。 第 3 章讲的是一个经典的算法问题——回文问题。 第 4 章是游戏专题。游戏专题从问题角度可以分为求解和博弈两大类,博弈类的问题将在第 12 章专门分析,本章将重点关注求解类游戏。 第 5 章介绍了两种常见的搜索算法——深度优先遍历和广度优先遍历。本章将告诉你两种搜索各自的特点是什么,适合解决什么问题。 第 6 章将对二分法进行讲解,包括其基本形式、解题技巧及算法模板等。 第 7 章讲的是位运算,旨在让读者从二进制的角度思考问题。 第 8 章讲的是设计题,学习本章内容需要读者对常见的数据结构足够熟悉。 第 9 章对两种常见的双指针进行了详细的讲解。 第 10 章对经典的算法——动态规划循序渐进地进行了细致的剖析,并介绍了一种空间优化的方法——滚动数组。 第 11 章讲的是滑动窗口。这种算法使用两个指针界定窗口左右边界,并统计窗口内的信息。当窗口发生滑动时,仅考虑窗口变化的部分,最大化利用已有的运算结果,从而降低时间复杂度。 第 12 章讲的是博弈问题。这一类问题出现的频率同样很高,仅与石子游戏相关的问题就在力扣(LeetCode)中出现了很多次。博弈问题虽然没有固定的思维方法,但也有一些规律可循。 第 13 章讲股票问题,其属于动态规划的子问题。建议读者在看完第 10 章动态规划之后再来阅读本章内容。 第 14 章和 15 章分别讲的是分治法和贪心法。这两个专题和动态规划类似,难度上限都很高,也不容易掌握。这两章从几个具体的例子入手, 帮助读者厘清贪心法和分治法问题的适用场景及解题策略。 第 16 章则是对第 5 章内容的扩展,介绍了另一种常见的搜索算法——回溯法。回溯法是什么?如何利用回溯法来解决具体的算法问题?回溯法的代码如何书写?回溯程序如何优化?本章将告诉你答案。 第 17 章则是作者精选的几个有意思的题目,在这里分享给读者。 第 18 章是一些解题模板,是对前面内容的提炼,建议读者在阅读相应专题之后再来查看本章相应的模板。模板的意义在于提高解题速度,降低错误率,而不是被用来生搬硬套的,这一点读者要格外注意。 第 19 章提供了尽可能多的解法来拓展读者的思维,这与前面 18 章的做法不同。为了不影响阅读,前面的 18 章内容都是对单一的知识点进行讲解,同时为了和其内容匹配,有时也会放弃最优解而选择与本章内容匹配的解法。 第 20 章分享了一些作者认为非常不错的解题技巧。 新书就先秀到这里。 接下来就这本书在这里回答几个大家比较关心的问题。让我们进入 Q&A 环节吧! Q&A Q1:这本书是什么编程语言? A1: Python。不过我提供了配套网站。全部代码都提供了 Java,CPP,Python 三种代码,因此如果你不熟悉 Python,而只需要 Java 或者 CPP 也完全没有问题。另外部分题目还提供了 JS Code,后面我们也可能会根据读者的反馈增加其他语言。 本书配套网站地址:https://leetcode-solution.cn/book Q2:书的内容是 Github 仓库和公众号的内容么? A2:很多读者都是从我的 Github 过来的, Github 也提供了电子书版本。 Github 地址:https://github.com/azl397985856/leetcode 那么 Github 的电子书中的内容会和这本书重叠么? 答案是几乎没有任何重叠。本书内容几乎都是不曾公开的全新内容,大家不用担心买了一本开源书。 Q3:这本书适合小白么? A3:这本书就是为想科学高效刷题的人量身打造的。阅读这本书适合懂至少一门编程语言,能将思路转化为代码,并且了解常见数据结构人。如果你是这样的人,就可以买来阅读。 Q4:这本书上限高么?我想提高一下自己。 A4:这本书上限不高,难度基本上覆盖力扣中的简单,中等以及部分困难。也就是说看懂这本书可以解决大部分力扣题目。这种程度不足以应付算法比赛的,但是应付面试足以。 如果你还有什么问题,都可以给我留言。我会尽可能地回答大家~ ღ( ´・ᴗ・` )比心 粉丝福利五折优惠目前还是预售阶段,我给公众号的读者争取了一波福利,大家可以以更优惠的价格进行购买。 新书定价是 99 元,但是我帮大家争取到了五折优惠, 49.5 元就可以拿下了,另外前一万本书会附赠力扣的会员优惠券。 想入手的朋友现在入手非常划算,扫描下方二维码(或者使用这个链接 https://u.jd.com/gKbUGbR)就可以购买了。 免费送书另外力扣加加在粉丝中抽三位免费送!后续会不定期在这个号上抽奖送书,大家可以关注一下! 参与方式请仔细阅读哦: 在我的公众号力扣加加后台,发送【抽奖】这 2 个字(不加任何符号 or 表情),即可参与抽奖。 点击关注上方账号,回复【抽奖】即可参与 提醒下哦:是在公众号后台哦,不是在这评论区、不是这号后台发消息、也不是发微信哈。 9 月 9 日 12:00 自动开奖,开奖后微信会自动通知。 抽奖由第三方平台开奖,抽奖、兑奖过程中有任何疑问请添加小秘书微信(微信号:wxid_d5q3rgueie4r22) 另外公众号脑洞前端也在做同样的抽奖活动,在脑洞前端后台回复抽奖同样可以参与抽奖哦。两个号都参与,中奖率翻倍!","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/categories/书/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/tags/书/"}]},{"title":"入职一个月,远程办公感受如何?","slug":"wfh","date":"2021-09-03T16:00:00.000Z","updated":"2023-01-07T12:34:34.657Z","comments":true,"path":"2021/09/04/wfh/","link":"","permalink":"https://lucifer.ren/blog/2021/09/04/wfh/","excerpt":"上一次给大家讲了我现在已经全职远程办公(简称 wfh),算起来有超过一个月的时间了。很多同学对此比较感兴趣,我们就来聊聊我最近工作如何。","text":"上一次给大家讲了我现在已经全职远程办公(简称 wfh),算起来有超过一个月的时间了。很多同学对此比较感兴趣,我们就来聊聊我最近工作如何。 未经审视的人生不值得过。我认为我属于那种经历逆境比较少的人,印象中最大的逆境似乎就是科目三考了好几次才过?但很多时候,反推力才是人类前进的最直接、最有效的动力。因此我的决断就是自己尝试一下不同的选择,拒绝一成不变的老年生活。关于这些,我尝试了很多东西,其中就包括了远程办公,这时候有时间再给大家细聊。 我经常会思考自己想要什么样的生活。 那当然是幸福的生活啦。 我想了一下,如果要实现幸福,最主要的有这两个方面: 有充足的物质保障。可以做自己喜欢的事情,而不必为了某些原因做一些自己不喜欢的东西 受人尊敬 似乎每一个都很难。不过这才有意义嘛。 为了做到第一点,可以从两方面入手,一个是提高收入,一个是降低开支。也就是开源节流。提高收入的话最简单的就是增加自己的能力提高工资,拓宽收入的来源,还有就是提高理财能力。而节流的话就是控制自己的欲望,目前我的方法主要是延迟满足,每个月给自己设定一个开支计划,并且只有完成每个月的任务的百分之多少才能去花。 另外一点就是可以考虑去生活成本相对较低的城市,比如我的老家。我认为选择远程办公至少在节流上做到了。另外长期来看,远程办公可以对标更高的工资标准,因此长期上开源也做到了。另外选择的公司也相对节奏慢,这样我可以做一些其他副业拓宽自己的收入渠道。比如我会去做一些培训,写一些教程,书籍之类的。 为了做到第二点,可以提高自己的影响力,并真心地为他人着想。积极乐观面试生活,不断倾听和观察,必要时给予帮助。这其实并不是我的强项,只不过在慢慢改变罢了,这我就不展开说了。 现在的工作很自由回到开头的话题:聊聊我最近工作如何。 简单来说这一段时间的 wfh 给我的最大感受就是自由。我可以自由安排自己的工作时间。不仅仅是我,我的同事都是这样的一个状态。 以我为例,我就可以经常在“大家的工作时间”做一些和工作无关的事情(摸鱼?)。比如健身环大冒险,看看电影,逗逗宠物等等。 很多人问我:“wfh 会不会工作和生活没有界限,导致 007 的出现?” 我的回答是这取决于公司文化,如果是某些公司确实很有可能,而一些人性化的公司是大可不必担心这样的事情发生。 大家更关注的是你的成果,而不是关心你的过程。只要你不是很过分,基本就不会有问题。比如每次线上找你都需要好久才能响应,这就比较过分了。 如果暂时不能响应,只需要在工作软件上设置一个“离开状态”即可,最好加一个时间。比如“下午三点前勿扰” 另外外企假期也会有很多,年假和病假每年都有十多天,老员工会更多。你请个假出去玩回来也完全不影响工作。其他各种福利更不用说了。 在这里你只需要思考如何把工作做好就行了。大家不关心你是在家思考的方案还是在公园思考的方案。如果你效率高很快就完成了任务,那就可以自由安排你的时间。不需要像很多公司下班了不肯走,打死不做第一个走的人,假装工作实则摸鱼。 这种真实的感觉真好! 工作也很有挑战性目前在做的是公司内部开发引擎,具体细节就不透露了。总之这算是一个很有挑战性,需要有一定的经验和跨专业能力。经常要做一些技术调研以及技术储备。另外也会时不时和业界的一些开源大佬(具体就不透露了,非常著名的那种就是了)探讨一些技术问题。 内推你如果也想要这样的远程办公(wfh),工作和生活平衡(work life balance) 的工作,可以找我内推,大家加我的微信就好(可以提供免费面试咨询哦) 加好友请注明:内推","categories":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/categories/成长经历/"}],"tags":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/tags/成长经历/"}]},{"title":"github 又出新功能,布局云端 vscode?","slug":"vscode-dev-codespaces","date":"2021-09-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.942Z","comments":true,"path":"2021/09/02/vscode-dev-codespaces/","link":"","permalink":"https://lucifer.ren/blog/2021/09/02/vscode-dev-codespaces/","excerpt":"vscode 和 github 是微软的两大开源产品,二者在业界的影响力都是巨大的。 近日 Github 新出了一个功能,用户可以通过将 url 中的 .com 替换为 .dev 来直接打开在线版的 vscode 编辑器。","text":"vscode 和 github 是微软的两大开源产品,二者在业界的影响力都是巨大的。 近日 Github 新出了一个功能,用户可以通过将 url 中的 .com 替换为 .dev 来直接打开在线版的 vscode 编辑器。 如果大家听过之前出现的一个叫做 github1s 的第三方工具的话,那就很容易理解这个功能。和 github1s 的一样,用户通过修改 url 中的少量字符就可以直接在在线版的 vscode 中打开仓库。 比如 github 地址为: https://github.com/azl397985856/leetcode 那么你就可以通过访问如下地址直接在云端 ide 打开: https://github.dev/azl397985856/leetcode url 长度还是相同的,甚至还有一点整齐美 还有一种更简单的方法:只要你在任何 GitHub Repo 页面上按下 .键 会自动跳转到 github.dev 的网站。 笔者特意对 github1s 和 github.dev 进行了对比,结果如下图: 可以发现二者不管是 UI 还是功能都是非常类似的。大的不同点就是 github.dev 集成了 codespaces,这也是微软的下一个主战场。不难想象,将来不仅是云端 vscode 还是本地的 vscode 都会向 codespaces 发力。 codespaces 允许你使用云端的资源,而不仅仅是作为一个编辑器,整体感觉类似 gitpod,不过功能会更多。 gitpod 也是一款非常不错的产品,推荐大家使用 codespace 定位更高端,比如可以像 github actions 那样定制镜像,环境等。GitHub Codespaces 支持 Visual Studio Code 和新式 Web 浏览器。 借助在云端的开发,可无缝切换使用不同的工具,随时随地贡献代码。 想直接体验?戳这里:https://visualstudio.microsoft.com/zh-hans/services/github-codespaces/ 如下是 codespaces 的架构图: 可以看出其主要由两部分组成:一部分是编辑器,另外一部分是云端的虚拟机,而几乎所有的功能都可以在云端完成,比如 AI 提供的自动补全,根据注释写代码等功能。更多关于 codespaces 的介绍参考:https://docs.github.com/en/codespaces/overview#joining-the-beta 不过 codespaces 目前还没有大规模推广,期待这 codespaces 可以尽快推广,给广大像我一样的开发者带来便利。","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"VSCODE","slug":"工具/VSCODE","permalink":"https://lucifer.ren/blog/categories/工具/VSCODE/"}],"tags":[{"name":"VSCODE","slug":"VSCODE","permalink":"https://lucifer.ren/blog/tags/VSCODE/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"如何搞定不同公司的算法面试?(早早聊分享文字版)","slug":"zaozaoliao","date":"2021-09-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.973Z","comments":true,"path":"2021/09/02/zaozaoliao/","link":"","permalink":"https://lucifer.ren/blog/2021/09/02/zaozaoliao/","excerpt":"前几天西法参加了《前端早早聊》第 24 界的分享。我的分享主题是《如何搞定不同公司的算法面试?》 这是这次分享的文字版,供大家查看。如果大家需要分享的原版 ppt,也可以到我的公众号《脑洞前端》中回复早早聊获取。","text":"前几天西法参加了《前端早早聊》第 24 界的分享。我的分享主题是《如何搞定不同公司的算法面试?》 这是这次分享的文字版,供大家查看。如果大家需要分享的原版 ppt,也可以到我的公众号《脑洞前端》中回复早早聊获取。 正文本次分享我们会按照如下顺序进行: 第一部分是前端算法面试都考什么?(其实这部分内容不同公司差异不大,我列举的是总体公司的大部分面试考察点,差不多可以覆盖 90% ) 第二部分是不同公司算法面试有何区别?(由于不同公司的算法难度和侧重不尽相同,有时候甚至是天差地别,因此我们有必要对不同类型公司的算法面试进行区分) 第三部分是如何准备算法面试?(知道了算法面试的考察内容和不同公司的面试特点,就可以根据自己心仪的公司对号入座了。那学习的顺序是什么,重点又是什么?这部分告诉你) 第四部分是中等难度算法题实战(通过一个具体的例子,带大家看一下,解决一道具体的算法题背后的思路究竟是如何的,我是如何和已有的知识建立建立的,希望能给大家一点点启发) 最后一部分是我的工具与方法论(这部分是我一直在使用的一些工具和方法论,个人觉得不错,这里分享给大家) 为什么是我? 我从 17 年开始研究算法面试题,到现在已经接近 4 年了。可以说经验算是比较丰富的了。 另外我本人刷了大概 2000 道左右的算法题。就大家比较熟悉的力扣平台而言,我几乎将他们所有的树,链表,堆,二分,动态规划等刷完了。除此之外,我也会刷一些其他 OJ 平台,比如牛客,Binary-Search 等等。在刷题过程中总结了大量的经验和套路。 除此之外,我也经常在力扣上分享自己的刷题心得以及题解。当力扣刚推出等级系统的时候,我就已经是满级作者了。除了在力扣上分享经验,我还在其他渠道进行分享,比如 公众号和 Github 。Github 上关于刷题的项目已经超过 4w star 了,非常感谢大家的认可。 另外在算法面试这个点上,我已经做了三期的算法面试培训了,累计有 1000 多人参加,很多人拿到大厂 offer 也会过来还愿。 最后一点我觉得最重要。 由于我本人是前端,也参加了无数的面试,包括面试别人和被别人面试。从面试官的角度我能知道广大求职者的面试状态,从求职者的角度,我能知道各个公司的考察形式和内容。这一来一回,一正一反,使得我对算法面试更加了解。当然除了我本人亲自面试的经验,我还经常和其他公司的朋友以及群友交流面试题目,他们提供面试真题,我提供免费的算法题目指导。通过这样的过程,我积累了相当可观数目的算法面试真题。使得我对很多公司的面试情况更加了解。 一点点说明为了方便描述,我将公司分成四个梯队。 T1:世界头部公司,比如 Google, Amazon, Facebook 等 T2:国内头部公司,比如 BAT ,头条 ,美团等 T3:其他中大型公司,比如滴滴,有赞等 T4:小微公司(这类不在我们的论述范围,因此它们几乎没有算法题) 右边是一个金字塔图,表示的意思是 T4 公司是最多的,其次是 T3,T2,T1。 前端算法面试都考什么?接下来开始我们本次分享的第一个主要内容 - 《前端算法面试都考什么?》 有的人可能会问,我的这个前端算法面试考点数据来源是哪?有事实依据么?这里我说一句。 这里的考察知识点的数据来源就是我前面做自我介绍时候提到的“亲身经历+好友反馈“。 我从大的方向,将考察点分成了两类。第一类是数据结构与算法基础知识。 数据结构与算法基础知识这部分,我又做了一个小小的细分,将其分为两个小点。 各种数据结构的特性与基本操作。比如数组,队列,栈,链表,树,图等。对于前端,尤其需要掌握的栈和树。这是因为前端使用到栈和树的地方实在太多了。比如 DOM 树(虚拟 DOM 树),树形选择器,浏览器执行栈,浏览器历史记录栈等等。另外题目上围绕栈和树的题目也相当多,从最简单直接的树形数据结构转化复杂一点的数据结构解析,基本就是栈+DFS 都可以搞定,而做 DFS 的时候通常都围绕树型结构进行递归求解的。所以这两个数据结构对前端非常重要。面试频率非常高,这里我敲一下重点,希望大家认真对待这部分。 复杂度分析。复杂度分析是学习数据结构与算法的基础,也是核心。我建议大家一定要先学会分析算法的复杂度再去学习具体算法。这部分内容包括时间复杂度和空间复杂度分析,其中每一种复杂度都有最好,最坏以及均摊复杂度。而一般我们使用最坏复杂度比较多,而我写的新书《lucifer 的算法之路》中的全部复杂度也全部都是最坏复杂度,而且这部分内容是在全书的最开始,可见其重要性。另外分析复杂度除了分析迭代,也要会分析递归,递归栈的空间开销经常被大家所忽略,这点值得引起大家的注意。 OK,以上是第一个考点:《数据结构与算法基础知识》,接下来我们来看第二个考点。 算法思想(90%考点)第二类是算法思想,需要大家在掌握了上面内容基础上再来学习。 这里我列举了五个考点,它们分别是: 搜索(BFS,DFS,回溯,二分等) 暴力优化(双指针,单调栈,前缀和等) 动态规划 分治 贪心 以上内容覆盖了前端算法面试的 90% 考点。一些比较“冷门”的知识比如二分图,跳表,蓄水池抽样算法等考察频率很低,我就没有列出来。 我希望大家集中精力将重心投入到这 90% 考点中。其他知识点大家可以根据自己的情况学习(比如你想进 T1 或者其他学的差不多了想精进)。 不同公司算法面试有何区别?接下来,我们来看下本次分享的核心主题 - 《不同公司算法面试有何区别?》 首先我声明一点:即使是同样的公司,同样的岗位不同时期题型和难度都不一定相同。因此以下内容仅表示历史数据,不表示之后的情况。如果大家想获取第一手的不同公司算法面试情况,可以在本次演讲结束后关注我的个人公众号。 前面我们已经对不同公司进行了一个简单的分类,那么这里就直接根据前面的分类进行逐一讲解,比较一下不同公司的算法面试有什么不同。 T1难度和题型首先我们来看下 T1 级别的公司。 T1 级别的公司算法面试题难度几乎没有上限,可能会超出普通 OJ 平台的难度,且题目非常灵活,他们经常会时不时原创题库, 因此碰不到原题或者类似题是很正常的。 还有一点有必要和大家强调从一下。 很多人觉得社招算法难度比校招要大,这是不对的。 一般而言,校招会更看重基础,这是因为校招的人通常项目经验都比较欠缺。而算法就是这些基础中非常重要的一项。社招的话,国内公司还是以项目经验和解决问题能力为主,对于算法的要求通常比校招的要求低。因此大家不要觉得校招的算法都这么难,那我社招岂不是更难? 不要有这样的想法。 T1 公司题型也更加广泛,除了上面列举的 90% 考点,还会有一些图论,前缀树,KMP 等其他公司不太会问到的“冷门”知识。虽然 T1 公司很多题目也可以在网上找到原题。不过区别于 T2,T3,他们的出题形式会更加有关联,循序进阶,且会伴随着一些 follow up。 比如第一道题是两数和,第二道题可能就是三数和,甚至是 k 数和。另外还会提出一些进阶要求,比如要求不借助额外空间等。 而且 T1 通常会定期扩充原创题库,这是其他 T2, T3 级别公司(尤其是 T3)很难做到的。 这些题库的题目经过一段时间也会逐步扩散到网上,进而又变成了大家所谓的“网上的原题”。 面试形式T1 公司之前很多是白板面试,即让你在一块白板上书写。白板书写对你的调试能力, api 了解能力,甚至代码组织能力都有很高的要求。不过最近 T1 公司基本都改成了视频面试的形式,而不是传统的 onsite,因此白板也变成了云编辑器。 T1 公司通常要求思路正确,代码 bug free(有时候也会要求你不经过提示的情况下一次通过),一般也对复杂度有一定要求。不仅需要你能够正确地分析出算法的复杂度,还需要对算法进行改进以达到尽可能优的复杂度。 前面我已经提到了,复杂度分析是基础中的基础,这部分大家注意一下。 面试完成后会有一份完整的面试报告,记录了你每一道题的回答情况甚至用时情况。面试委员会会根据这份面试报告来决定面试是否通过以具体的定级之类的。 一个典型的 t1 公司算法面试流程接下来,我们来看下《一个典型的 t1 公司算法面试流程》。 面试官:我把题目贴到了屏幕左侧,你可以在右侧编辑器中进行作答了。我现在给你放第一题。 求职者:OK。(看了一会)题目的意思是不是。。。(复述题目,确保理解正确,询问限制条件,比如数据规模) 面试官:(回答求职者的问题) 求职者:我的思路是。。。,这种做法的时间复杂度为。。。空间复杂度为 。。。 (如果面试官觉得没有问题,你就可以开始写代码了。写代码的时候要边写边交流) 面试官:你可以对你的算法进行优化么?(如果求职者给的算法不是最优解) (面试官会在求职者做题的时候记录求职者的回答情况以及每一部分所花费的时间) 最后将几道题(一般是 2 - 4 道)的回答情况汇总到面试报告中 如上是我模拟的一个典型的教科书般的面试流程,大家可以参考这位求职者的回答。 其中核心点就在于: 复述题目,确保理解正确,询问限制条件,比如数据规模 先讲思路,再写代码。讲思路后记得用复杂度对算法进行评估。 如何可能,继续优化你的代码。 T2难度和题型T2 公司难度基本不会超过中等,且题目比较固定(就是一些 OJ 原题或者稍微改改)。以我的经历来说,我面试了很多国内顶尖公司,比如头条,阿里,腾讯等。只有腾讯遇到了 hard,还不让你用 hard 解法就能过关(用了 hard 解法反而可能不够,这个和面试官有关,建议你先用最笨的解法,有必要再优化)。 另外从其他朋友的反馈来看,前端岗位算法面试难度为困难的题目很少,且题目比较固定,比如 LRU,分组反转链表等。(可能是公司题库就那几道困难题目?) 题型上来看,前面提到的算法思想基本都可以完全覆盖,很少会有一些刁钻的题目。并且面试的题目大部分都是可以在网上找到原题或者类似的题。这也是 T2 和 T1 的一个显著区别。 这里要提一下校招的笔试题。校招的笔试题大家都觉得是新题, 其实不然,以我这么久和校招题目打交道的过程来看。题目基本都是换皮,而且是那种换了个说法而已的换皮。 面试形式T2 公司通常是采用云编辑器和本地编辑器的考察形式。比如头条大多数使用的是牛客的编辑器,阿里有些用的是内部开发的伯乐系统,这两个公司通常需要你在云端编辑器完成。再比如腾讯就使用的是腾讯文档,但是允许你在自己的本地编辑器中写代码。这一点还是非常人性化的。 T2 公司对算法题目的要求通常是思路正确,bug free。(不一定一次通过,通常给你几次机会,具体几次视题目难度而定。比如简单题可能就不给你错的机会,困难题允许你错一次或者可以给提示这样) 面试完成后会有一份面试报告,这部分面试报告通常不会像 T1 公司的那样完整,一般只是记录了你最终完成的代码。 需要注意的是你的每一次面试都会记录下来,之后当你再次参加他们公司的面试,有时候也会参考之前的面试记录(通常只会参考最近半年或者一年的面试记录,毕竟每个人都会成长的)。 一个典型的 t2 公司算法面试流程t2 公司面试流程和 t1 公司有很大的相似性。 虽然考察的形式是差不多的,也就是说非常形似。不过 t2 公司的算法面试题的难度和数量一般会比 t1 的低一些,t1 公司的面试报告也会更加详细一些。 T3难度和题型T3 级别的公司难度绝大多数不会超过中等,且基本都是网上非常常见的面试题。如果你想通过这些公司的算法面试,你可以选择完全放弃困难题目。没错,网上常见的困难题也没有必要做了。并且基本上只需要刷我提到的算法思想部分,然后将网上的面经遇到的题目练习一下就差不多够了。 考察形式考察形式上也通过是让你说思路或者写出伪代码。通常要求思路正确,由于通常是说思路和伪代码,因此也不要求 bug free。 T3 公司面试完成后会有一份简短的面试反馈,一般是对面试人能力的大体概括。这个面试报告可能是在招聘系统中直接给出,也可能是口头的。 一个典型的 t3 公司算法面试流程t3 公司流程就和 t1 ,t2 有很大差别了。它们有时候根本就没有算法题。如果有的话,通常也是一个加分项。面试的难度也会更低,并且碰上原题的概率会很大,毕竟不是所有公司都有自己的题库的。就连 t2 公司的题库也使用了一些外部题目。 还有一点我发现的一个有意思的点是: T3 公司通常你做出来就行了,不太会让你去优化。比如,我有一次参加一个 T3 公司的面试,它们给我出了一个括号匹配(力扣简单难度)。我先用栈完成了,心想面试官肯定会让我优化成 $O(1)$ 空间。结果是没有,并且面试官对我很满意,觉得我写的很快。 实际上这不是巧合,我的经验告诉我。如果你的算法性能不是差到离谱,只要能做出来就行。什么叫差到离谱?比如两数和,你用 $O(n^2)$ 做出来,这就是离谱。 最后我给一个汇总性的对比表格方便大家查看。 大家可以通过这个表感受不同类型公司算法面试的区别。 我该如何准备算法面试?前面已经对各种不同的公司从多个维度进行了详细对比。接下来给大家一点实操建议。即如何准备算法面试。 这部分我总结了四点,在这里分享给大家。 1. 先学习数据结构与算法基础知识。第一个我要给大家的建议是:先学习数据结构与算法基础知识。 我发现很多同学喜欢一下子就钻到刷题中去,他们甚至连复杂度分析以及各种数据结构的特点都还不清楚。 这是万万不可取的做法。一定要注意,做题只是巩固知识的手段,如果你根本没有知识,或者知识不足,那么做题是几乎没有任何效果的。 具体的基础知识内容可以参考我后面要讲的学习路线图。 01.数组,栈,队列 02.链表 03.树 04.哈希表 05.双指针 06.图(加餐) 模拟和枚举 前缀和与差分 如何衡量算法的性能 各种排序 2. 找到你心仪的公司,通过多种渠道了解其出题风格和难度挑选出你心仪的几家公司,看看他们属于 T 几,先有一些大体的映象和心理准备。接下来,你需要从多个渠道了解它们算法题目的类型和难度。我提供几个渠道给大家: 牛客网讨论区 力扣讨论区 一亩三分地 社群和好友 如果你愿意的话,也可以直接来咨询我。我会尽可能帮助你的 之后你要做的就是根据上面步骤总结的信息针对性刷题,这样可以使你的效率最大化。 3. 不依赖自己顺手的编辑器平时练习的过程中大家可能习惯了自己顺手的 IDE。比如自己本地的 IDE 或者平台提供的 IDE。 笔试的时候很多情况下是没有条件让你这么做的,所以大家在面试前需要自己适应一下脱离 IDE 写代码。 一个不顺手的 IDE 确实会让你的效率大大降低,比如没有智能提示,没有语法高亮,缺乏良好的格式化,很多快捷键不支持等。当你没有这些良好的条件的时候,会加大你面试的紧张情绪,进而影响你的发挥。因此不依赖你自己顺手的编辑器是一个相当重要的点。 4. 重点突破搜索类和动态规划最后一个,我觉得或许是对大家最有用的一个建议了,这部分再次划重点。(这是本次分享的第二个重点了。还记得第一个重点么?第一个重点是两个数据结构,栈和树) 回忆一下前面我提到的五种占比 90%的算法思想: 搜索(BFS,DFS,回溯,二分等) 暴力优化(双指针,单调栈,前缀和等) 动态规划 分治 贪心 前端算法面试 90% 的题目都不超过这几项,尤其是非 T1 公司。 如果要从从这五项再说重点的话,我推荐大家刷: 搜索 动态规划 而搜索的话,又以树为主。其中的原因我在前面也进行了分析,这里就不赘述了。 至于动态规划,大家也应该着重准备。比如很多公司特别喜欢考察的背包问题,爬楼梯问题,这些都是动态规划。悄悄告诉你,这些问题我本人也都在实际面试中遇到过,可见面试频率还是蛮高的,值得大家重点投入。 而如果你面试国外的公司的话,除了谷歌据说考察动态规划的很少。 举个栗子接下来上干货,带大家来解决一道算法题。 我们以力扣上的 402. 移掉 K 位数字 为例,讲解一下如何思考一道题目的解法。 之所以要选这道题是因为: 难度适中。 没有很难,但也绝对不简单。 这题很经典,搞懂这道题你可以 AC 好多道题,我在后面的 PPT 给大家都列好了,大家等会可以看下。另外这道题的题解让我收获了几千的点赞和收藏,可见质量还是不错的。 我们来看一下这道题。 题目描述123456789101112131415161718192021给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。注意:num 的长度小于 10002 且 ≥ k。num 不会包含任何前导零。示例 1 :输入: num = "1432219", k = 3输出: "1219"解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。示例 2 :输入: num = "10200", k = 1输出: "200"解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。示例 3 :输入: num = "10", k = 2输出: "0"解释: 从原数字移除所有的数字,剩余为空就是0。 前置知识 数学 栈 思路这道题让我们从一个字符串数字中删除 k 个数字,使得剩下的数最小。也就说,我们要保持原来的数字的相对位置不变。 以题目中的 num = 1432219, k = 3 为例,我们需要返回一个长度为 4 的字符串,问题在于: 我们怎么才能求出这四个位置依次是什么呢? (图 1) 暴力法的话,我们需要枚举C_n^(n - k) 种序列(其中 n 为数字长度),并逐个比较最大。这个时间复杂度是指数级别的,必须进行优化。 一个思路是: 从左到右遍历 对于每一个遍历到的元素,我们决定是丢弃还是保留 问题的关键是:我们怎么知道,一个元素是应该保留还是丢弃呢? 这里有一个前置知识:对于两个数 123a456 和 123b456,如果 a > b, 那么数字 123a456 大于 数字 123b456,否则数字 123a456 小于等于数字 123b456。也就说,两个相同位数的数字大小关系取决于第一个不同的数的大小。 因此我们的思路就是: 从左到右遍历 对于遍历到的元素,我们选择保留。 但是我们可以选择性丢弃前面相邻的元素。 丢弃与否的依据如上面的前置知识中阐述中的方法。 以题目中的 num = 1432219, k = 3 为例的图解过程如下: (图 2) 由于没有左侧相邻元素,因此没办法丢弃。 (图 3) 由于 4 比左侧相邻的 1 大。如果选择丢弃左侧的 1,那么会使得剩下的数字更大(开头的数从 1 变成了 4)。因此我们仍然选择不丢弃。 (图 4) 由于 3 比左侧相邻的 4 小。 如果选择丢弃左侧的 4,那么会使得剩下的数字更小(开头的数从 4 变成了 3)。因此我们选择丢弃。 那我们要继续丢弃 1 么?不用,因为这样会造成数字更大(从 1xxx 变成了 3xxx)。 。。。 后面的思路类似,我就不继续分析啦。 然而需要注意的是,如果给定的数字是一个单调递增的数字,那么我们的算法会永远选择不丢弃。这个题目中要求的,我们要永远确保丢弃 k 个矛盾。 一个简单的思路就是: 每次丢弃一次,k 减去 1。当 k 减到 0 ,我们可以提前终止遍历。 而当遍历完成,如果 k 仍然大于 0。不妨假设最终还剩下 x 个需要丢弃,那么我们需要选择删除末尾 x 个元素。 上面的思路可行,但是稍显复杂。 (图 5) 我们需要把思路逆转过来。刚才我的关注点一直是丢弃,题目要求我们丢弃 k 个。反过来说,不就是让我们保留 $n - k$ 个元素么?其中 n 为数字长度。 那么我们只需要按照上面的方法遍历完成之后,再截取前n - k个元素即可。 按照上面的思路,我们来选择数据结构。由于我们需要保留和丢弃相邻的元素,因此使用栈这种在一端进行添加和删除的数据结构是再合适不过了,我们来看下代码实现。 代码代码支持: Python3, JS Pythopn3 Code: 1234567891011class Solution(object): def removeKdigits(self, num, k): stack = [] remain = len(num) - k for digit in num: while k and stack and stack[-1] > digit: stack.pop() k -= 1 stack.append(digit) # 为了类似 \"10200\" /n 这种 case 过不了,需要移除前导0。 另外为了防止将 0 全部移除,我们需要判空 return ''.join(stack[:remain]).lstrip('0') or '0' JS Code: 123456789101112var removeKdigits = function (num, k) { const stack = []; const remain = num.length - k; for (const digit of num) { while (k && stack && stack[stack.length - 1] > digit) { stack.pop(); k -= 1; } stack.push(digit); } return stack.slice(0, remain).join(\"\").replace(/^0+/, \"\") || \"0\";}; 复杂度分析 令 n 为数字长度。 时间复杂度:虽然内层还有一个 while 循环,但是由于每个数字最多仅会入栈出栈一次,因此时间复杂度仍然为 $O(n)$。 空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 $O(n)$。 提示: 如果题目改成求删除 k 个字符之后的最大数,我们只需要将 stack[-1] > digit 中的大于号改成小于号即可。 相关题目大家做完这个题,就可以 AC 下面几个题啦~ 去除重复字母(困难) 拼接最大数(困难) 不同字符的最小子序列(中等) 我的工具与方法论最后是我的工具和方法论,主要介绍一下我学习路上的一些好用的工具,以及我是如何刷题的经验分享。 学习路线这里我给出一个适合前端同学的一个算法学习路线。 复杂度分析:如何衡量算法的性能? 基础的数据结构:线性数据结构 (数组,链表,栈,队列,哈希表) 基础的数据结构:非线性数据结构 (树 和 图) 排序算法:经典排序算法 递归: 一种”高级“的思维技巧 暴力搜索篇: 回溯,BFS, DFS 暴力优化篇:剪枝,滑动窗口,双指针,单调栈 高级搜索篇:二分法与位运算 动态规划篇:记忆化搜索与动态规划 分治:大事化小,小事化了 贪心:简单高效但”不靠谱“的算法 逆向思考 大家可以根据自己的实际情况从不同阶段开始。如果你是刚开始接触算法,我建议你从头开始跟着这个学习路线坚持下去,我相信一定会有突然间成长的一天。 多做总结,多写题解大家在学习的过程这一定要及时总结和复习,而写题解就是总结和复习很好的一个途径。我本人写的题解数量已经有几百篇了。通过写题解能够加深我对算法的理解能力。下一次碰到相同或者类似的题目,则可以从大脑中提取更多信息。 使用 anki 管理知识前面提到了要及时复习。而 anki 就是一个帮助你管理复习计划的软件。它是一个能够根据遗忘周期来自动制定复习计划的软件。我在前期学习算法的时候制作了一部分的复习卡片,它们帮助了我度过了学习算法最艰难的入门期。 等到你刷了足够数量的题目了,就不需要它们了。但是在前期,它确实可以有效地帮助到你。 刷题插件 leetcode-cheatsheet最后提一下我自己制作的一个刷题插件,现在有几千个人在用了。通过它可以帮助你更有效率刷题和写题解。 他提供了学习路线可以帮助你理清不同专题的基本题型和套路。 他提供了代码模板可以帮助你快速写出 bug free 的代码。 他提供了数据结构可视化和题解模板则可以帮你快速写出格式良好的题解。 他提供了复杂度速查, 这是一个帮助你快速判断算法是否可行的参考工具。 推荐一本好书给大家最后推荐一本书大家。《算法第四版》- 一本非常适合初中级选手的算法学习书籍。 这本书给了我很多帮助,少走了一些弯路。这里推荐给大家,希望也可以帮助到正在学习算法的你。这本书我买了好多本,不仅自己看,还送给了我的朋友和学员。 这本书只是让你入门,以及学习一些经典算法。仅靠这个去参加比赛或者 T1 面试是不够的。大家还需要准备一些别的资料。不过我觉得入门是最难的,相信你入门之后就可以自己去挑选学习资料了。比如 oi wiki。 关注我我本人最近也在写一个关于前端算法面试的专栏,目前正在寻找合作平台,如果你是平台方可以微信联系我。如果你对内容感兴趣可以订阅我的公众号《脑洞前端》,第一时间消息会在公众号同步大家。 另外我的 《91 天学算法》第四期已经快要开始了,现在还没有开始报名哦,活动介绍:https://leetcode-solution.cn/91。开始报名时间大概是 2021.5.1 - 2021.6.1 之间。具体时间请关注我的公众号《力扣加加》,第一时间获取最新消息。 以上就是本次分享的全部内容了,大家有什么问题的话都可以提,我会尽量回答。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"前端","slug":"算法/前端","permalink":"https://lucifer.ren/blog/categories/算法/前端/"},{"name":"面试","slug":"算法/前端/面试","permalink":"https://lucifer.ren/blog/categories/算法/前端/面试/"}],"tags":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第五期)","slug":"91algo-5","date":"2021-08-20T16:00:00.000Z","updated":"2023-01-07T13:56:10.279Z","comments":true,"path":"2021/08/21/91algo-5/","link":"","permalink":"https://lucifer.ren/blog/2021/08/21/91algo-5/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 第五期本来想和力扣官方合作一起搞,这样打卡就可以无缝衔接,如果你有力扣会员甚至可以免费参加。可是力扣官方给的感觉是:快了,已经在新建文件夹了。 就好像我虽然还是 行号 0, 列号 0,字数 0,但是却和催更的读者说快写好了一样。 因此第五期我们就先开始吧! ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 第五期本来想和力扣官方合作一起搞,这样打卡就可以无缝衔接,如果你有力扣会员甚至可以免费参加。可是力扣官方给的感觉是:快了,已经在新建文件夹了。 就好像我虽然还是 行号 0, 列号 0,字数 0,但是却和催更的读者说快写好了一样。 因此第五期我们就先开始吧! ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第五期和往期的不同。 移除了进阶篇的 - 高频面试题,将自习篇内容改为非自习内容,也就是基础篇的枚举篇和图 改为非自习内容。 自习指的是不给每日一题的做题时间,需要自己找时间学习和练习。因此这次改动意味着除了先导篇,其他全部内容都变为非自习。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 网站上直接打卡,不用跳转到 Github 打卡,在一个网站完成所有操作。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-09-10 至 2021-12-10 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 先导篇 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 五期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 本期有共五位讲师,每个专题由一位具体的讲师负责,大家有不会的问题可以进行提问。如果讲师来不及回答,大家可以在仓库中提 issue。 关于每一个专题的负责讲义,我们会在 91 官网中的“讲师”模块给出,如上图所示。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡一张哦 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 分享海报满 3 天 已经付费 则可联系 lucifer,并告知你的 Github 登录名即可。 分享返现如果你没有抢到前 50 名免费的学习机会也不要气馁。我们贴心地为大家搞了分享返现活动,手慢照样可以免费参加哦~ 活动规则: 发送宣传海报到你的朋友圈不屏蔽好友保留三天,三天之后加 lucifer 微信好友(微信号:DevelopeEngineer)进行验证,验证通过全额返现。 不到三天就没必要联系我验证了,必须不屏蔽好友满三天才可以验证。 朋友圈文案统一为: 91 天,遇见更好的自己。发送本海报到朋友圈,不屏蔽好友保留三天即可免费学习(文案需保留)。快扫描下方二维码报名吧! 海报: 朋友圈分享海报示例: FAQ Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第五期和前四内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/ 官方网站从上一期起,我们开始制作自己的官方网站:https://leetcode-solution.cn/91 本次在上次的基础上,增加了网站打卡功能。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"你不知道的 LeetCode 技巧(第二篇)","slug":"ydk-leetcode-2","date":"2021-08-16T16:00:00.000Z","updated":"2023-01-05T12:24:49.400Z","comments":true,"path":"2021/08/17/ydk-leetcode-2/","link":"","permalink":"https://lucifer.ren/blog/2021/08/17/ydk-leetcode-2/","excerpt":"上一篇 你不知道的 LeetCode 技巧(第一篇) 讲述了三个 JS 刷题的小技巧。今天来分享几个 leetcode 通用小技巧,不管你是用什么语言刷题都可以使用。","text":"上一篇 你不知道的 LeetCode 技巧(第一篇) 讲述了三个 JS 刷题的小技巧。今天来分享几个 leetcode 通用小技巧,不管你是用什么语言刷题都可以使用。 tip1 - 中英文切换力扣中国部分题目描述由于翻译的原因,会变得难以理解,甚至出现翻译错误导致题意发生变化的情况。 这个时候推荐大家使用切换语言功能,将其转化为英文。 如下是中文题目描述: 接下来,可以点击下面的图标切换语言。 这只是切换单个题目的语言,如果你想切换整个网站的语言,可以点击力扣中国顶部右侧的这个按钮。 tip2 - 快捷键使用快捷键可以显著提高效率,刷力扣也是一样。 如下是我常用的两个快捷键,强烈推荐使用。 提交代码:cmd + enter 执行代码(测试):cmd + ` 目前力扣编辑器提供八个快捷键绑定,更多快捷键可以参考力扣的提示。如下所示: 另外如果你是 windows 那么上述快捷键肯定不生效,那么你可以按照如上方式查看文档了解具体的快捷键绑定。 tip3 - 限定时间刷题之前我提到过:推荐大家给自己做题设定一个时间限制。可以采用如下方式: 力扣模拟面试 力扣周赛 大家可以做题时点击这个给自己限定时间。 值得注意的是,默认限制时间是 30 分钟。建议大家逐步缩短时间,做到 15 分钟以内,有条件的话挑战一下五分钟。 tip4 - 在线面试如果你是面试官,也可直接使用力扣来出题。力扣中的很多功能都可以使用。 你可以自由添加力扣原题,当然也可以自己出题。 更有意思的的是,竟然可以出前端题 😹。其实前端题就是一个类似于 stackblitz 的东西。不得不说,如果你是前端面试官,这个在线面试功能可以说很全面了。 另外也支持文字聊天,语音,视频,在线评价等常见的面试功能。 简单适用了一下,一个月可以免费创建 10 次面试,每次面试不能超过三个小时,总体来说还蛮方便的。 tip5 - 刷题插件我自己做了一个刷题插件 leetcode-cheatsheet,它扩展了力扣平台的功能。 比如: 一键写题解 一键复制所有内置测试用例 数据结构可视化 复杂度对照表, 直接根据数据规模反猜答案的算法复杂度 功能太多了,不一一介绍了。 更多功能以及如果获取插件可以参考之前我写的文章: 力扣刷题插件 总结文章写于 2021-08-17。如果后续力扣改版,那么相应功能可能会发生变化。如果后续力扣推出新的功能,力扣加加也会第一时间同步大家。比如力扣刚刚发布 APP 的时候,我就参与了内测第一时间给大家带来了这篇文章 力扣 APP 全新改版,史诗级增强!。 本文给大家介绍了五个在力扣中国刷题的技巧: 使用中英文切换功能,防止因为翻译问题使自己看不懂题目描述。 使用快捷键提供自己的效率,尤其是执行和提交这两个功能。 限定时间刷题,创造面试的紧张感。 如果你是面试官,不妨试试力扣的在线面试功能。 如果你刷力扣,强烈推荐我开发的力扣刷题插件。更多功能,等你来提。","categories":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"你不知道的 LeetCode 技巧(第一篇)","slug":"ydk-leetcode","date":"2021-08-05T16:00:00.000Z","updated":"2023-01-07T12:34:34.709Z","comments":true,"path":"2021/08/06/ydk-leetcode/","link":"","permalink":"https://lucifer.ren/blog/2021/08/06/ydk-leetcode/","excerpt":"今天来给使用 JS 刷题的朋友分享三个 LeetCode 上你或许不知道的刷题技巧。","text":"今天来给使用 JS 刷题的朋友分享三个 LeetCode 上你或许不知道的刷题技巧。 tip1 - ES6+首先穿插一个小知识:我们提交的 JS 是如何被 LeetCode 执行的? 我们在力扣提交的代码是放到力扣后台运行的, 而 JS 代码在力扣后台是在 node 中以 —harmony 方式运行的。 大概是这样: 1node --harmony index.js 其中 index.js 就是你提交的代码。 比如: 12345678// 前面 LeetCode 会添加一些代码function sum(a, b) { // you code}// 这里是 LeetCode 的测试用例expect(sum(1, 2)).toBe(3);expect(sum(1, 8)).toBe(9); // 如果测试用例不通过,则直接抛出错误给前端 因此 ES6 特性是完全支持的,大家可以放心使用。 比如我们可以使用 ES6 的解构语法完成数组两个值的交换。 1[a, b] = [b, a]; 如下就是使用了 ES6 的数组解构语法,更多 ES6+ 请参考相关文档。 tip2 - lodash在 LeetCode 中 lodash 默认可直接通过 _ 访问。 这是因为 LeetCode 直接将 lodash require 进来了。类似: 1234567891011const _ = require(\"lodash\");// 前面 LeetCode 会添加一些代码function sum(a, b) { // you code // 你的代码可以通过 _ 访问到 lodash 的所有功能。}// 这里是 LeetCode 的测试用例expect(sum(1, 2)).toBe(3);expect(sum(1, 8)).toBe(9); // 如果测试用例不通过,则直接抛出错误给前端 lodash 有很多有用的功能可直接使用。西法建议你如果让你手写你能够写出,那么就可以放心的使用 lodash 提供的功能。 比如数组拍平: 12_.flatten([1, [2, [3, [4]], 5]]);// => [1, 2, [3, [4]], 5] 再比如深拷贝: 12345var objects = [{ a: 1 }, { b: 2 }];var deep = _.cloneDeep(objects);console.log(deep[0] === objects[0]);// => false 更多 API 可参考官方文档。 tip3 - queue & priority-queue为了弥补 JS 内置数据结构的缺失。除了 JS 内置数据结构之外,LeetCode 平台还对 JS 提供了两种额外的数据结构,它们分别是: queue priority-queue 这两个数据结构都使用的是第三方 datastructures-js 实现的版本,代码我看过了,还是很容易看懂的。 queueLeetCode 提供了 JS 对队列的支持。 12345// empty queueconst queue = new Queue();// from an arrayconst queue = new Queue([1, 2, 3]); 其中 queue 的实现也是使用数组模拟。不过不是直接使用 shift 来删除头部元素,因为直接使用 shift 删除最坏情况时间复杂度是 $O(n)$。这里它使用了一种标记技巧,即每次删除头部元素并不是真的移除,而是标记其已经被移除。 这种做法时间复杂度可以降低到 $O(1)$。只不过如果不停入队和出队,空间复杂度会很高,因为会保留所有的已经出队的元素。因此它会在每次出队超过一半的时候执行一次缩容(类似于数组扩容)。这样时间复杂度会增大到 $O(logn)$,但是空间会省。 详细用法可以参考:https://github.com/datastructures-js/queue 另外西法我自己实现了一套 queue,我是使用链表实现的,理论上复杂度更好,插入和删除时间复杂度都是 O(1),也不会有空间的浪费,核心代码就20 行。但实际使用的话性能不一定谁好,为什么呢,大家可以思考一下? 西法自己实现的 deque priority-queue除了普通队列,LeetCode 还提供了一种特殊的队列 - 优先队列。 12345678// empty queue with default priority the element value itself.const numbersQueue = new MinPriorityQueue();// empty queue, will provide priority in .enqueueconst patientsQueue = new MinPriorityQueue();// empty queue with priority returned from a prop of the queued objectconst biddersQueue = new MaxPriorityQueue({ priority: (bid) => bid.value }); priority-queue 的 api 则可以参考 https://github.com/datastructures-js/priority-queue 同样,西法也实现了堆,大家可以参考一下。 西法自己实现的 heap 值得一提的是,西法还实现了一些堆的高级功能,详情参考 indexed_priority_queue 总结LeetCode 对 JS 的支持主要有: ES6+ 语法的支持 内置 lodash 库,可直接通过 _ 来使用其上的功能函数。 内置数据结构支持队列和优先队列。 文中提到的我自己实现的数据结构代码来自 js-algorithm-light,它是轻量级的 JavaScript 数据结构与算法库。为了让使用 JS 刷题的朋友学习和使用一些常用的数据结构,我开辟了这个仓库,暂定的目标是对标 Python 所有的内置数据结构和算法。 贴一下西法已经实现的数据结构。 求个一键三连支持一下,点赞多的话西法立马就安排下一篇。下一次给大家分享几个 你不知道的 LeetCode 通用小技巧。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"刷题技巧","slug":"LeetCode/刷题技巧","permalink":"https://lucifer.ren/blog/categories/LeetCode/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"从零到谷歌程序员:「狗头」的面试刷题心得","slug":"91algo-interview-yixiao","date":"2021-07-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.802Z","comments":true,"path":"2021/07/29/91algo-interview-yixiao/","link":"","permalink":"https://lucifer.ren/blog/2021/07/29/91algo-interview-yixiao/","excerpt":"本文作者:易潇 她的 Github:https://github.com/lilyzhaoyilu 大家好,我是易潇,也是 91 算法群里大家熟悉的狗头。最近申请和面试基本结束,刚刚过了Google 的 HC。 我的本科读的是商学,所以算是文科转码和0基础转码的一员。在这里想跟大家分享一下我面对面试刷题的心得~ 注:Google 的招人模式比较特别,过了 HC 可以视为被 Google 接受并且已经结束了所有技术性面试,简称过了。具体关于申请 Google 的信息会在后续的文章中跟大家分享,敬请期待。 ​","text":"本文作者:易潇 她的 Github:https://github.com/lilyzhaoyilu 大家好,我是易潇,也是 91 算法群里大家熟悉的狗头。最近申请和面试基本结束,刚刚过了Google 的 HC。 我的本科读的是商学,所以算是文科转码和0基础转码的一员。在这里想跟大家分享一下我面对面试刷题的心得~ 注:Google 的招人模式比较特别,过了 HC 可以视为被 Google 接受并且已经结束了所有技术性面试,简称过了。具体关于申请 Google 的信息会在后续的文章中跟大家分享,敬请期待。 ​ 我的刷题情况在谷歌面试的时候,我大概刷了 550 道题左右:其中大概 150 道简单,310 道中等,90 道困难。其中有中国站一些剑指和面试经典的重复题。对于前 200 和一些高频题,我刷了两遍以上或者更多。 如上图是我刷图论的模板题。左侧是我的提交记录,右侧是我的代码。 我在四月份之前断断续续的大概刷过 60 道题左右,然后在四月份的时候我新开了一个进程并且开始好好刷题。完成最后一个面试是 7 月 20 日。 刷题心得按照一个基于 tag/topic 的计划开始刷题tag/topic 的定义:在力扣题库界面的上方可以看到不同的标签(tag),点进去就可以进入每个标签的列表。 按照标签刷题的好处是它能帮助你巩固你对某个数据结构的理解,并且不断的改正和进步。 网上有很多大佬分享过刷题顺序和题单,或者你也可以自己创建一个列表。我觉得不用太纠结“到底哪个列表最好”,作为一个刚开始刷题的人,今天先学 A 或者 B 一般来说影响不会太大 —- 反正到最后都是要学的。最重要的是“开始学”,不要害怕计划不完美。计划肯定不会是完美的,是会随着自己的认知和进度而修正的。 对于我个人来说,我幸运的参加了力扣加加举办的 91 天算法。91 算法要求每天一起打卡一道题,并且每个礼拜的题都是围绕一个数据结构。这样按照一个标签集中刷题很好的验证自己对某个数据结构的知识并且修正了自己一些错误的认知。 建立一个有正向反馈的 Todo List为什么我这么推荐按照计划刷题呢,还有一个原因是 Todo List 是一个很方便建立正向反馈的工具。为什么建立正向反馈很重要呢 —- 你如果打过游戏,那么你一定有过“呀就打了一会儿游戏怎么就这么点啦”的感觉。打游戏的时候时间过的飞快就是因为大多数设计好的游戏都会不断的给予玩家正向反馈。同理,我们想要一直刷题,那么就要给自己创造一个有持续正向反馈的环境。 Todo List 就是这样一个工具。我刷题的时候使用的是ipad + goodnotes + ElenaLin_青青的电子手账模板 + apple pencil。一般来说,我每天会制定第二天需要做什么事情,并且尽量把它拆分成小的、容易完成的步骤。比如说我想学习字典树,那么我就会把计划拆分成: 看 91 算法的字典树专题 写 91 算法的字典树模板题 LC208 写 91 算法的字典树推荐题 LC211 LC212 LC472 LC648 LC820 LC1032 (如果还有精力)做力扣上字典树标签里的高频 5 道题 一般来说,除了学习某个专题,我的一天还会有一些其他事情,比如打卡 91 算法每日一题,打卡力扣每日一题,看邮件,投递申请等等。大部分时候,我起床之后坐在电脑前,都本能的觉得“太难了”,以至于“我不想干”。每当我有这种感觉的时候,我就在所有的待办事项中选一个我觉得最容易完成的项目并且开始做。换句话说 —- 别想那么多,兄弟,冲就完了!(好吧我是个 fps 游戏玩家)。我一般会选择先做 91 每日一题,因为它不会太难,而且打卡了之后会被其他刷题小伙伴看到,这让我有种觉得自己很棒棒的感觉。只要开始建立 “克服困难” -> “执行任务” -> “完成之后感觉自己很棒棒” 的行为模式之后,一切都会变得简单起来。 ElenaLin 的电子手账使用教程可参考这个视频 https://www.bilibili.com/video/BV1Ai4y1w7ZM 贴一个我中期的日程表,这个时候我已经建立了比较好的循环,所以会看到每天完成的任务多了起来,而且我也已经不太依赖“在完成每个任务之后划掉”这个动作了。我的笔记也并没有很整洁或者美观,只要自己能看懂就可以。 当然,在工具上不一定要跟我一样。本子和笔也可以。不过我强烈推荐用一个本子,因为周末和月末的时候回顾看看也会继续加深上面提到的有正向反馈的行为模式。 学会站在巨人的肩膀上一道题看10 分钟如果没有思路就不要太浪费时间在错误的方法上面,学会看和学习别人的题解。 一般力扣解题区的高票题解都很好。一个题解如果看不懂也没关系,首先照着它写一遍,然后通过各种打印和调试搞清楚每一行代码都是在做什么;如果还是不清楚为什么,可以把某一段代码注释掉再点提交。这个时候系统会报错,你再拿着这个输入一行行的分析题解,就能明白这一段代码在做什么、为什么必须要存在在题解里。(这样提交成功率会变低,但是变低又怎么样呢,我们的目标又不是成为提交成功率最高的仔)。 当然,问其他人也可以,但是我更推荐带有具体目的性的提问,而不是“这道题怎么做?”这种宽泛的问题。一般来说,问题越具体,越好回答,被回答的几率越高。 数量还是质量?其实都需要。一定的数量是熟练度的保证和见过大部分题型的基础。 熟练度在面试中很重要:面试一共那么长时间,其他条件不变,写代码越快,能做的其他事情就越多。其他事情可以是多跟面试官交流,回答一些 follow up,做一些优化等等。这些都是面试加分的项目。 见过大部分题型也很重要:见题型多的本质其实是对于某一类问题能否更好的抽象。比如对于堆来说,91 算法一直强调它的本质是“动态求极值”。刚读完我是不理解的,做了一道题我可能稍微有点理解了,做了三道题我就会觉得“哇塞还能这样,哦果然能这样!”拥有抽象问题的能力就等同于拥有以不变应万变的能力,在遇到没见过的题的时候就可以不慌。在面试中遇见原题的概率确实也不大,所以锻炼抽象能力很重要。 当然,刷题的质量也重要。当我们说刷题的质量的时候,我们在说什么呢?我觉得质量是指:一道题是否真的理解它在求什么。对于每一种这道题可能的思路,运用了哪些数据结构,用这种数据结构的优势和劣势在哪里。比如两数和,哈希表是常见的时间优化解法。这里利用了哈希表能用 O(1)时间获得对应 key 的值的特性,牺牲了空间复杂度,而优化了时间复杂度。 复习和重复复习和重复是很重要的,毕竟“孰能生巧”嘛。对于我来说,每个数据类型的模板题是我写的最多的,多到我能一遍过的基础上每隔一段时间还要刷一下。我在面试之前对于每个常见的数据类型都有自己喜欢的模板写法,并且能根据每道题对模板进行改进。 我记得我当时学堆的时候觉得太难了,我就照着 91 算法讲师小漾写过的堆写了一个礼拜 — 就是从早到晚一直写的那种一个礼拜。之后我经常打趣说 “就算你半夜三点把我拉起来让我手写一个堆都不会有任何 bug”。 凑巧的是,我在面试某个 pre-ipo 公司的时候就考到了跟堆相关的知识。当时的运行环境是 hacker rank,需要把代码跑出来并且通过实例(注:并不是每个公司都要求代码能在环境中运行,有的公司面试更像在白板上写代码)。我当即给面试官表演了一段速写最大堆,因为太熟了,所以行云流水一气呵成。写完了之后我发现两位面试官的眼睛放光,后来 recruiter 主动反馈说面试结果非常非常好。在给 offer 的时候给了比申请的级别高了一点,而且最后谈薪资的时候还主动加了 12%。 有很多一起刷题的小伙伴刷题,尤其是一开始刷题,都是令人感到困难的。很多时候这件事并不难,但是这种“好难啊”的感觉才是真正阻碍我们开始的东西。当有了很多小伙伴的时候,有了周围人的压力和鼓励,这种感觉会减弱一些。 刷题的中后期也会经常感到迷茫和焦虑,有很多小伙伴也能很好的减轻迷茫感。甚至很多时候,因为刷题群、技术群里很多大佬都上岸了,大佬们还会分享职位信息,可谓是一举两得。 对于我来说,我在决定要刷题的初期在网上加了很多技术群和刷题群。在我刷题的大部分时间里, 我每天早上睁眼看微信都是一页的刷题群未读消息。每次看到这个,我就有种我也要赶紧起床刷题的紧迫感。每次刷题觉得郁闷了,去群里跟大家聊聊天互相鼓励,也会一会儿就觉得元气满满。 在哪里找这些群呢 —- 力扣题解区有一些活跃的大佬们的刷题群都不错,力扣的讨论区也经常有人拉刷题群。 适应面试环境就算是算法面试,和在家自己悠闲刷题的感觉还是不一样的。算法面试要求快速的锁定思路并且进行精准打击。(呃,好像游戏打多了…)这方面我强烈推荐力扣的周赛和进行一些模拟面试。模拟面试可以找朋友,也有一些提供类似服务的网站。我在 interviewing.io 模拟了三次,感觉还是挺有用的。 我现在的竞赛分大概是 1780。曾经我也很焦虑的问 Lucifer 竞赛/面试一紧张没思路怎么办。Lucifer 的回复很简单也很有效:“先打 20 场再说”。 Just do it“先打 20 场再说”是面向面试刷题中最重要的指导思想了。 开始总是让人觉得非常困难,但是事情往往没有想的那么难。我也常常因为害怕自己“做不好”而浪费很多时间在寻找“最完美的”开始方法上 —- 实际上,当一个人开始学习某一项技能的时候,大概率这个学习方法是不完美的。所以如果你也想开始面向面试刷题,请把它想成一款游戏 —- 先开始打了再说,打了之后再根据需求和网上的攻略调整自己打游戏的方式就能慢慢变成大神啦。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"},{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/categories/谷歌/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"},{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/tags/谷歌/"}]},{"title":"史诗级更新,VSCODE 可无缝调试浏览器了!","slug":"vscode-brower-debug","date":"2021-07-27T16:00:00.000Z","updated":"2023-01-07T12:34:34.654Z","comments":true,"path":"2021/07/28/vscode-brower-debug/","link":"","permalink":"https://lucifer.ren/blog/2021/07/28/vscode-brower-debug/","excerpt":"2021-07-16 微软发布了一篇博客专门介绍了这个功能,VSCODE 牛逼! 在此之前,你想要在 vscode 内调试 chrome 或者 edge 需要借助于 Chrome Debugger 或者 the Microsoft Edge Debugger extension 这两款 vscode 扩展。 并且更重要的是,其仅能提供最基本的控制台功能,其他的诸如 network,element 是无法查看的,我们仍然需要到浏览器中查看。","text":"2021-07-16 微软发布了一篇博客专门介绍了这个功能,VSCODE 牛逼! 在此之前,你想要在 vscode 内调试 chrome 或者 edge 需要借助于 Chrome Debugger 或者 the Microsoft Edge Debugger extension 这两款 vscode 扩展。 并且更重要的是,其仅能提供最基本的控制台功能,其他的诸如 network,element 是无法查看的,我们仍然需要到浏览器中查看。 这是个什么功能更新之后,我们可以直接在 vscode 中 open link in chrome or edge,并且直接在 vscode 内完成诸如查看 element,network 等几乎所有的常见调试需要用到的功能。 效果截图: (edge devtools) (debug console) 如何使用使用方式非常简单,大家只需要在前端项目中按 F5 触发调试并进行简单的配置即可。这里给大家贴一份 lauch.json 配置,有了它就可以直接开启调试浏览器了。 123456789101112{ \"version\": \"0.2.0\", \"configurations\": [ { \"type\": \"pwa-msedge\", \"request\": \"launch\", \"name\": \"Launch Microsoft Edge and open the Edge DevTools\", \"url\": \"http://localhost:8080\", \"webRoot\": \"${workspaceFolder}\" } ]} 大家需要根据自己的情况修改 url 和 webRoot 等参数。 原理原理其实和 chrome debugger 扩展原理类似。也是基于 Chrome 的 devtool 协议,建立 websocket 链接。通过发送格式化的 json 数据进行交互,这样 vscode 就可以动态拿到运行时的一些信息。比如浏览器网络线程发送的请求以及 DOM 节点信息。 你可以通过 chrome devtool protocol 拿到很多信息,比如上面提到的 network 请求。 由于是 websocket 建立的双向链接,因此在 VSCODE 中改变 dom 等触发浏览器的修改也变得容易。我们只需要在 VSCODE(websocket client) 中操作后通过 websocket 发送一条 JSON 数据到浏览器(websocket server)即可。浏览器会根据收到的 JSON 数据进行一些操作,从效果上来看和用户直接在手动在浏览器中操作并无二致。 值得注意的,chrome devtool protocol 的客户端有很多,不仅仅是 NodeJS 客户端,Python,Java,PHP 等各种客户端一应俱全。 更多 Easier browser debugging with Developer Tools integration in Visual Studio Code vscode-js-debug chrome devtools-protocol Microsoft Edge (Chromium) DevTools Protocol overview","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"VSCODE","slug":"工具/VSCODE","permalink":"https://lucifer.ren/blog/categories/工具/VSCODE/"}],"tags":[{"name":"VSCODE","slug":"VSCODE","permalink":"https://lucifer.ren/blog/tags/VSCODE/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"【91专访】 微软大佬 cabbage 分享算法面试心得","slug":"91algo-interview-cabbage","date":"2021-07-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.614Z","comments":true,"path":"2021/07/27/91algo-interview-cabbage/","link":"","permalink":"https://lucifer.ren/blog/2021/07/27/91algo-interview-cabbage/","excerpt":"背景最近得知 cabbage 拿到了微软的 offer,并在准备拿其他更大公司的 offer。就迫不及待地联系了他,希望他本人可以接受采访。于是这篇采访稿就和大家见面了。 cabbage 是一个做事情非常认真细致的人,对待工作一丝不苟,基本上事情交给他你就可以放心那种,这样的人谁不喜欢?我本人非常看好他,一定可以进更好的公司。 以下 Q 为 lucifer,A 为 cabbage。 ​","text":"背景最近得知 cabbage 拿到了微软的 offer,并在准备拿其他更大公司的 offer。就迫不及待地联系了他,希望他本人可以接受采访。于是这篇采访稿就和大家见面了。 cabbage 是一个做事情非常认真细致的人,对待工作一丝不苟,基本上事情交给他你就可以放心那种,这样的人谁不喜欢?我本人非常看好他,一定可以进更好的公司。 以下 Q 为 lucifer,A 为 cabbage。 ​ 采访 Q: 你是什么时候开始接触数据结构与算法(以下简称算法)的? A: 20 年准备跳大厂的时候才开始的,半路出家的前端开发,以前根本都用不到算法所以也就没去学过。 Q: 你是什么时候接触 91 天学算法(以下简称 91 天)的?从什么途径得知的? A: 通过 GitHub 上的 Lucifer 的算法 repo 关注的公众号,然后通过公众号了解到的。 Q: 91 天有给你带来了什么样的变化么? A: 从基本啥都不会到对基本的数据结构有一点了解,以前只会做 easy 的题,现在有部分 medium 的题也能做出来了,主要是学会了套模板。 Q: 学习算法过程中有“顿悟”的时刻么? A: 有吧,就突然有一天发现看到新题了会不自觉地去拿以前做题的套路去往上套,然后发现还真能用。 Q: 你比较擅长的算法是什么?可以给大家简单分享一下么? A: 说不上擅长,就是套模板的题我都还挺喜欢的,比如说找排列组合的上来就先写个 dfs,找满足条件的 substring 就先上一个 sliding window,大部分的时候套上模板就能解决了。 Q: 有没有什么想和刚入坑算法的同学分享的? A: 模板特别有用,不管会不会先背下来,背的同时就是一个理解的过程,91 算法的题解就可以帮你加深记忆也可以帮你更好的理解。另外刷题可以一次性把一个类型的都刷完,比如可以跟着 91 的分类刷,会更有效率。 Q: 相对而言,你觉得 91 天哪里做的还不够好?应该如何改进? A: 同类型的题可以再多加几道加深记忆,容易到困难的过渡不知道还能不能再 smooth 一点,没有 CS 背景的对于有些类型的题理解起来不容易,可能需要更多的 background,比如 DP 和 back track 这样的。(因为一直在做前端开发的缘故,思维总是习惯性的线性思维,不容易绕过来) Q: 在你学习算法的过程中,对你帮助最大的学习资料是什么? A: LeetCode 的题解讨论区以及油管的一些视频。 lucifer 注:有的 leetcode 题解和油管视频真的做的不错,我们也在《91 天学算法》的讲义中给大家进行了推荐。 Q: 愿意把 91 天分享给你的朋友么? A: 愿意。 lucifer 点评收到 cabbage 的建议。 下一期《91 天学算法》西法打算做几点改变。 目前正在和力扣官方合作,希望可以将《91 天学算法》整合力扣的学习计划和LeetBook。这样大家学习起来更加无缝,效率更高。届时也会有专门的交流群。 增加图, 模拟与枚举 的题目,以前这部分是自习的 增加排序 章节 前面增加了几道题目,相应其他章节需要删除题目,我打算将高频面试题删掉。 部分题解不够完善,再次打磨。 部分难度梯度跨度较大,增加一点缓冲题目或者对跨度较大的进行大篇幅讲解,尽量让大家不掉队。 另外现在已经不建议大家参加这一期(第四期)的《91 天学算法》了,大家可以等待下一期。并且下期的周期可能缩短为一个月左右,而不是现在的 91 天。这是因为每天一道题节奏太慢,会导致学习效果变差。而如果你觉得 91 天都跟不上,一个月岂不是更跟不上?这种担心我觉得是多余的,因为跟不上和时间关系不大,更多的是学习的方法技巧以及不断做题后的顿悟。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"将命令行工具转为 Web 页面?","slug":"ttw","date":"2021-07-04T16:00:00.000Z","updated":"2023-01-05T12:24:50.133Z","comments":true,"path":"2021/07/05/ttw/","link":"","permalink":"https://lucifer.ren/blog/2021/07/05/ttw/","excerpt":"这是个什么东西作为程序员不可避免的会与命令行打交道。我们会用很多的命令行工具,甚至自己开发一些命令行工具。那么如何将一个命令行工具转成 web 页面,变成一个云端应用,方便地与队友共享呢? 比如我做了一个可以将命令行转为 web 页面的工具叫 ttw(terminal to web),此时我想将 vi 变成一个 web 页面。 首先,可以执行如下命令: 1ttw vi 然后就返回一个 web 页面地址,比如是 https://lucifer.ren/ttw/dsuh8643&8934 打开后会发现就是 vim 的页面,然后你可以像本地命令行一样去进行操作,并得到实时的返回效果。 至此,我们就完成了将命令行工具转化为 web 页面的功能。","text":"这是个什么东西作为程序员不可避免的会与命令行打交道。我们会用很多的命令行工具,甚至自己开发一些命令行工具。那么如何将一个命令行工具转成 web 页面,变成一个云端应用,方便地与队友共享呢? 比如我做了一个可以将命令行转为 web 页面的工具叫 ttw(terminal to web),此时我想将 vi 变成一个 web 页面。 首先,可以执行如下命令: 1ttw vi 然后就返回一个 web 页面地址,比如是 https://lucifer.ren/ttw/dsuh8643&8934 打开后会发现就是 vim 的页面,然后你可以像本地命令行一样去进行操作,并得到实时的返回效果。 至此,我们就完成了将命令行工具转化为 web 页面的功能。 如何实现我们可以将命令行工具看成是从标准输入或者命令行参数读取输入,然后做一些处理,最后做出响应(包括读写文件,输出等)。 而做成 web 页面后,除了输出其实都是还是在本地的电脑上进行就好了。 因此我们要做的其实就是将输出部分转到 web 上而已。 基于此,我们只需要: 代理命令行的输入和输出。 将输出通过 web socket 同步到 web 页面。 显然,我们可以将同步到多个客户端。 整个架构可以分为三个部分,命令行客户端,web socket 客户端 和 server 端。 命名行客户端负责解析输入,并 fork 子进程调用真实的命令行工具,同时将代理的输入传递给它。接下来开启一个服务端的监听,同时将真实命令行的输出通过 web socket 同步到 web socket 客户端。 这里我们甚至可以做一些权限控制,比如哪些人可以做编辑,哪些人不可以等等。 有什么用我们可以做一些在线的控制台。比如阿里云的控制台,可以直接在 web 上操作远程的主机。 比如我本地使用命令行报错了(比如 npm run dev 报错),就可以通过这个工具实现远程投屏效果,给别人最最精准的信息,甚至可以用它来报 bug 也行。 更多用处,等你来探索。 相关工具推荐原理差不多讲完了,那有没有现成的工具可以做到呢? 当然有了,以下这两款工具都可以将你的命令行转化为 web 页面。 gotty termpair","categories":[{"name":"命令行","slug":"命令行","permalink":"https://lucifer.ren/blog/categories/命令行/"}],"tags":[{"name":"命令行","slug":"命令行","permalink":"https://lucifer.ren/blog/tags/命令行/"}]},{"title":"从零实现 vite(先导篇)","slug":"mono-vite","date":"2021-07-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.466Z","comments":true,"path":"2021/07/03/mono-vite/","link":"","permalink":"https://lucifer.ren/blog/2021/07/03/mono-vite/","excerpt":"来实现一个 vite ?","text":"来实现一个 vite ? 基本知识假设我们有如下两个 JS 文件。 即两个 esm 的模块, 并且 main.mjs 依赖 utils.mjs。 如上代码可以被支持 ESM 的浏览器所识别,但并不意味着其可以直接被运行。 比如我的代码依赖了 npm 包和一些相对路径,这些浏览器是无法识别的。 而 vite 则解决了这个问题。由于 vite 本质还是依赖了浏览器的特性,因此可以直接利用浏览器的诸如缓存的特点来提高性能。 除此之外, 每次修改文件,比如修改上面的 main.mjs 或者 utils.mjs 中的任意一个文件并不会导致“打包”全部文件。这是因为 vite 根本没有打包过程, 而是直接将修改过的文件热更新到浏览器的内存中。 比如,我修改了 main.js,那么就直接发送一个 http 去请求最新的 main.mjs 文件,而 utils.mjs 则可以继续使用浏览器缓存中的内容即可。 我画了一个简单的原理图给大家参考一下。 模块之间的关系如上图所示。并且这个时序指的是更新一个文件之后的更新流程。 我将其分成了若干模块,它们分别是: 浏览器。用于处理 ESM 文件系统。用于存储源代码文件。 vite-server。 响应浏览器,并返回内容。这些内容主要是最新的文件系统中的文件,除此外还有注入到 client 中的代码等。 hrm-sever。用于根据模块的依赖关系确定应该更新的模块,并触发相应的回调函数。 watcher。 监听文件系统的变更,当文件内容发生变化的时候,通知 hmr-server。之后 hmr-server 再去通过 websocket 通知浏览器获取最新的模块(按需请求)。 如何确定需要更新的模块我们可以根据 esm 的 import 关系生成一个依赖图。并将图中的所有点都放入一个哈希表中,key 可以是文件的请求路径,value 可以是模块本身,这样就可以根据请求路径在 $O(1)$ 的时间获取到指定的节点。之后我们可以遍历依赖图,并依次发起浏览器的 http 请求获取最新内容,并触发回调函数。 如下图红色的模块被更新,我们通过 $O(1)$ 时间获取到它,然后依次遍历虚线的两个模块,发起请求获取其最新模块内容,最后触发注册到这三个模块上的回调函数即可。 回调函数通过 module.hot.accept 注册,具体参考 hmr 相关文档。 一个更复杂的例子: 之后我会根据这个原理图带大家一步步实现一个 mono-vite(等西法有时间的)。","categories":[{"name":"vite","slug":"vite","permalink":"https://lucifer.ren/blog/categories/vite/"}],"tags":[{"name":"vite","slug":"vite","permalink":"https://lucifer.ren/blog/tags/vite/"},{"name":"模块","slug":"模块","permalink":"https://lucifer.ren/blog/tags/模块/"}]},{"title":"一个可以让你肆意摸鱼的 vscode 插件","slug":"moyu-1","date":"2021-06-22T16:00:00.000Z","updated":"2023-01-05T12:24:49.954Z","comments":true,"path":"2021/06/23/moyu-1/","link":"","permalink":"https://lucifer.ren/blog/2021/06/23/moyu-1/","excerpt":"最近发现摸鱼插件挺火的,尤其是 vscode,各种插件层出不穷。","text":"最近发现摸鱼插件挺火的,尤其是 vscode,各种插件层出不穷。 比如: 看股票 玩游戏 看小说 二次元 逛知乎 刷 Leetcode 等等。。。 不过被别人看到你的屏幕也是很容易发现。 很多人采取的措施是:大屏放代码或者需求文档给别人看,小屏幕(比如笔记本)给自己摸鱼。 不过你那个屏幕一天到晚都不变,而且连个字都不打,是不是有点太假了? 今天给大家介绍的插件就可以很好地解决这个问题,可以让 vscode 自己写代码,从而让别人误以为你很忙,这样你就可以安心摸鱼了。 插件介绍这是一个基于 VSCode 的模拟写代码,划水,摸鱼神器。代码写的快,提早完工被压榨怎么办?你需要一个模拟写代码工具,让代码自己重写一遍。当然也可以用来 CodeReview 做酷炫的效果让团队其他人猜猜后面的代码如何写。 安装方法在 vscode 的扩展栏,在应用商店中搜索“swimming”,点击”install”或“安装” 使用方法此工具需要配合低声音键盘,即使抚摸键盘也可以完美演绎敲键盘的样子! 选中代码后,右击菜单中选中 Code Rewriting 即可,也可以右键暂停或继续。如下图: 如果想要快捷键暂停代码重写,可以直接使用以下按键暂停: win 默认:ctrl+alt+shift+p mac 默认:alt+cmd+shift+p 如果想要快捷键放弃代码重写,可以直接使用以下按键停止: win 默认:ctrl+alt+x mac 默认:alt+cmd+x 接下来,你就可以让大屏自己写代码,偶尔瞄一眼,假装在干活,然后专注小屏摸鱼就行了。 重要说明请先提交代码,否则代码丢失概不负责! 仓库地址https://github.com/zy445566/vscode-plugin-swimming","categories":[],"tags":[]},{"title":"我离职了~","slug":"quit-esign","date":"2021-06-17T16:00:00.000Z","updated":"2023-01-05T12:24:49.918Z","comments":true,"path":"2021/06/18/quit-esign/","link":"","permalink":"https://lucifer.ren/blog/2021/06/18/quit-esign/","excerpt":"","text":"离开我最近向公司提了离职,结束了这段为期两年的工作经历。 再见啦~ 前几天在公众号看了一篇文章 一群北京高薪 90 后决定集体裸辞:花光积蓄同住 1000㎡、不买菜不吃外卖,变穷也开心!,特别有感触,这不就是理想的生活该有的样子么 ?很羡慕他们的生活态度和方式。 不过也仅仅是羡慕一下子罢了。真正做到这种程度需要天时地利人和。 既然我不回家种地,那会做什么呢? 起航接下来我可能会入职一份 WFH (远程办公)的工作。 WFH 有很多好处,比如没有通勤成本,办公地点自由(我甚至可以回老家)等。 当然也有一个明显的坏处,那就是交际圈可能会因此雪上加霜。 霜也只能霜了,咱也没办法不是? 不过综合来说我还是比较喜欢这种模式的,很早之前我也提到过将来想找一份 WFH 的工作,这也算是给将来做一些铺垫吧。 大家如果也喜欢远程办公这种模式的话,可以关注我,后续会给大家做一些内推。如果怕错过信息,可以进我的内推群,公众号力扣加加回复内推获取入群方式。 关于面试我发现这种提供 WFH 的外企都是比较注重算法的。因此如果你也想找 WFH 的工作,需要有一定的算法基础才行哦~ 说到算法,容我先打个广告 ~ —- 广告开始 如果你对自己的算法不自信,可以参加我的《91 天学算法》活动。公众号力扣加加回复91 即可获取参与方式了。 —- 广告结束 最近也参加了几场面试,大公司小公司都有。也陆续接到了几个 Offer,薪资涨幅也不错(事实证明刷算法真的有用啊!)。 西法我面的是前端岗位。面试的内容除了前端基础知识外,还有这些问的比较多: 算法 网络 开放设计题 系统架构(不仅限于前端方向) 除了极个别面试体验极差之外,大部分体验都很不错。 最后讲一下我刚才提到的 WFH 的工作的面试。整个面试过程还蛮轻松愉快的,难度中等偏高。面试内容来说大部分都是场景题(或者说是设计题),问题都比较发散。问题也不仅限于前端,后端也有涉及。另外即使是前端,也不仅限于 web 端,客户端等都有涉及。 这些发散的问题可以很好地对你的知识面以及知识深度做一个检测。这比八股文有意思多了。 基本上每一面都会有一个算法题,难度都是由浅入深,刚开始的话一般是简单难度,做出来后会有一些进阶这样子。不过都比较常规,没有什么特别偏,特别难为人的。 题目不方便透露,不过可以讲一下涉及到的考点: 树的遍历 自底向上递推 滑动窗口 平衡二叉树 二分 关注我的人大多数其实不是前端。那我就聊点算法面试的准备吧。关于算法面试的话,反正我经历的面试算法面试难度都不大。不过从网友的供述 上来看,有的公司还是有一丢丢难度的。 如果你需要准备算法面试的话,一定有一个完整的科学的学习路线,接下来: 如果你很自律,那么建议自己找网上的资料看看就行了。 如果你不那么自律,可以考找一个组织互相监督或者加入我的 91 天学算法。 不管你是哪种人,在面试前我都建议你做一些模拟题,或者打一些周赛,找找做题的感觉,尤其是有压力的情况下做题的感觉。如果有条件的话,可以找下心仪公司的模拟面试(就好像考驾照时候租模拟考试车一样)。 ok,先就讲这么多吧!改天再唠吧。 接下来一段时间可能就用这个小桌子办公了。回头如果搬家的话换个大桌子。 我们下一站再见!","categories":[],"tags":[{"name":"技能","slug":"技能","permalink":"https://lucifer.ren/blog/tags/技能/"},{"name":"PPT","slug":"PPT","permalink":"https://lucifer.ren/blog/tags/PPT/"}]},{"title":"手把手教你刷搜索","slug":"search","date":"2021-06-01T16:00:00.000Z","updated":"2023-04-29T06:09:46.056Z","comments":true,"path":"2021/06/02/search/","link":"","permalink":"https://lucifer.ren/blog/2021/06/02/search/","excerpt":"大话搜索搜索一般指在有限的状态空间中进行枚举,通过穷尽所有的可能来找到符合条件的解或者解的个数。根据搜索方式的不同,搜索算法可以分为 DFS,BFS,A*算法等。这里只介绍 DFS 和 BFS,以及发生在 DFS 上一种技巧-回溯。 搜索问题覆盖面非常广泛,并且在算法题中也占据了很高的比例。我甚至还在公开演讲中提到了 前端算法面试中搜索类占据了很大的比重,尤其是国内公司。 搜索专题中的子专题有很多,而大家所熟知的 BFS,DFS 只是其中特别基础的内容。除此之外,还有状态记录与维护,剪枝,联通分量,拓扑排序等等。这些内容,我会在这里一一给大家介绍。 另外即使仅仅考虑 DFS 和 BFS 两种基本算法,里面能玩的花样也非常多。比如 BFS 的双向搜索,比如 DFS 的前中后序,迭代加深等等。 关于搜索,其实在二叉树部分已经做了介绍了。而这里的搜索,其实就是进一步的泛化。数据结构不再局限于前面提到的数组,链表或者树。而扩展到了诸如二维数组,多叉树,图等。不过核心仍然是一样的,只不过数据结构发生了变化而已。 ​","text":"大话搜索搜索一般指在有限的状态空间中进行枚举,通过穷尽所有的可能来找到符合条件的解或者解的个数。根据搜索方式的不同,搜索算法可以分为 DFS,BFS,A*算法等。这里只介绍 DFS 和 BFS,以及发生在 DFS 上一种技巧-回溯。 搜索问题覆盖面非常广泛,并且在算法题中也占据了很高的比例。我甚至还在公开演讲中提到了 前端算法面试中搜索类占据了很大的比重,尤其是国内公司。 搜索专题中的子专题有很多,而大家所熟知的 BFS,DFS 只是其中特别基础的内容。除此之外,还有状态记录与维护,剪枝,联通分量,拓扑排序等等。这些内容,我会在这里一一给大家介绍。 另外即使仅仅考虑 DFS 和 BFS 两种基本算法,里面能玩的花样也非常多。比如 BFS 的双向搜索,比如 DFS 的前中后序,迭代加深等等。 关于搜索,其实在二叉树部分已经做了介绍了。而这里的搜索,其实就是进一步的泛化。数据结构不再局限于前面提到的数组,链表或者树。而扩展到了诸如二维数组,多叉树,图等。不过核心仍然是一样的,只不过数据结构发生了变化而已。 ​ 搜索的核心是什么?实际上搜索题目本质就是将题目中的状态映射为图中的点,将状态间的联系映射为图中的边。根据题目信息构建状态空间,然后对状态空间进行遍历,遍历过程需要记录和维护状态,并通过剪枝和数据结构等提高搜索效率。 状态空间的数据结构不同会导致算法不同。比如对数组进行搜索,和对树,图进行搜索就不太一样。 再次强调一下,我这里讲的数组,树和图是状态空间的逻辑结构,而不是题目给的数据结构。比如题目给了一个数组,让你求数组的搜索子集。虽然题目给的线性的数据结构数组,然而实际上我们是对树这种非线性数据结构进行搜索。这是因为这道题对应的状态空间是非线性的。 对于搜索问题,我们核心关注的信息有哪些?又该如何计算呢?这也是搜索篇核心关注的。而市面上很多资料讲述的不是很详细。搜索的核心需要关注的指标有很多,比如树的深度,图的 DFS 序,图中两点间的距离等等。这些指标都是完成高级算法必不可少的,而这些指标可以通过一些经典算法来实现。这也是为什么我一直强调一定要先学习好基础的数据结构与算法的原因。 不过要讲这些讲述完整并非容易,以至于如果完整写完可能需要花很多的时间,因此一直没有动手去写。 另外由于其他数据结构都可以看做是图的特例。因此研究透图的基本思想,就很容易将其扩展到其他数据结构上,比如树。因此我打算围绕图进行讲解,并逐步具象化到其他特殊的数据结构,比如树。 状态空间结论先行:状态空间其实就是一个图结构,图中的节点表示状态,图中的边表示状态之前的联系,这种联系就是题目给出的各种关系。 搜索题目的状态空间通常是非线性的。比如上面提到的例子:求一个数组的子集。这里的状态空间实际上就是数组的各种组合。 对于这道题来说,其状态空间的一种可行的划分方式为: 长度为 1 的子集 长度为 2 的子集 。。。 长度为 n 的子集(其中 n 为数组长度) 而如何确定上面所有的子集呢。 一种可行的方案是可以采取类似分治的方式逐一确定。 比如我们可以: 先确定某一种子集的第一个数是什么 再确定第二个数是什么 。。。 如何确定第一个数,第二个数。。。呢? 暴力枚举所有可能就可以了。 这就是搜索问题的核心,其他都是辅助,所以这句话请务必记住。 所谓的暴力枚举所有可能在这里就是尝试数组中所有可能的数字。 比如第一个数是什么?很明显可能是数组中任意一项。ok,我们就枚举 n 种情况。 第二个数呢?很明显可以是除了上面已经被选择的数之外的任意一个数。ok,我们就枚举 n - 1 种情况。 据此,你可以画出如下的决策树。 (下图描述的是对一个长度为 3 的数组进行决策的部分过程,树节点中的数字表示索引。即确定第一个数有三个选择,确定第二个数会根据上次的选择变为剩下的两个选择) 决策过程动图演示: 一些搜索算法就是基于这个朴素的思想,本质就是模拟这个决策树。这里面其实也有很多有趣的细节,后面我们会对其进行更加详细的讲解。而现在大家只需要对解空间是什么以及如何对解空间进行遍历有一点概念就行了。 后面我会继续对这个概念进行加深。 这里大家只要记住状态空间就是图,构建状态空间就是构建图。如何构建呢?当然是根据题目描述了 。 DFS 和 BFSDFS 和 BFS 是搜索的核心,贯穿搜索篇的始终,因此有必要先对其进行讲解。 DFSDFS 的概念来自于图论,但是搜索中 DFS 和图论中 DFS 还是有一些区别,搜索中 DFS 一般指的是通过递归函数实现暴力枚举。 如果不使用递归,也可以使用栈来实现。不过本质上是类似的。 首先将题目的状态空间映射到一张图,状态就是图中的节点,状态之间的联系就是图中的边,那么 DFS 就是在这种图上进行深度优先的遍历。而 BFS 也是类似,只不过遍历的策略变为了广度优先,一层层铺开而已。所以BFS 和 DFS 只是遍历这个状态图的两种方式罢了,如何构建状态图才是关键。 本质上,对上面的图进行遍历的话会生成一颗搜索树。为了避免重复访问,我们需要记录已经访问过的节点。这些是所有的搜索算法共有的,后面不再赘述。 如果你是在树上进行遍历,是不会有环的,也自然不需要为了避免环的产生记录已经访问的节点,这是因为树本质上是一个简单无环图。 算法流程 首先将根节点放入stack中。 从stack中取出第一个节点,并检验它是否为目标。如果找到目标,则结束搜寻并回传结果。否则将它某一个尚未检验过的直接子节点加入stack中。 重复步骤 2。 如果不存在未检测过的直接子节点。将上一级节点加入stack中。重复步骤 2。 重复步骤 4。 若stack为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。 这里的 stack 可以理解为自实现的栈,也可以理解为调用栈 算法模板下面我们借助递归来完成 DFS。 12345678910111213const visited = {}function dfs(i) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } visited[i] = true // 将当前状态标为已搜索 for (根据i能到达的下个状态j) { if (!visited[j]) { // 如果状态j没有被搜索过 dfs(j) } }} 常用技巧前序遍历与后序遍历DFS 常见的形式有前序和后序。二者的使用场景也是截然不同的。 上面讲述了搜索本质就是在状态空间进行遍历,空间中的状态可以抽象为图中的点。那么如果搜索过程中,当前点的结果需要依赖其他节点(大多数情况都会有依赖),那么遍历顺序就变得重要。 比如当前节点需要依赖其子节点的计算信息,那么使用后序遍历自底向上递推就显得必要了。而如果当前节点需要依赖其父节点的信息,那么使用先序遍历进行自顶向下的递归就不难想到。 比如下文要讲的计算树的深度。由于树的深度的递归公式为: $f(x) = f(y) + 1$。其中 f(x) 表示节点 x 的深度,并且 x 是 y 的子节点。很明显这个递推公式的 base case 就是根节点深度为一,通过这个 base case 我们可以递推求出树中任意节点的深度。显然,使用先序遍历自顶向下的方式统计是简单而又直接的。 再比如下文要讲的计算树的子节点个数。由于树的子节点递归公式为: $f(x) = sum_{i=0}^{n}{f(a_i)}$ 其中 x 为树中的某一个节点,$a_i$ 为树中节点的子节点。而 base case 则是没有任何子节点(也就是叶子节点),此时 $f(x) = 1$。 因此我们可以利用后序遍历自底向上来完成子节点个数的统计。 关于从递推关系分析使用何种遍历方法, 我在《91 天学算法》中的基础篇中的《模拟,枚举与递推》子专题中对此进行了详细的描述。91 学员可以直接进行查看。关于树的各种遍历方法,我在树专题中进行了详细的介绍。 迭代加深迭代加深本质上是一种可行性的剪枝。关于剪枝,我会在后面的《回溯与剪枝》部分做更多的介绍。 所谓迭代加深指的是在递归树比较深的时候,通过设定递归深度阈值,超过阈值就退出的方式主动减少递归深度的优化手段。这种算法成立的前提是题目中告诉我们答案不超过 xxx,这样我们可以将 xxx 作为递归深度阈值,这样不仅不会错过正确解,还能在极端情况下有效减少不必须的运算。 具体地,我们可以使用自顶向下的方式记录递归树的层次,和上面介绍如何计算树深度的方法是一样的。接下来在主逻辑前增加当前层次是否超过阈值的判断即可。 主代码: 12345MAX_LEVEL = 20def dfs(root, level): if level > MAX_LEVEL: return # 主逻辑dfs(root, 0) 这种技巧在实际使用中并不常见,不过在某些时候能发挥意想不到的作用。 双向搜索有时候问题规模很大,直接搜索会超时。此时可以考虑从起点搜索到问题规模的一半。然后将此过程中产生的状态存起来。接下来目标转化为在存储的中间状态中寻找满足条件的状态。进而达到降低时间复杂度的效果。 上面的说法可能不太容易理解。 接下来通过一个例子帮助大家理解。 题目地址https://leetcode-cn.com/problems/closest-subsequence-sum/ 题目描述123456789101112131415161718192021222324252627282930313233给你一个整数数组 nums 和一个目标值 goal 。你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和为 sum ,你需要 最小化绝对差 abs(sum - goal) 。返回 abs(sum - goal) 可能的 最小值 。注意,数组的子序列是通过移除原始数组中的某些元素(可能全部或无)而形成的数组。 示例 1:输入:nums = [5,-7,3,5], goal = 6输出:0解释:选择整个数组作为选出的子序列,元素和为 6 。子序列和与目标值相等,所以绝对差为 0 。示例 2:输入:nums = [7,-9,15,-2], goal = -5输出:1解释:选出子序列 [7,-9,-2] ,元素和为 -4 。绝对差为 abs(-4 - (-5)) = abs(1) = 1 ,是可能的最小值。示例 3:输入:nums = [1,2,3], goal = -7输出:7 提示:1 <= nums.length <= 40-10^7 <= nums[i] <= 10^7-10^9 <= goal <= 10^9 思路从数据范围可以看出,这道题大概率是一个 $O(2^m)$ 时间复杂度的解法,其中 m 是 nums.length 的一半。 为什么?首先如果题目数组长度限制为小于等于 20,那么大概率是一个 $O(2^n)$ 的解法。 如果这个也不知道,建议看一下这篇文章 https://lucifer.ren/blog/2020/12/21/shuati-silu3/ 另外我的刷题插件 leetcode-cheatsheet 也给出了时间复杂度速查表供大家参考。 将 40 砍半恰好就可以 AC 了。实际上,40 这个数字就是一个强有力的信号。 回到题目中。我们可以用一个二进制位表示原数组 nums 的一个子集,这样用一个长度为 $2^n$ 的数组就可以描述 nums 的所有子集了,这就是状态压缩。一般题目数据范围是 <= 20 都应该想到。 这里 40 折半就是 20 了。 如果不熟悉状态压缩,可以看下我的这篇文章 状压 DP 是什么?这篇题解带你入门 接下来,我们使用动态规划求出所有的子集和。 令 dp[i] 表示选择情况如 i 所示的和。什么是选择情况如 i 所示呢? 比如我要求 nums 的子集和。那么 nums 的子集有 $2^n$ 个,即 nums 中每一个数都有选择和不选择两者情况。因此一共就有$2^n$种。如果用一个数字的二进制来表示这种选择情况,其中 0 表示选择 1 表示不选择,那么一个位数足够的数(二进制位数需要大于 n)可以用来表示一种可能的选择情况。 我们可以枚举数组的每一项,对于每一项我们都考虑将其加入到选择中。那么转移方程为 : dp[(1 << i) + j] = dp[j] + A[i],其中 j 为 i 的子集, i 和 j 的二进制表示的是 nums 的选择情况。 动态规划求子集和代码如下: 1234567def combine_sum(A): n = len(A) dp = [0] * (1 << n) for i in range(n): for j in range(1 << i): dp[(1 << i) + j] = dp[j] + A[i] # 加 i 加入选择 return dp 接下来,我们将 nums 平分为两部分,分别计算子集和: 123n = len(nums)c1 = combine_sum(nums[: n // 2])c2 = combine_sum(nums[n // 2 :]) 其中 c1 就是前半部分数组的子集和,c2 就是后半部分的子集和。 接下来问题转化为:在两个数组 c1 和 c2中找两个数,其和最接近 goal。而这是一个非常经典的双指针问题,逻辑类似两数和。 只不过两数和是一个数组挑两个数,这里是两个数组分别挑一个数罢了。 这里其实只需要一个指针指向一个数组的头,另外一个指向另外一个数组的尾即可。 代码不难写出: 12345678910111213141516def combine_closest(c1, c2): # 先排序以便使用双指针 c1.sort() c2.sort() ans = float(\"inf\") i, j = 0, len(c2) - 1 while i < len(c1) and j >= 0: _sum = c1[i] + c2[j] ans = min(ans, abs(_sum - goal)) if _sum > goal: j -= 1 elif _sum < goal: i += 1 else: return 0 return ans 上面这个代码不懂的多看看两数和。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122232425262728class Solution: def minAbsDifference(self, nums: List[int], goal: int) -> int: def combine_sum(A): n = len(A) dp = [0] * (1 << n) for i in range(n): for j in range(1 << i): dp[(1 << i) + j] = dp[j] + A[i] return dp def combine_closest(c1, c2): c1.sort() c2.sort() ans = float(\"inf\") i, j = 0, len(c2) - 1 while i < len(c1) and j >= 0: _sum = c1[i] + c2[j] ans = min(ans, abs(_sum - goal)) if _sum > goal: j -= 1 elif _sum < goal: i += 1 else: return 0 return ans n = len(nums) return combine_closest(combine_sum(nums[: n // 2]), combine_sum(nums[n // 2 :])) 复杂度分析 令 n 为数组长度, m 为 $\\frac{n}{2}$。 时间复杂度:$O(m*2^m)$ 空间复杂度:$O(2^m)$ 相关题目推荐: 16. 最接近的三数之和 1049. 最后一块石头的重量 II 1774. 最接近目标价格的甜点成本 这道题和双向搜索有什么关系呢? 回一下开头我的话:有时候问题规模很大,直接搜索会超时。此时可以考虑从起点搜索到问题规模的一半。然后将此过程中产生的状态存起来。接下来目标转化为在存储的中间状态中寻找满足条件的状态。进而达到降低时间复杂度的效果。 对应这道题,我们如果直接暴力搜索。那就是枚举所有子集和,然后找到和 goal 最接近的,思路简单直接。可是这样会超时,那么就搜索到一半, 然后将状态存起来(对应这道题就是存到了 dp 数组)。接下来问题转化为两个 dp 数组的运算。该算法,本质上是将位于指数位的常数项挪动到了系数位。这是一种常见的双向搜索,我姑且称为 DFS 的双向搜索。目的是为了和后面的 BFS 双向搜索进行区分。 BFSBFS 也是图论中算法的一种。不同于 DFS, BFS 采用横向搜索的方式,从初始状态一层层展开直到目标状态,在数据结构上通常采用队列结构。 具体地,我们不断从队头取出状态,然后将此状态对应的决策产生的所有新的状态推入队尾,重复以上过程直至队列为空即可。 注意这里有两个关键点: 将此状态对应的决策。 实际上这句话指的就是状态空间中的图的边,而不管是 DFS 和 BFS 边都是确定的。也就是说不管是 DFS 还是 BFS 这个决策都是一样的。不同的是什么?不同的是进行决策的方向不同。 所有新的状态推入队尾。上面说 BFS 和 DFS 是进行决策的方向不同。这就可以通过这个动作体现出来。由于直接将所有状态空间中的当前点的邻边放到队尾。由队列的先进先出的特性,当前点的邻边访问完成之前是不会继续向外扩展的。这一点大家可以和 DFS 进行对比。 最简单的 BFS 每次扩展新的状态就增加一步,通过这样一步步逼近答案。其实也就等价于在一个权值为 1 的图上进行 BFS。由于队列的单调性和二值性,当第一次取出目标状态时就是最少的步数。基于这个特性,BFS 适合求解一些最少操作的题目。 关于单调性和二值性,我会在后面的 BFS 和 DFS 的对比那块进行讲解。 前面 DFS 部分提到了不管是什么搜索都需要记录和维护状态,其中一个就是节点访问状态以防止环的产生。而 BFS 中我们常常用来求点的最短距离。值得注意的是,有时候我们会使用一个哈希表 dist 来记录从源点到图中其他点的距离。这个 dist 也可以充当防止环产生的功能,这是因为第一次到达一个点后再次到达此点的距离一定比第一次到达大,利用这点就可知道是否是第一次访问了。 算法流程 首先将根节点放入队列中。 从队列中取出第一个节点,并检验它是否为目标。 如果找到目标,则结束搜索并回传结果。 否则将它所有尚未检验过的直接子节点加入队列中。 若队列为空,表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传“找不到目标”。 重复步骤 2。 算法模板123456789101112131415const visited = {}function bfs() { let q = new Queue() q.push(初始状态) while(q.length) { let i = q.pop() if (visited[i]) continue for (i的可抵达状态j) { if (j 合法) { q.push(j) } } } // 找到所有合法解} 常用技巧双向搜索题目地址(126. 单词接龙 II)https://leetcode-cn.com/problems/word-ladder-ii/ 题目描述12345678910111213141516171819202122232425262728293031323334353637按字典 wordList 完成从单词 beginWord 到单词 endWord 转化,一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk 这样的单词序列,并满足:每对相邻的单词之间仅有单个字母不同。转换过程中的每个单词 si(1 <= i <= k)必须是字典 wordList 中的单词。注意,beginWord 不必是字典 wordList 中的单词。sk == endWord给你两个单词 beginWord 和 endWord ,以及一个字典 wordList 。请你找出并返回所有从 beginWord 到 endWord 的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。每个序列都应该以单词列表 [beginWord, s1, s2, ..., sk] 的形式返回。 示例 1:输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]输出:[["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]解释:存在 2 种最短的转换序列:"hit" -> "hot" -> "dot" -> "dog" -> "cog""hit" -> "hot" -> "lot" -> "log" -> "cog"示例 2:输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]输出:[]解释:endWord "cog" 不在字典 wordList 中,所以不存在符合要求的转换序列。 提示:1 <= beginWord.length <= 7endWord.length == beginWord.length1 <= wordList.length <= 5000wordList[i].length == beginWord.lengthbeginWord、endWord 和 wordList[i] 由小写英文字母组成beginWord != endWordwordList 中的所有单词 互不相同 思路这道题就是我们日常玩的成语接龙游戏。即让你从 beginWord 开始, 接龙的 endWord。让你找到最短的接龙方式,如果有多个,则全部返回。 不同于成语接龙的字首接字尾。这种接龙需要的是下一个单词和上一个单词仅有一个单词不同。 我们可以对问题进行抽象:即构建一个大小为 n 的图,图中的每一个点表示一个单词,我们的目标是找到一条从节点 beginWord 到节点 endWord 的一条最短路径。 这是一个不折不扣的图上 BFS 的题目。套用上面的解题模板可以轻松解决。唯一需要注意的是如何构建图。更进一步说就是如何构建边。 由题目信息的转换规则:每对相邻的单词之间仅有单个字母不同。不难知道,如果两个单词的仅有单个字母不同 ,就说明两者之间有一条边。 明白了这一点,我们就可以构建邻接矩阵了。 核心代码: 1234neighbors = collections.defaultdict(list)for word in wordList: for i in range(len(word)): neighbors[word[:i] + \"*\" + word[i + 1 :]].append(word) 构建好了图。 BFS 剩下要做的就是明确起点和终点就好了。对于这道题来说,起点是 beginWord,终点是 endWord。 那我们就可以将 beginWord 入队。不断在图上做 BFS,直到第一次遇到 endWord 就好了。 套用上面的 BFS 模板,不难写出如下代码: 这里我用了 cost 而不是 visitd,目的是为了让大家见识多种写法。下面的优化解法会使用 visited 来记录。 12345678910111213141516171819202122232425class Solution: def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]: cost = collections.defaultdict(lambda: float(\"inf\")) cost[beginWord] = 0 neighbors = collections.defaultdict(list) ans = [] for word in wordList: for i in range(len(word)): neighbors[word[:i] + \"*\" + word[i + 1 :]].append(word) q = collections.deque([[beginWord]]) while q: path = q.popleft() cur = path[-1] if cur == endWord: ans.append(path.copy()) else: for i in range(len(cur)): for neighbor in neighbors[cur[:i] + \"*\" + cur[i + 1 :]]: if cost[cur] + 1 <= cost[neighbor]: q.append(path + [neighbor]) cost[neighbor] = cost[cur] + 1 return ans 当终点可以逆向搜索的时候,我们也可以尝试双向 BFS。更本质一点就是:如果你构建的状态空间的边是双向的,那么就可以使用双向 BFS。 和 DFS 的双向搜索思想是类似的。我们只需要使用两个队列分别存储中起点和终点进行扩展的节点(我称其为起点集与终点集)即可。当起点和终点在某一时刻交汇了,说明找到了一个从起点到终点的路径,其路径长度就是两个队列扩展的路径长度和。 以上就是双向搜索的大体思路。用图来表示就是这样的: 如上图,我们从起点和重点(A 和 Z)分别开始搜索,如果起点的扩展状态和终点的扩展状态重叠(本质上就是队列中的元素重叠了),那么我们就知道了一个从节点到终点的最短路径。 动图演示: 看到这里有必要暂停一下插几句话。 为什么双向搜索就快了?什么情况都会更快么?那为什么不都用双向搜索?有哪些使用条件? 我们一个个回答。 为什么双向搜索更快了?通过上面的图我们发现通常刚开始的时候边比较少,队列中的数据也比较少。而随着搜索的进行,搜索树越来越大, 队列中的节点随之增多。和上面双向搜索类似,这种增长速度很多情况下是指数级别的,而双向搜索可以将指数的常系数移动到多项式系数。如果不使用双向搜索那么搜索树大概是这样的: 可以看出搜索树大了很多,以至于很多点我都画不下,只好用 ”。。。“ 来表示。 什么情况下更快?相比于单向搜索,双向搜索通常更快。当然也有例外,举个极端的例子,假如从起点到终点只要一条路径,那么无论使用单向搜索还是双向搜索结果都是一样。 如图使用单向搜索还是双向搜索都是一样的。 为什么不都用双向搜索?实际上做题中,我建议大家尽量使用单向搜索,因为写起来更节点,并且大多数都可以通过所有的测试用例。除非你预估到可能会超时或者提交后发现超时的时候再尝试使用双向搜索。 有哪些使用条件?正如前面所说:”终点可以逆向搜索的时候,可以尝试双向 BFS。更本质一点就是:如果你构建的状态空间的边是双向的,那么就可以使用双向 BFS。“ 让我们继续回到这道题。为了能够判断两者是否交汇,我们可以使用两个 hashSet 分别存储起点集合终点集。当一个节点既出现起点集又出现在终点集,那就说明出现了交汇。 为了节省代码量以及空间消耗,我没有使用上面的队列,而是直接使用了哈希表来代替队列。这种做法可行的关键仍然是上面提到的队列的二值性和单调性。 由于新一轮的出队列前,队列中的权值都是相同的。因此从左到右遍历或者从右到左遍历,甚至是任意顺序遍历都是无所谓的。(很多题都无所谓)因此使用哈希表而不是队列也是可以的。这点需要引起大家的注意。希望大家对 BFS 的本质有更深的理解。 那我们是不是不需要队列,就用哈希表,哈希集合啥的存就行了?非也!我会在双端队列部分为大家揭晓。 这道题的具体算法: 定义两个队列:q1 和 q2 ,分别从起点和终点进行搜索。 构建邻接矩阵 每次都尝试从 q1 和 q2 中的较小的进行扩展。这样可以达到剪枝的效果。 如果 q1 和 q2 交汇了,则将两者的路径拼接起来即可。 代码 语言支持:Python3 Python3 Code: 1234567891011121314151617181920212223242526272829303132333435363738class Solution: def findLadders(self, beginWord: str, endWord: str, wordList: list) -> list: # 剪枝 1 if endWord not in wordList: return [] ans = [] visited = set() q1, q2 = {beginWord: [[beginWord]]}, {endWord: [[endWord]]} steps = 0 # 预处理,空间换时间 neighbors = collections.defaultdict(list) for word in wordList: for i in range(len(word)): neighbors[word[:i] + \"*\" + word[i + 1 :]].append(word) while q1: # 剪枝 2 if len(q1) > len(q2): q1, q2 = q2, q1 nxt = collections.defaultdict(list) for _ in range(len(q1)): word, paths = q1.popitem() visited.add(word) for i in range(len(word)): for neighbor in neighbors[word[:i] + \"*\" + word[i + 1 :]]: if neighbor in q2: # 从 beginWord 扩展过来的 if paths[0][0] == beginWord: ans += [path1 + path2[::-1] for path1 in paths for path2 in q2[neighbor]] # 从 endWord 扩展过来的 else: ans += [path2 + path1[::-1] for path1 in paths for path2 in q2[neighbor]] if neighbor in wordList and neighbor not in visited: nxt[neighbor] += [path + [neighbor] for path in paths] steps += 1 # 剪枝 3 if ans and steps + 2 > len(ans[0]): break q1 = nxt return ans 总结我想通过这道题给大家传递的知识点很多。分别是: 队列不一定非得是常规的队列,也可以是哈希表等。不过某些情况必须是双端队列,这个等会讲双端队列给大家讲。 双向 BFS 是只适合双向图。也就是说从终点也往前推。 双向 BFS 从较少状态的一端进行扩展可以起到剪枝的效果 visitd 和 dist/cost 都可以起到记录点访问情况以防止环的产生的作用。不过 dist 的作用更多,相应空间占用也更大。 双端队列上面提到了 BFS 本质上可以看做是在一个边权值为 1 的图上进行遍历。实际上,我们可以进行一个简单的扩展。如果图中边权值不全是 1,而是 0 和 1 呢?这样其实我们用到双端队列。 双端队列可以在头部和尾部同时进行插入和删除,而普通的队列仅允许在头部删除,在尾部插入。 使用双端队列,当每次取出一个状态的时候。如果我们可以无代价的进行转移,那么就可以将其直接放在队头,否则放在队尾。由前面讲的队列的单调性和二值性不难得出算法的正确性。而如果状态转移是有代价的,那么就将其放到队尾即可。这也是很多语言提供的内置数据结构是双端队列,而不是队列的原因之一。 如下图: 上面的队列是普通的队列。 而下面的双端队列,可以看出我们在队头插队了一个 B。 动图演示: 思考:如果图对应的权值不出 0 和 1,而是任意正整数呢? 前面我们提到了是不是不需要队列,就用哈希表,哈希集合啥的存就行了? 这里为大家揭秘。不可以的。因为哈希表无法处理这里的权值为 0 的情况。 DFS 和 BFS 的对比BFS 和 DFS 分别处理什么样的问题?两者究竟有什么样的区别?这些都值得我们认真研究。 简单来说,不管是 DFS 还是 BFS 都是对题目对应的状态空间进行搜索。 具体来说,二者区别在于: DFS 在分叉点会任选一条深入进入,遇到终点则返回,再次返回到分叉口后尝试下一个选择。基于此,我们可以在路径上记录一些数据。由此也可以衍生出很多有趣的东西。 如下图,我们遍历到 A,有三个选择。此时我们可以任意选择一条,比如选择了 B,程序会继续往下进行选择分支 2,3 。。。 如下动图演示了一个典型的 DFS 流程。后面的章节,我们会给大家带来更复杂的图上 DFS。 BFS 在分叉点会选择搜索的路径各尝试一次。使用队列来存储待处理的元素时,队列中最多只会有两层的元素,且满足单调性,即相同层的元素在一起。基于这个特点有很多有趣的优化。 如下图,广度优先遍历会将搜索的选择全部选择一遍会才会进入到下一层。和上面一样,我给大家标注了程序执行的一种可能的顺序。 可以发现,和我上面说的一样。右侧的队列始终最多有两层的节点,并且相同层的总在一起,换句话说队列的元素在层上满足单调性。 如下动图演示了一个典型的 BFS 流程。后面的章节,我们会给大家带来更复杂的图上 BFS。 总结以上就是《搜索篇(上)》的所有内容了。总结一下搜索篇的解题思路: 根据题目信息构建状态空间(图)。 对图进行遍历(BFS 或者 DFS) 记录和维护状态。(比如 visited 维护访问情况, 队列和栈维护状态的决策方向等等) 我们花了大量的篇幅对 BFS 和 DFS 进行了详细的讲解,包括两个的对比。 核心点需要大家注意: DFS 通常都是有递推关系的,而递归关系就是图的边。根据递归关系大家可以选择使用前序遍历或者后序遍历。 BFS 由于其单调性,因此适合求解最短距离问题。 。。。 双向搜索的本质是将复杂度的常数项从一个影响较大的位置(比如指数位)移到了影响较小的位置(比如系数位)。 搜索篇知识点比较密集,希望大家多多总结复习。 下一节,我们介绍: 回溯与剪枝。 常用的指标与统计方法。具体包括: 树的深度与子树大小 图的 DFS 序 图的拓扑序 图的联通分量 下节内容会首发在《91 天学算法》。想参加的可以戳这里了解详情:https://github.com/azl397985856/leetcode/discussions/532","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"搜索","slug":"搜索","permalink":"https://lucifer.ren/blog/categories/搜索/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"BFS","slug":"BFS","permalink":"https://lucifer.ren/blog/tags/BFS/"},{"name":"搜索","slug":"搜索","permalink":"https://lucifer.ren/blog/tags/搜索/"},{"name":"DFS","slug":"DFS","permalink":"https://lucifer.ren/blog/tags/DFS/"},{"name":"回溯","slug":"回溯","permalink":"https://lucifer.ren/blog/tags/回溯/"}]},{"title":"某区块链公司竟然用这道算法题来面试","slug":"interview-fe-bi","date":"2021-05-23T16:00:00.000Z","updated":"2023-01-07T12:34:34.265Z","comments":true,"path":"2021/05/24/interview-fe-bi/","link":"","permalink":"https://lucifer.ren/blog/2021/05/24/interview-fe-bi/","excerpt":"最近有粉丝和我交流面试遇到的算法题。其中有一道题比较有意思,分享给大家。 ta 说自己面试了一家某大型区块链的公司的前端岗位,被问到了一道算法题。这道题也是一个非常常见的题目了,力扣中也有原题 110. 平衡二叉树,难度为简单。 不过面试官做了一点点小的扩展,难度瞬间升级了。我们来看下面试官做了什么扩展。","text":"最近有粉丝和我交流面试遇到的算法题。其中有一道题比较有意思,分享给大家。 ta 说自己面试了一家某大型区块链的公司的前端岗位,被问到了一道算法题。这道题也是一个非常常见的题目了,力扣中也有原题 110. 平衡二叉树,难度为简单。 不过面试官做了一点点小的扩展,难度瞬间升级了。我们来看下面试官做了什么扩展。 题目题目是《判断一棵树是否为平衡二叉树》,所谓平衡二叉树指的是二叉树中所有节点的左右子树的深度之差不超过 1。输入参数是二叉树的根节点 root,输出是一个 bool 值。 代码会被以如下的方式调用: 123console.log(isBalance([3, 9, 2, null, null, 5, 5]));console.log(isBalance([1, 1, 2, 3, 4, null, null, 4, 4])); 思路求解的思路就是围绕着二叉树的定义来进行即可。 对于二叉树中的每一个节点都: 分别计算左右子树的高度,如果高度差大于 1,直接返回 false 否则继续递归调用左右子节点,如果左右子节点全部都是平衡二叉树,那么返回 true。否则返回 false 可以看出我们的算法就是死扣定义。 计算节点深度比较容易,既可以使用前序遍历 + 参考扩展的方式,也可使用后序遍历的方式,这里我用的是前序遍历 + 参数扩展。 对此不熟悉的强烈建议看一下这篇文章 几乎刷完了力扣所有的树题,我发现了这些东西。。。 于是你可以写出如下的代码。 1234567891011121314function getDepth(root, d = 0) { if (!root) return 0; return max(getDepth(root.left, d + 1), getDepth(root.right, d + 1));}function dfs(root) { if (!root) return true; if (abs(getDepth(root.left), getDepth(root.right)) > 1) return false; return dfs(root.left) && dfs(root.right);}function isBalance(root) { return dfs(root);} 不难发现,这道题的结果和节点(TreeNode) 的 val 没有任何关系,val 是多少完全不影响结果。 这就完了么?可以仔细观察题目给的使用示例,会发现题目给的是 nodes 数组,并不是二叉树的根节点 root。 因此我们需要先构建二叉树。 构建二叉树本质上是一个反序列的过程。要想知道如何反序列化,肯定要先知道序列化。 而二叉树序列的方法有很多啊?题目给的是哪种呢?这需要你和面试官沟通。很有可能面试官在等着你问他呢!!! 反序列化我们先来看下什么是序列化,以下定义来自维基百科: 序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 可见,序列化和反序列化在计算机科学中的应用还是非常广泛的。就拿 LeetCode 平台来说,其允许用户输入形如: 1[1,2,3,null,null,4,5] 这样的数据结构来描述一颗树: ([1,2,3,null,null,4,5] 对应的二叉树) 其实序列化和反序列化只是一个概念,不是一种具体的算法,而是很多的算法。并且针对不同的数据结构,算法也会不一样。 前置知识阅读本文之前,需要你对树的遍历以及 BFS 和 DFS 比较熟悉。如果你还不熟悉,推荐阅读一下相关文章之后再来看。或者我这边也写了一个总结性的文章二叉树的遍历,你也可以看看。 前言我们知道:二叉树的深度优先遍历,根据访问根节点的顺序不同,可以将其分为前序遍历,中序遍历, 后序遍历。即如果先访问根节点就是前序遍历,最后访问根节点就是后序遍历,其它则是中序遍历。而左右节点的相对顺序是不会变的,一定是先左后右。 当然也可以设定为先右后左。 并且知道了三种遍历结果中的任意两种即可还原出原有的树结构。这不就是序列化和反序列化么?如果对这个比较陌生的同学建议看看我之前写的《构造二叉树系列》 有了这样一个前提之后算法就自然而然了。即先对二叉树进行两次不同的遍历,不妨假设按照前序和中序进行两次遍历。然后将两次遍历结果序列化,比如将两次遍历结果以逗号“,” join 成一个字符串。 之后将字符串反序列即可,比如将其以逗号“,” split 成一个数组。 序列化: 1234567891011121314class Solution: def preorder(self, root: TreeNode): if not root: return [] return [str(root.val)] +self. preorder(root.left) + self.preorder(root.right) def inorder(self, root: TreeNode): if not root: return [] return self.inorder(root.left) + [str(root.val)] + self.inorder(root.right) def serialize(self, root): ans = '' ans += ','.join(self.preorder(root)) ans += '$' ans += ','.join(self.inorder(root)) return ans 反序列化: 这里我直接用了力扣 105. 从前序与中序遍历序列构造二叉树 的解法,一行代码都不改。 1234567891011121314151617class Solution: def deserialize(self, data: str): preorder, inorder = data.split('$') if not preorder: return None return self.buildTree(preorder.split(','), inorder.split(',')) def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 实际上inorder 和 preorder 一定是同时为空的,因此你无论判断哪个都行 if not preorder: return None root = TreeNode(preorder[0]) i = inorder.index(root.val) root.left = self.buildTree(preorder[1:i + 1], inorder[:i]) root.right = self.buildTree(preorder[i + 1:], inorder[i+1:]) return root 实际上这个算法是不一定成立的,原因在于树的节点可能存在重复元素。也就是说我前面说的知道了三种遍历结果中的任意两种即可还原出原有的树结构是不对的,严格来说应该是如果树中不存在重复的元素,那么知道了三种遍历结果中的任意两种即可还原出原有的树结构。 聪明的你应该发现了,上面我的代码用了 i = inorder.index(root.val),如果存在重复元素,那么得到的索引 i 就可能不是准确的。但是,如果题目限定了没有重复元素则可以用这种算法。但是现实中不出现重复元素不太现实,因此需要考虑其他方法。那究竟是什么样的方法呢? 答案是记录空节点。接下来进入正题。 DFS序列化我们来模仿一下力扣的记法。 比如:[1,2,3,null,null,4,5](本质上是 BFS 层次遍历),对应的树如下: 选择这种记法,而不是 DFS 的记法的原因是看起来比较直观。并不代表我们这里是要讲 BFS 的序列化和反序列化。 序列化的代码非常简单, 我们只需要在普通的遍历基础上,增加对空节点的输出即可(普通的遍历是不处理空节点的)。 比如我们都树进行一次前序遍历的同时增加空节点的处理。选择前序遍历的原因是容易知道根节点的位置,并且代码好写,不信你可以试试。 因此序列化就仅仅是普通的 DFS 而已,直接给大家看看代码。 Python 代码: 123456789101112class Codec: def serialize_dfs(self, root, ans): # 空节点也需要序列化,否则无法唯一确定一棵树,后不赘述。 if not root: return ans + '#,' # 节点之间通过逗号(,)分割 ans += str(root.val) + ',' ans = self.serialize_dfs(root.left, ans) ans = self.serialize_dfs(root.right, ans) return ans def serialize(self, root): # 由于最后会添加一个额外的逗号,因此需要去除最后一个字符,后不赘述。 return self.serialize_dfs(root, '')[:-1] Java 代码: 12345678910111213141516public class Codec { public String serialize_dfs(TreeNode root, String str) { if (root == null) { str += \"None,\"; } else { str += str.valueOf(root.val) + \",\"; str = serialize_dfs(root.left, str); str = serialize_dfs(root.right, str); } return str; } public String serialize(TreeNode root) { return serialize_dfs(root, \"\"); }} [1,2,3,null,null,4,5] 会被处理为1,2,#,#,3,4,#,#,5,#,# 我们先看一个短视频: (动画来自力扣) 反序列化反序列化的第一步就是将其展开。以上面的例子来说,则会变成数组:[1,2,#,#,3,4,#,#,5,#,#],然后我们同样执行一次前序遍历,每次处理一个元素,重建即可。由于我们采用的前序遍历,因此第一个是根元素,下一个是其左子节点,下下一个是其右子节点。 Python 代码: 1234567891011121314def deserialize_dfs(self, nodes): if nodes: if nodes[0] == '#': nodes.pop(0) return None root = TreeNode(nodes.pop(0)) root.left = self.deserialize_dfs(nodes) root.right = self.deserialize_dfs(nodes) return root return Nonedef deserialize(self, data: str): nodes = data.split(',') return self.deserialize_dfs(nodes) Java 代码: 12345678910111213141516171819public TreeNode deserialize_dfs(List<String> l) { if (l.get(0).equals(\"None\")) { l.remove(0); return null; } TreeNode root = new TreeNode(Integer.valueOf(l.get(0))); l.remove(0); root.left = deserialize_dfs(l); root.right = deserialize_dfs(l); return root;}public TreeNode deserialize(String data) { String[] data_array = data.split(\",\"); List<String> data_list = new LinkedList<String>(Arrays.asList(data_array)); return deserialize_dfs(data_list);} 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:空间复杂度取决于栈深度,因此空间复杂度为 $O(h)$,其中 $h$ 为树的深度。 BFS序列化实际上我们也可以使用 BFS 的方式来表示一棵树。在这一点上其实就和力扣的记法是一致的了。 我们知道层次遍历的时候实际上是有层次的。只不过有的题目需要你记录每一个节点的层次信息,有些则不需要。 这其实就是一个朴实无华的 BFS,唯一不同则是增加了空节点。 Python 代码: 1234567891011121314class Codec: def serialize(self, root): ans = '' queue = [root] while queue: node = queue.pop(0) if node: ans += str(node.val) + ',' queue.append(node.left) queue.append(node.right) else: ans += '#,' return ans[:-1] 反序列化如图有这样一棵树: 那么其层次遍历为 [1,2,3,#,#, 4, 5]。我们根据此层次遍历的结果来看下如何还原二叉树,如下是我画的一个示意图: 动画演示: 容易看出: level x 的节点一定指向 level x + 1 的节点,如何找到 level + 1 呢? 这很容易通过层次遍历来做到。 对于给的的 level x,从左到右依次对应 level x + 1 的节点,即第 1 个节点的左右子节点对应下一层的第 1 个和第 2 个节点,第 2 个节点的左右子节点对应下一层的第 3 个和第 4 个节点。。。 接上,其实如果你仔细观察的话,实际上 level x 和 level x + 1 的判断是无需特别判断的。我们可以把思路逆转过来:即第 1 个节点的左右子节点对应第 1 个和第 2 个节点,第 2 个节点的左右子节点对应第 3 个和第 4 个节点。。。(注意,没了下一层三个字) 因此我们的思路也是同样的 BFS,并依次连接左右节点。 Python 代码: {13-16}12345678910111213141516171819202122232425262728def deserialize(self, data: str): if data == '#': return None # 数据准备 nodes = data.split(',') if not nodes: return None # BFS root = TreeNode(nodes[0]) queue = collections.deque([root]) # 已经有 root 了,因此从 1 开始 i = 1 while i < len(nodes) - 1: node = queue.popleft() lv = nodes[i] rv = nodes[i + 1] i += 2 # 对于给的的 level x,从左到右依次对应 level x + 1 的节点 # node 是 level x 的节点,l 和 r 则是 level x + 1 的节点 if lv != '#': l = TreeNode(lv) node.left = l queue.append(l) if rv != '#': r = TreeNode(rv) node.right = r queue.append(r) return root 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:$O(N)$,其中 $N$ 为节点的总数。 这样就结束了吗?有了上面的序列化的知识。 我们就可以问面试官是哪种序列化的手段。 并针对性选择反序列化方案构造出二叉树。最后再使用本文开头的方法解决即可。 以为这里就结束了吗? 并没有!面试官让他说出自己的复杂度。 读到这里,不妨自己暂停一下,思考这个解法的复杂度是多少? 1 2 3 4 5 ok,我们来揭秘。 时间复杂度是 $O(n) + O(n^2)$,其中 $O(n)$ 是生成树的时间,$O(n^2)$ 是判断是否是平衡二叉树的时间。 为什么判断平衡二叉树的时间复杂度是 $O(n^2)$? 这是因为我们对每一个节点都计算其深度,因此总的时间为所有节点深度之和,最差情况是退化到链表的情况,此时的高度之和为 $1 + 2 + … n$ ,根据等差数列求和公式可知,时间复杂度是 $O(n^2)$。 空间复杂度很明显是 $O(n)$。这其中包括了构建二叉树的 n 以及递归栈的开销。 面试官又追问:可以优化么? 读到这里,不妨自己暂停一下,思考这个解法的复杂度是多少? 1 2 3 4 5 ok,我们来揭秘。 优化的手段有两种。第一种是: 空间换时间。将 getDepth 函数的返回值记录下来,确保多次执行 getDepth 且参数相同的情况不会重复执行。这样时间复杂度可以降低到 $O(n)$ 第二种方法和上面的方法是类似的,其本质是记忆化递归(和动态规划类似)。 我在上一篇文章 读者:西法,记忆化递归究竟怎么改成动态规划啊? 详细讲述了记忆化递归和动态规划的互相转换。如果你看了的话,会发现这里就是记忆化递归。 第一种方法代码比较简单,就不写了。这里给一下第二种方法的代码。 定义函数 getDepth(root) 返回 root 的深度。 需要注意的是,如果子节点不平衡,直接返回 -1。 这样上面的两个函数功能(getDepth 和 isBalance)就可以放到一个函数中执行了。 1234567891011121314class Solution: def isBalanced(self, root: TreeNode) -> bool: def getDepth(root: TreeNode) -> int: if not root: return 0 lh = getDepth(root.left) rh = getDepth(root.right) # lh == -1 表示左子树不平衡 # rh == -1 表示右子树不平衡 if lh == -1 or rh == -1 or abs(rh - lh) > 1: return -1 return max(lh, rh) + 1 return getDepth(root) != -1 总结虽然这道面试题目是一个常见的常规题。不过参数改了一下,瞬间难度就上来了。如果面试官没有直接给你说 nodes 是怎么序列化来的,他可能是故意的。二叉树序列的方法有很多啊?题目给的是哪种呢?这需要你和面试官沟通。很有可能面试官在等着你问他呢!!! 这正是这道题的难点所在。 构造二叉树本质就是一个二叉树反序列的过程。 而如何反序列化需要结合序列化算法。 序列化方法根据是否存储空节点可以分为:存储空节点和不存储空节点。 存储空节点会造成空间的浪费,不存储空节点会造成无法唯一确定一个包含重复值的树。 而关于序列化,本文主要讲述的是二叉树的序列化和反序列化。看完本文之后,你就可以放心大胆地去 AC 以下两道题: 449. 序列化和反序列化二叉搜索树(中等) 297. 二叉树的序列化与反序列化(困难) 另外仅仅是暴力做出来还不够,大家要对自己提出更高的要求。 最起码你要会分析自己的算法,常用的就是复杂度分析。进一步如果你可以对算法进行优化会很加分。比如这里我就通过两种优化方法将时间优化到了 $O(n)$。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"}]},{"title":"读者:西法,记忆化递归究竟怎么改成动态规划啊?","slug":"dp-bottom-up","date":"2021-05-17T16:00:00.000Z","updated":"2023-01-07T13:54:06.072Z","comments":true,"path":"2021/05/18/dp-bottom-up/","link":"","permalink":"https://lucifer.ren/blog/2021/05/18/dp-bottom-up/","excerpt":"我在动态规划专题反复强调了先学习递归,再学习记忆化,最后再学动态规划。 其中原因已经讲得很透了,相信大家已经明白了。如果不明白,强烈建议先看看那篇文章。 尽管很多看了我文章的小伙伴知道了先去学记忆化递归,但是还是有一些粉丝问我:“记忆化递归转化为动态规划老是出错,不得要领怎么办?有没有什么要领呀?” 今天我就来回答一下粉丝的这个问题。 实际上我的动态规划那篇文章已经讲了将记忆化递归转化为动态规划的大概的思路,只是可能不是特别细,今天我们就尝试细化一波。 我们仍然先以经典的爬楼梯为例,给大家讲一点基础知识。接下来,我会带大家解决一个更加复杂的题目。","text":"我在动态规划专题反复强调了先学习递归,再学习记忆化,最后再学动态规划。 其中原因已经讲得很透了,相信大家已经明白了。如果不明白,强烈建议先看看那篇文章。 尽管很多看了我文章的小伙伴知道了先去学记忆化递归,但是还是有一些粉丝问我:“记忆化递归转化为动态规划老是出错,不得要领怎么办?有没有什么要领呀?” 今天我就来回答一下粉丝的这个问题。 实际上我的动态规划那篇文章已经讲了将记忆化递归转化为动态规划的大概的思路,只是可能不是特别细,今天我们就尝试细化一波。 我们仍然先以经典的爬楼梯为例,给大家讲一点基础知识。接下来,我会带大家解决一个更加复杂的题目。 爬楼梯题目描述一个人爬楼梯,每次只能爬 1 个或 2 个台阶,假设有 n 个台阶,那么这个人有多少种不同的爬楼梯方法? 思路由于第 n 级台阶一定是从 n - 1 级台阶或者 n - 2 级台阶来的,因此到第 n 级台阶的数目就是 到第 n - 1 级台阶的数目加上到第 n - 1 级台阶的数目。 记忆化递归代码: 1234567891011const memo = {};function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; if (n in memo) return memo[n]; ans = climbStairs(n - 1) + climbStairs(n - 2); memo[n] = ans; return ans;}climbStairs(10); 首先为了方便看出关系,我们先将 memo 的名字改一下,将 memo 换成 dp: {1,5,7}1234567891011const dp = {};function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; if (n in dp) return dp[n]; ans = climbStairs(n - 1) + climbStairs(n - 2); dp[n] = ans; return ans;}climbStairs(10); 其他地方一点没动,就是名字改了下。 那么这个记忆化递归代码如何改造成动态规划呢?这里我总结了三个步骤,根据这三个步骤就可以将很多记忆化递归轻松地转化为动态规划。 1. 根据记忆化递归的入参建立 dp 数组在动态规划专题中,西法还提过动态规划的核心就是状态。动态规划问题时间复杂度打底就是状态数,空间复杂度如果不考虑滚动数组优化打底也是状态数,而状态数是什么?不就是各个状态的取值范围的笛卡尔积么?而状态正好对应的就是记忆化递归的入参。 对应这道题,显然状态是当前位于第几级台阶。那么状态数就有 n 个。因此开辟一个长度为 n 的一维数组就好了。 我用 from 表示改造前的记忆化递归代码, to 表示改造后的动态规划代码。(下同,不再赘述) from: 12dp = {};function climbStairs(n) {} to: 123function climbStairs(n) { const dp = new Array(n);} 2. 用记忆化递归的叶子节点返回值填充 dp 数组初始值如果你模拟上面 dp 函数的执行过程会发现: if n == 1 return 1 和 if n == 2 return 2,对应递归树的叶子节点,这两行代码深入到叶子节点才会执行。接下来再根据子 dp 函数的返回值合并结果,是一个典型的后序遍历。 如果改造成迭代,如何做呢?一个朴素的想法就是从叶子节点开始模拟递归栈返回的过程,没错动态规划本质就是如此。从叶子节点开始,到根节点结束,这也是为什么记忆化递归通常被称为自顶向下,而动态规划被称为自底向上的原因。这里的底和顶可以看做是递归树的叶子和根。 知道了记忆化递归和动态规划的本质区别。 接下来,我们填充初始化,填充的逻辑就是记忆化递归的叶子节点 return 部分。 from: 12345const dp = {};function climbStairs(n) { if (n == 1) return 1; if (n == 2) return 2;} to: 12345function climbStairs(n) { const dp = new Array(n); dp[0] = 1; dp[1] = 2;} dp 长度为 n,索引范围是 [0,n-1],因此 dp[n-1] 对应记忆化递归的 dp(n)。因此 dp[0] = 1 等价于上面的 if n == 1: return 1。 如果你想让二者完全对应也是可以的,数组长度开辟为 n + 1,并且数组索引 0 不用即可。 3. 枚举笛卡尔积,并复制主逻辑 if (xxx in dp) return dp[xxx] 这种代码删掉 将递归函数 f(xxx, yyy, …) 改成 dp[xxx][yyy][….] ,对应这道题就是 climbStairs(n) 改成 dp[n] 将递归改成迭代。比如这道题每次 climbStairs(n) 递归调用了 climbStairs(n-1) 和 climbStairs(n-2),一共调用 n 次,我们要做的就是迭代模拟。比如这里调用了 n 次,我们就用一层循环来模拟执行 n 次。如果有两个参数就两层循环,三个参数就三层循环,以此类推。 from: 12345678const dp = {};function climbStairs(n) { // ... if (n in dp) return dp[n]; ans = climbStairs(n - 1) + climbStairs(n - 2); dp[n] = ans; return ans;} to: 12345678function climbStairs(n) { // ... // 这个循环其实就是咱上面提到的状态的笛卡尔积。由于这道题就一个状态,枚举一层就好了。如果状态有两个,那么笛卡尔积就可以用两层循环搞定。至于谁在外层循环谁在内层循环,请看我的动态规划专题。 for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 将上面几个步骤的成果合并起来就可以将原有的记忆化递归改造为动态规划了。 完整代码: 1234567891011function climbStairs(n) { if (n == 1) return 1; const dp = new Array(n); dp[0] = 1; dp[1] = 2; for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 有的人可能觉得这道题太简单了。实际上确实有点简单了。 而且我也承认有的记忆化递归比较难以改写,什么情况记忆化递归比较好写,改成动态规划就比较麻烦我也在动态规划专题给大家讲过了,不清楚的同学翻翻。 据我所知,如果动态规划可以过,大多数记忆化递归都可以过。有一些极端情况记忆化递归过不了:那就是力扣测试用例偏多,并且数据量大的测试用例比较多。这是由于力扣的超时判断是多个测试用例的用时总和,而不是单独计算时间。 接下来,我再举一个稍微难一点的例子(这个例子就必须使用动态规划才能过,记忆化递归会超时)。带大家熟悉我上面给大家的套路。 1824. 最少侧跳次数题目描述12345678910111213给你一个长度为 n 的 3 跑道道路 ,它总共包含 n + 1 个 点 ,编号为 0 到 n 。一只青蛙从 0 号点第二条跑道 出发 ,它想要跳到点 n 处。然而道路上可能有一些障碍。给你一个长度为 n + 1 的数组 obstacles ,其中 obstacles[i] (取值范围从 0 到 3)表示在点 i 处的 obstacles[i] 跑道上有一个障碍。如果 obstacles[i] == 0 ,那么点 i 处没有障碍。任何一个点的三条跑道中 最多有一个 障碍。比方说,如果 obstacles[2] == 1 ,那么说明在点 2 处跑道 1 有障碍。这只青蛙从点 i 跳到点 i + 1 且跑道不变的前提是点 i + 1 的同一跑道上没有障碍。为了躲避障碍,这只青蛙也可以在 同一个 点处 侧跳 到 另外一条 跑道(这两条跑道可以不相邻),但前提是跳过去的跑道该点处没有障碍。比方说,这只青蛙可以从点 3 处的跑道 3 跳到点 3 处的跑道 1 。这只青蛙从点 0 处跑道 2 出发,并想到达点 n 处的 任一跑道 ,请你返回 最少侧跳次数 。注意:点 0 处和点 n 处的任一跑道都不会有障碍。示例 1: 12345输入:obstacles = [0,1,2,3,0]输出:2解释:最优方案如上图箭头所示。总共有 2 次侧跳(红色箭头)。注意,这只青蛙只有当侧跳时才可以跳过障碍(如上图点 2 处所示)。示例 2: 1234输入:obstacles = [0,1,1,3,3,0]输出:0解释:跑道 2 没有任何障碍,所以不需要任何侧跳。示例 3: 12345678910输入:obstacles = [0,2,1,0,3,0]输出:2解释:最优方案如上图所示。总共有 2 次侧跳。提示:obstacles.length == n + 11 <= n <= 5 \\* 1050 <= obstacles[i] <= 3obstacles[0] == obstacles[n] == 0 思路 这个青蛙在反复横跳?? 稍微解释一下这个题目。 如果当前跑道后面一个位置没有障碍物,这种情况左右横跳一定不会比直接平跳更优,我们应该贪心地直接平跳(不是横跳)过去。这是因为最坏情况我们可以先平跳过去再横跳,这和先横跳再平跳是一样的。 如果当前跑道后面一个位置有障碍物,我们需要横跳到一个没有障碍物的通道,同时横跳计数器 + 1。 最后选取所有到达终点的横跳次数最少的即可,对应递归树中就是到达叶子节点时计数器最小的。 使用 dp(pos, line) 表示当前在通道 line, 从 pos 跳到终点需要的最少的横跳数。不难写出如下记忆化递归代码。 由于本篇文章主要讲的是记忆化递归改造动态规划,因此这道题的细节就不多介绍了,大家看代码就好。 我们来看下代码: 123456789101112131415161718192021class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): if (pos, line) in dp: return dp[(pos, line)] if pos == len(obstacles) - 1: return 0 # 贪心地平跳 if obstacles[pos + 1] != line: ans = f(pos + 1, line) dp[(pos, line)] = ans return ans ans = float(\"inf\") for nxt in [1, 2, 3]: if nxt != line and obstacles[pos] != nxt: ans = min(ans, 1 +f(pos, nxt)) dp[(pos, line)] = ans return ans return f(0, 2) 这道题记忆化递归会超时,需要使用动态规划才行。 那么如何将 ta 改造成动态规划呢? 还是用上面的口诀。 1. 根据记忆化递归的入参建立 dp 数组上面递归函数的是 dp(pos, line),状态就是形参,因此需要建立一个 m * n 的二维数组,其中 m 和 n 分别是 pos 和 line 的取值范围集合的大小。而 line 取值范围其实就是 [1,3],为了方便索引对应,这次西法决定浪费一个空间。由于这道题是求最小,因此初始化为无穷大没毛病。 from: 1234567class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): # ... return f(0, 2) to: 12345class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] # ... return min(dp[-1]) 2. 用记忆化递归的叶子节点返回值填充 dp 数组初始值不多说了,直接上代码。 from: 123456789class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): if pos == len(obstacles) - 1: return 0 # ... return f(0, 2) to: 123456class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] # ... return min(dp[-1]) 3. 枚举笛卡尔积,并复制主逻辑这道题如何枚举状态?当然是枚举状态的笛卡尔积了。简单,几个状态就几层循环呗。 上代码。 12345678class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] for pos in range(1, len(obstacles)): for line in range(1, 4): # ... return min(dp[-1]) 接下来就是把记忆化递归的主逻辑复制一下粘贴过来就行。 from: 123456789101112131415161718class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): # ... # 贪心地平跳 if obstacles[pos + 1] != line: ans = f(pos + 1, line) dp[(pos, line)] = ans return ans ans = float(\"inf\") for nxt in [1, 2, 3]: if nxt != line and obstacles[pos] != nxt: ans = min(ans, 1 +f(pos, nxt)) dp[(pos, line)] = ans return ans return f(0, 2) to: 1234567891011121314class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] for pos in range(1, len(obstacles)): for line in range(1, 4): if obstacles[pos - 1] != line: # 由于自底向上,因此是和 pos - 1 建立联系,而不是 pos + 1 dp[pos][line] = min(dp[pos][line], dp[pos - 1][line]) else: for nxt in range(1, 4): if nxt != line and obstacles[pos] != nxt: dp[pos][line] = min(dp[pos][line], 1 + dp[pos][nxt]) return min(dp[-1]) 可以看出我基本就是把主逻辑复制过来,稍微改改。 改的基本就是因为: 之前是递归函数,因此 return 需要去掉,比如改成 continue 啥的,不能让函数直接返回,而是继续枚举下一个状态。 之前是 dp[(pos, line)] = ans 现在则改成填充咱上面初始好的二维 dp 数组。 你以为这就结束了么? 那你就错了。之所以选这道题是有原因的。这道题直接提交会报错,是答案错误(WA)。 这里我要告诉大家的是:由于我们使用迭代模拟递归过程,使用多层循环枚举状态的笛卡尔积,而主逻辑部分则是状态转移方程,而转移方程的书写和枚举的顺序息息相关。 从代码不难看出:对这道题来说我们采用的是从小到大枚举,而 dp[pos][line] 也仅仅依赖 dp[pos-1][line] 和 dp[pos][nxt]。 而问题的关键是 nxt,比如处理到了 dp[2][1],d[2][1] 依赖了 dp[2][3] 的值,而实际上 dp[2][3] 是没有处理到的。 因此上面动态规划的的这一行代码有问题: 1dp[pos][line] = min(dp[pos][line], 1 + dp[pos][nxt]) 因为遍历到 dp[pos][line] 的时候,有可能 dp[pos][nxt] 还没计算好(没有枚举到),这就是产生了 bug。 那为什么记忆化递归就没问题呢? 其实很简单。递归函数里面的子问题都是没有计算好的,到叶子节点后再开始计算,计算好后往上返回,而返回的过程其实和迭代是类似的。 比如这道题的 f(0,2) 的递归树大概是这样的,其中虚线标识可能无法到达。 当从 f(0, 2) 递归到 f(0, 1) 或者 f(0, 3) 的的时候,都是没计算好的,因此都无所谓,代码会继续往叶子节点方向扩展,到达叶子节点返回后,所有的子节点肯定都已经计算好了,接下来的过程和普通的迭代就很像了。 比如 f(0,2) 递归到 f(0,3) ,f(0,3) 会继续向下递归知道叶子节点,然后向上返回,当再次回到 f(0,2) 的时候,f(0,3) 一定是已经计算好的。 形象点来说就是:f(0,2) 是一个 leader,告诉他的下属 f(0,3),我想要 xxxx,怎么实现我不管,你有的话直接给我(记忆化),没有的话想办法获取(递归)。不管怎么样,反正你给我弄出来送到我手上。 而如果使用迭代的动态规划,你有的话直接给我(记忆化)很容易做到。关键是没有的话想办法获取(递归)不容易做到啊,至少需要一个类似的循环去完成吧? 那如何解决这个问题呢? 很简单,每次只依赖已经计算好的状态就好了。 对于这道题来说,虽然 dp[pos][nxt] 可能没计算好了,那么 dp[pos-1][nxt] 一定是计算好的,因为 dp[pos-1][nxt] 已经在上一次主循环计算好了。 但是直接改成 dp[pos-1][nxt] 逻辑还对么?这就要具体问题具体分析了,对于这道题来说,这么写是可以的。 这是因为这里的逻辑是如果当前赛道的前面一个位置有障碍物,那么我们不能从当前赛道的前一个位置过来,而只能选择从其他两个赛道横跳过来。 我画了一个简图。其中 X 表示障碍物,O 表示当前的位置,数字表示时间上的先后循序,先跳 1 再跳 2 。。。 123-XO------ 在这里,而以下两种情况其实是等价的: 情况 1(也就是上面 dp[pos][nxt] 的情况): 123-X2--1--- 情况 2(也就是上面 dp[pos-1][nxt] 的情况): 123-X3-12--- 可以看出二者是一样的。没懂?多看看,多想想。 综上,我们将 dp[pos][nxt] 改成 dp[pos-1][nxt] 不会有问题。大家遇到其他问题也采取类似思路分析一波即可。 完整代码: 1234567891011121314class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] for pos in range(1, len(obstacles)): for line in range(1, 4): if obstacles[pos - 1] != line: # 由于自底向上,因此是和 pos - 1 建立联系,而不是 pos + 1 dp[pos][line] = min(dp[pos][line], dp[pos - 1][line]) else: for nxt in range(1, 4): if nxt != line and obstacles[pos] != nxt: dp[pos][line] = min(dp[pos][line], 1 + dp[pos-1][nxt]) return min(dp[-1]) 趁热打铁再来一个再来一个例子,1866. 恰有 K 根木棍可以看到的排列数目。 思路直接上记忆化递归代码: 12345678class Solution: def rearrangeSticks(self, n: int, k: int) -> int: @lru_cache(None) def dp(i, j): if i == 0 and j != 0: return 0 if i == 0 and j == 0: return 1 return (dp(i - 1, j - 1) + dp(i - 1, j) * (i - 1)) % (10**9 + 7) return dp(n, k) % (10**9 + 7) 咱不管这个题是啥,代码怎么来的。假设这个代码咱已经写出来了。那么如何改造成动态规划呢?继续套用三部曲。 1. 根据记忆化递归的入参建立 dp 数组由于 i 的取值 [0-n] 一共 n + 1 个, j 的取值是 [0-k] 一共 k + 1 个。因此初始化一个二维数组即可。 1dp = [[0] * (k+1) for _ in range(n+1)] 2. 用记忆化递归的叶子节点返回值填充 dp 数组初始值由于 i == 0 and j == 0 是 1,因此直接写 dp[0][0] = 1 就好了。 {2}12dp = [[0] * (k+1) for _ in range(n+1)]dp[0][0] = 1 3. 枚举笛卡尔积,并复制主逻辑就是两层循环枚举 i 和 j 的所有组合就好了。 {4-6}1234567dp = [[0] * (k+1) for _ in range(n+1)]dp[0][0] = 1for i in range(1, n + 1): for j in range(1, min(k, i) + 1): # ...return dp[-1][-1] 最后把主逻辑复制过来完工了。 比如: return xxx 改成 dp[形参一][形参二] = xxx 等小细节。 最终的一个代码就是: {7-11}123456789101112class Solution: def rearrangeSticks(self, n: int, k: int) -> int: dp = [[0] * (k+1) for _ in range(n+1)] dp[0][0] = 1 for i in range(1, n + 1): for j in range(1, min(k, i) + 1): dp[i][j] = dp[i-1][j-1] if i - 1 >= j: dp[i][j] += dp[i-1][j] * (i - 1) dp[i][j] %= 10**9 + 7 return dp[-1][-1] 总结有的记忆化递归比较难以改写,什么情况记忆化递归比较好写,改成动态规划就比较麻烦我也在动态规划专题给大家讲过了,不清楚的同学翻翻。 我之所以推荐大家从记忆化递归入手,正是因为很多情况下记忆化写起来简单,而且容错高(想想上面的青蛙跳的例子)。这是因为记忆化递归总是后序遍历,会在到达叶子节点只会往上计算。而往上计算的过程和迭代的动态规划是类似的。或者你也可以认为迭代的动态规划是在模拟记忆化递归的归的过程。 我们要做的就是把一些容易改造的方法学会,接下来面对难的尽量用记忆化递归。据我所知,如果动态规划可以过,大多数记忆化递归都可以过。有一个极端情况记忆化递归过不了:那就是力扣测试用例偏多,并且数据量大的测试用例比较多。这是由于力扣的超时判断是多个测试用例的用时总和,而不是单独计算时间。 将记忆化递归改造成动态规划可以参考我的这三个步骤: 根据记忆化递归的入参建立 dp 数组 用记忆化递归的叶子节点返回值填充 dp 数组初始值 枚举笛卡尔积,并复制主逻辑 另外有一点需要注意的是:状态转移方程的确定和枚举的方向息息相关,虽然不同题目细节差异很大。 但是我们只要牢牢把握一个原则就行了,那就是:永远不要用没有计算好的状态,而是仅适用已经计算好的状态。","categories":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/categories/动态规划/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"为了提高 Github 的阅读体验,我做了一个 Github 阅读器","slug":"github-reader","date":"2021-05-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.864Z","comments":true,"path":"2021/05/16/github-reader/","link":"","permalink":"https://lucifer.ren/blog/2021/05/16/github-reader/","excerpt":"这是个什么东西?虽然自从 Github 嫁给微软之后,做了很多的工作。不管是功能上,还是视觉 UI 上。因此在 Github 上看文档也比以前舒服多了。 可我仍然不是很喜欢这样的界面,我想让界面更加好看一点。 于是我就做了这么一个工具。 只需要输入 Github 地址,点击阅读就 OK 了。是不是很简单?","text":"这是个什么东西?虽然自从 Github 嫁给微软之后,做了很多的工作。不管是功能上,还是视觉 UI 上。因此在 Github 上看文档也比以前舒服多了。 可我仍然不是很喜欢这样的界面,我想让界面更加好看一点。 于是我就做了这么一个工具。 只需要输入 Github 地址,点击阅读就 OK 了。是不是很简单? 上面的 mardown 页面转换后的效果: 如何体验?体验地址:https://leetcode-solution.cn/github 界面非常简单。简单来说就是:输入一个 github 的 md 地址,点击阅读就行了。 另外你如果有一个 md 源码,想在线转化也是可以的。比如我用 md 写了下面一段话: 123456789101112131415161718192021## 思路这个是我的思路。上一个图片吧。![蓝色表示叶子节点](https://p.ipic.vip/z0syhs.jpg)## 代码```pydef f(): pass```**复杂度分析**令 N 为数组长度。- 时间复杂度:$O(N+max(0, K-N)^2)$- 空间复杂度:$O(max(1, K - N))$ 你可以将其复制粘贴到我这里的多行文本框,点击前往阅读即可。 如果你愿意的话,也可以将渲染结果复制粘贴到其他地方,比如一些云笔记平台。 值得注意的是,现在只支持 markdown,如果你输入的地址不是 markdown 是不可以的哦。 之后计划支持更多网站,不仅仅是 Github。 现在很多人都把周刊或者一些面试资料以 markdown 的形式放到 github 上, 如果你也经常看在 Github 上看 markdown 不妨尝试一下吧~ 推荐几个资源最后推荐几个 Github 上的阅读资源给大家: https://github.com/ruanyf/weekly https://github.com/sorrycc/weekly https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/get-started/ch1.md https://github.com/azl397985856/fe-interview/blob/master/docs/topics/network/tcp.md 有没有一个很突兀?","categories":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/categories/Github/"}],"tags":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/tags/Github/"}]},{"title":"努力上岸的元气少女","slug":"91-student-1","date":"2021-05-09T16:00:00.000Z","updated":"2023-01-05T12:24:50.104Z","comments":true,"path":"2021/05/10/91-student-1/","link":"","permalink":"https://lucifer.ren/blog/2021/05/10/91-student-1/","excerpt":"时间:2021-05-07 被采访人:易潇 活动介绍力扣加加,一个努力做西湖区最好的算法题解的团队。91 天学算法是力扣加加举办的一个算法训练活动,通过良好的学习计划 和 91 天的集中学习,帮助大家摆脱困境,征服算法。 现在 《91 天学算法》已经举办了四期了,第四期的活动时间是 2021-05-10 到 2021-08-08。 每一期都有很多优秀学员,我想将这些人的学习经验分享给大家,帮助大家少走弯路。由于我开辟了这个 91 天学算法优秀学员专访 活动。 这次,我们邀请到了远在美国的转码少女 - 易潇,一个元气满满的活力女孩。来看看她是如何学习的吧! ​","text":"时间:2021-05-07 被采访人:易潇 活动介绍力扣加加,一个努力做西湖区最好的算法题解的团队。91 天学算法是力扣加加举办的一个算法训练活动,通过良好的学习计划 和 91 天的集中学习,帮助大家摆脱困境,征服算法。 现在 《91 天学算法》已经举办了四期了,第四期的活动时间是 2021-05-10 到 2021-08-08。 每一期都有很多优秀学员,我想将这些人的学习经验分享给大家,帮助大家少走弯路。由于我开辟了这个 91 天学算法优秀学员专访 活动。 这次,我们邀请到了远在美国的转码少女 - 易潇,一个元气满满的活力女孩。来看看她是如何学习的吧! ​ 采访Q: 你是什么时候开始接触数据结构与算法(以下简称算法)的? A: 从很久之前就听说过数据结果与算法,但正式接触是打算转码的时候开始的。 Q: 你是什么时候接触 91 天学算法(以下简称 91 天)的?从什么途径得知的? A: 我是从第二期就开始接触 91 的,大概是关注公众号的时候关注到的。 Q: 91 天有给你带来了什么样的变化么? A: 91 让我从觉得用哈希表 O(N)写 two sum 很惊奇到了现在可以迷迷糊糊的状态下手写完堆(狗头 最近因为刷了 91,在做几个公司笔试初试(online coding challenge)的时候感觉很简单,也对自己的即将到来的面试更加有自信了~ Q: 学习算法过程中有“顿悟”的时刻么? A: 经常顿悟 — 顿悟源自于重复和每次重复带来的更深层次的理解。 Q: 你比较擅长的算法是什么?可以给大家简单分享一下么? A: 比较擅长的算法是看小漾怎么写的,然后默默的取长补短(就是都抄一遍的意思) Q: 有没有什么想和刚入坑算法的同学分享的? A: 我觉得刚入坑的同学们一定要学会处理自己的 “迷茫” 以及这种情绪带来的 “我不想做,这太难了”。我觉得在学习的过程中,只要能在自己感到迷茫的时候坚持再坚持,总会有那么一个 “顿悟”的时刻,然后就会觉得自己的坚持是值得的。我在正式学习编程之前的两三年里,也经常想过转码,但每次都会因为 “选什么语言” 或者 “初始化环境设定” 太难了等这种原因放弃。现在其实回头看,这些问题其实都很简单。 Q: 相对而言,你觉得 91 天哪里做的还不够好?应该如何改进? A:我希望 91 有更多的个人答题跟踪和一起打卡的人的榜单,这样大家可以相互鼓(shi)励(ya) Q: 愿意把 91 天分享给你的朋友么? A:当然愿意啦!我觉得新手入门的时候最难的就是选择 “学什么” , 而 91 恰好解决了这个问题。加入了 91 之后,只要根据 91 已经制定好的计划每天努力就可以了。而且问题是循序渐进的,对新手非常友好~ 再加上有这么多同学和超级棒的讲师们一起,我觉得 91 是最棒的刷题计划啦! lucifer 总结学习东西是需要循序渐进的。在这个循序渐进的路上会一次次地“顿悟”。如果你有一个良好的学习习惯和学习计划,那么我相信你效率一定很高,那么达到你理想的高度也只是时间的问题罢了。而如果你缺乏这样的学习习惯,不知道怎么去学习算法,可以试试 91 天学算法哦,活动介绍在文末的链接中,也可以直接在公众号力扣加加中回复 91 获取。 预告这几天西法倒腾了一个小功能 - Code Highlight lines,就是给一段代码中的某几行代码加高亮。 之所以搞这个小功能,是因为我最近在给大家贴代码的时候发现大多数代码都没啥意思,核心代码其实就几行。于是想要是能给某几行代码增加高亮就好了。可是找了半天也没发现什么特别好的解决方案,于是西法就自己实现了一下下(包括解析功能和 UI 美化)。 使用方法: 123456789101112131415```py {5-6,11-12}class Solution: def addToArrayForm(self, A: List[int], K: int) -> List[int]: carry = 0 for i in range(len(A) - 1, -1, -1): A[i], carry = (carry + A[i] + K % 10) % 10, (carry + A[i] + K % 10) // 10 K //= 10 B = [] # 如果全部加完还有进位,需要特殊处理。 比如 A = [2], K = 998 carry = carry + K while carry: B = [(carry) % 10] + B carry //= 10 return B + A``` 只需要在语言后面增加你想高亮的行即可。如上代码就是想要高亮第 5-6 行,以及第 11-12 行。 使用效果: 当然也可以直接高亮具体某一行,比如 py {3} 就是高亮第三行。 大家有什么想要的 feature, 也可以给我提,我会尽量满足大家。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第四期)","slug":"91algo-4","date":"2021-05-01T16:00:00.000Z","updated":"2023-01-07T13:56:10.278Z","comments":true,"path":"2021/05/02/91algo-4/","link":"","permalink":"https://lucifer.ren/blog/2021/05/02/91algo-4/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 最近我认识了一个新朋友 - 鸽子君,给大家介绍一下。 鸽子君呢?不见了~ 好吧,先不管它了。继续我们的正题~ 前几天我在微信群发了一个投票,让大家投下面两个选项: 现在开始,等不及了。 先不急,内部测试一下,等六一再上。 后来收集到的情况还是选择 1 的比较多。那我就差不多准备开始吧。为了稍微照顾一下选择 2 的同学,我将开始时间适当延后一点点,我们 05.10 开始。开始之前大家可以先熟悉节奏以及学习先导篇。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 最近我认识了一个新朋友 - 鸽子君,给大家介绍一下。 鸽子君呢?不见了~ 好吧,先不管它了。继续我们的正题~ 前几天我在微信群发了一个投票,让大家投下面两个选项: 现在开始,等不及了。 先不急,内部测试一下,等六一再上。 后来收集到的情况还是选择 1 的比较多。那我就差不多准备开始吧。为了稍微照顾一下选择 2 的同学,我将开始时间适当延后一点点,我们 05.10 开始。开始之前大家可以先熟悉节奏以及学习先导篇。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 活动时间2021-05-10 至 2021-08-08 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容本期部分内容独立于 Github 仓库,直接在我们的官网上进行,体验更棒哦~ 自习篇 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图(加餐) 专题篇 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 进阶篇 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 四期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定私有仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 第二天会对前一天的题目进行讲解。 本期有共五位讲师,每个专题由一位具体的讲师负责,大家有不会的问题可以进行提问。如果讲师来不及回答,大家可以在仓库中提 issue。 关于每一个专题的负责讲义,我们会在 91 官网中的“讲师”模块给出,如上图所示。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书,科学上网兑换码等 连续打卡七天可以获得补签卡一张哦 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 扫码进群(如果提示不能进入,说明已经超过 100 名了,需要找 lucifer 手动拉)。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可 当你满足以下三个条件: 前 50 名 分享海报满 3 天 已经付费 则可联系 lucifer,并告知你的 Github 登录名即可。 熟悉 91 的小伙伴可能发现相较于前三期涨价了。 其实涨价的目的是提供更好的服务,包括但不限于发放奖品,完善讲义,购买服务器(后期考虑),还望大家理解。如果你经济实在困难可以参加下面的返现活动哦。 分享返现如果你没有抢到前 50 名免费的学习机会也不要气馁。我们贴心地为大家搞了分享返现活动,手慢照样可以免费参加哦~ 活动规则: 发送宣传海报到你的朋友圈不屏蔽好友保留三天,三天之后加 lucifer 微信好友(微信号:DevelopeEngineer)进行验证,验证通过全额返现。 不到三天就没必要联系我验证了,必须不屏蔽好友满三天才可以验证。 朋友圈文案统一为: 91 天,遇见更好的自己。发送本海报到朋友圈,不屏蔽好友保留三天即可免费学习(文案需保留)。快扫描下方二维码报名吧! 海报: 朋友圈分享海报示例: FAQ Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第四期和前三期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到仓库,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在仓库中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和仓库,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在仓库和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/ Q:仓库在哪里?怎么进? A:仓库在 91 天学算法官网中的打卡模块中可以看到,官网地址:https://leetcode-solution.cn/91。如果你提示 404, 请耐心等待,我会在活动开始前给大家加。如果活动已经开始,请联系 lucifer 处理。 官方网站从这一期起,我们开始制作自己的官方网站:https://leetcode-solution.cn/91。 在这里大家可以做除了打卡(以及看别人打卡)之外的所有事,包括看目录,具体的讲义,日常安排,每天的题目以及官方题解等。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"他来了,他带着「 西法的刷题秘籍 2.0 」 走来了!","slug":"ebook-2.0","date":"2021-04-27T16:00:00.000Z","updated":"2023-01-05T12:24:49.629Z","comments":true,"path":"2021/04/28/ebook-2.0/","link":"","permalink":"https://lucifer.ren/blog/2021/04/28/ebook-2.0/","excerpt":"嫌文章长的,可以直接拉到底部,底部有 pdf 的获取方式,以下是正文。","text":"嫌文章长的,可以直接拉到底部,底部有 pdf 的获取方式,以下是正文。 之前我写的一些专题很大程度上是给自己复习用的。给自己复习用的内容肯定就比较随意一点,除了我能看懂,其他人很难完全 get 到我的点。 后来很多同学对我的仓库表示了认可,不断给我提 issue,pr 代码等等。 我就在想:这种给自己看的复习材料就能收到这么高的评价,那我从给别人讲懂的角度认真写岂不是更牛?于是我决定将自己仓库的专题装修一遍。把自己能看懂,变成人人都看得懂。 我目前已经完成了大概 20 多个专题,如果全部重新写一遍,这个工程肯定是很大的,西法前前后后肝了好几个月才写完。不过慢慢来,先把重要的重新写一遍再说。 目前我已经重写了 7 个专题,分别是: 数据结构与算法总览 数据结构有哪些?各自有什么特点?平时我们是如何使用它们的? 树 树的核心就是遍历,遍历无非就是 BFS 和 DFS。 链表 链表搞清楚指针就够了。 二分 二分就是让未知世界无机可乘的算法。最左最右二分解决所有二分问题。 堆 动态求极值就用堆。 动态规划 所谓动态就是阶段之间可以转移,所谓规划就是考虑如何转移才能利益最大化。动态规划题型有哪些?不同题型都有什么套路? 二叉树遍历 二叉树的各种遍历如何思维统一地解决?递归迭代思维可以统一么?可以! 每个专题不仅讲是什么,还讲原理,讲套路,讲模板,讲常见题型和技巧,并且结合具体的题目让你看我是如何运用这些技巧的,每一个专题都是精品。 这已经可以覆盖相当多的题型了!后面我会陆续更新其他专题,努力做西湖区最好的算法题解。 仅仅这七个专题就已经 10w + 字了。可见我写的还是蛮详细和认真的。 已经有不少小伙伴靠这份《刷题秘籍》成功拿到了 BAT offer。不管是应届生,还是工作几年想跳槽的人,这份刷题笔记都很值得看一波。 我们废话不多说,直接告诉大家怎么获取。 我推荐大家直接使用在线版 + latex Chrome 插件在线观看,不仅阅读体验好,而且可以享受自动更新的服务。在线地址:https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/ latex 插件我用的是 tex-all-the-things。 地址:https://chrome.google.com/webstore/detail/tex-all-the-things/cbimabofgmfdkicghcadidpemeenbffn 如果你喜欢电子书,也没关系。我还贴心地把这份《刷题秘籍》打包成了 PDF,方便大家阅读与学习。现在这本电子书限时免费下载! 公众号《力扣加加》回复电子书就可以获取了。注意:后期随时可能收费哦~ 另外西法我最近受邀参加《前端早早聊》算法专场,给大家分享我的刷题经验。如果你和我一样,也是一枚前端,并且对算法面试比较畏惧,那么一定要关注我,我的前端算法面试系列文章绝对是市面上绝无仅有的材料,不仅内容真实性高,并且内容质量高,一定让你有所收获。更多内容,关注公众号《力扣加加》,让算法飞起来。","categories":[{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/categories/书/"},{"name":"算法","slug":"书/算法","permalink":"https://lucifer.ren/blog/categories/书/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/tags/书/"},{"name":"草稿","slug":"草稿","permalink":"https://lucifer.ren/blog/tags/草稿/"}]},{"title":"动态规划到底有多难?","slug":"dp","date":"2021-04-19T16:00:00.000Z","updated":"2023-01-07T13:54:06.084Z","comments":true,"path":"2021/04/20/dp/","link":"","permalink":"https://lucifer.ren/blog/2021/04/20/dp/","excerpt":"动态规划是一个从其他行业借鉴过来的词语。 它的大概意思先将一件事情分成若干阶段,然后通过阶段之间的转移达到目标。由于转移的方向通常是多个,因此这个时候就需要决策选择具体哪一个转移方向。 ​","text":"动态规划是一个从其他行业借鉴过来的词语。 它的大概意思先将一件事情分成若干阶段,然后通过阶段之间的转移达到目标。由于转移的方向通常是多个,因此这个时候就需要决策选择具体哪一个转移方向。 ​ 动态规划所要解决的事情通常是完成一个具体的目标,而这个目标往往是最优解。并且: 阶段之间可以进行转移,这叫做动态。 达到一个可行解(目标阶段) 需要不断地转移,那如何转移才能达到最优解?这叫规划。 每个阶段抽象为状态(用圆圈来表示),状态之间可能会发生转化(用箭头表示)。可以画出类似如下的图: 那我们应该做出如何的决策序列才能使得结果最优?换句话说就是每一个状态应该如何选择到下一个具体状态,并最终到达目标状态。这就是动态规划研究的问题。 每次决策实际上不会考虑之后的决策,而只会考虑之前的状态。 形象点来说,其实是走一步看一步这种短视思维。为什么这种短视可以来求解最优解呢?那是因为: 我们将所有可能的转移全部模拟了一遍,最后挑了一个最优解。 无后向性(这个我们后面再说,先卖个关子) 而如果你没有模拟所有可能,而直接走了一条最优解,那就是贪心算法了。 没错,动态规划刚开始就是来求最优解的。只不过有的时候顺便可以求总的方案数等其他东西,这其实是动态规划的副产物。 好了,我们把动态规划拆成两部分分别进行解释,或许你大概知道了动态规划是一个什么样的东西。但是这对你做题并没有帮助。那算法上的动态规划究竟是个啥呢? 在算法上,动态规划和查表的递归(也称记忆化递归) 有很多相似的地方。我建议大家先从记忆化递归开始学习。本文也先从记忆化递归开始,逐步讲解到动态规划。 记忆化递归那么什么是递归?什么是查表(记忆化)?让我们慢慢来看。 什么是递归?递归是指在函数中调用函数自身的方法。 有意义的递归通常会把问题分解成规模缩小的同类子问题,当子问题缩写到寻常的时候,我们可以直接知道它的解。然后通过建立递归函数之间的联系(转移)即可解决原问题。 是不是和分治有点像? 分治指的是将问题一分为多,然后将多个解合并为一。而这里并不是这个意思。 一个问题要使用递归来解决必须有递归终止条件(算法的有穷性),也就是说递归会逐步缩小规模到寻常。 虽然以下代码也是递归,但由于其无法结束,因此不是一个有效的算法: 12def f(x): return x + f(x - 1) 上面的代码除非外界干预,否则会永远执行下去,不会停止。 因此更多的情况应该是: 123def f(n): if n == 1: return 1 return n + f(n - 1) 使用递归通常可以使代码短小,有时候也更可读。算法中使用递归可以很简单地完成一些用循环不太容易实现的功能,比如二叉树的左中右序遍历。 递归在算法中有非常广泛的使用,包括现在日趋流行的函数式编程。 递归在函数式编程中地位很高。 纯粹的函数式编程中没有循环,只有递归。 实际上,除了在编码上通过函数调用自身实现递归。我们也可以定义递归的数据结构。比如大家所熟知的树,链表等都是递归的数据结构。 1234Node { value: any; // 当前节点的值 children: Array<Node>; // 指向其儿子} 如上代码就是一个多叉树的定义形式,可以看出 children 就是 Node 的集合类,这就是一种递归的数据结构。 不仅仅是普通的递归函数本文中所提到的记忆化递归中的递归函数实际上指的是特殊的递归函数,即在普通的递归函数上满足以下几个条件: 递归函数不依赖外部变量 递归函数不改变外部变量 满足这两个条件有什么用呢?这是因为我们需要函数给定参数,其返回值也是确定的。这样我们才能记忆化。关于记忆化,我们后面再讲。 如果大家了解函数式编程,实际上这里的递归其实严格来说是函数式编程中的函数。如果不了解也没关系,这里的递归函数其实就是数学中的函数。 我们来回顾一下数学中的函数: 1在一个变化过程中,假设有两个变量 x、y,如果对于任意一个 x 都有唯一确定的一个 y 和它对应,那么就称 x 是自变量,y 是 x 的函数。x 的取值范围叫做这个函数的定义域,相应 y 的取值范围叫做函数的值域 。 而本文所讲的所有递归都是指的这种数学中的函数。 比如上面的递归函数: 123def f(x): if x == 1: return 1 return x + f(x - 1) x 就是自变量,x 的所有可能的返回值构成的集合就是定义域。 f(x) 就是函数。 f(x) 的所有可能的返回值构成的集合就是值域。 自变量也可以有多个,对应递归函数的参数可以有多个,比如 f(x1, x2, x3)。 通过函数来描述问题,并通过函数的调用关系来描述问题间的关系就是记忆化递归的核心内容。 每一个动态规划问题,实际上都可以抽象为一个数学上的函数。这个函数的自变量集合就是题目的所有取值,值域就是题目要求的答案的所有可能。我们的目标其实就是填充这个函数的内容,使得给定自变量 x,能够唯一映射到一个值 y。(当然自变量可能有多个,对应递归函数参数可能有多个) 解决动态规划问题可以看成是填充函数这个黑盒,使得定义域中的数并正确地映射到值域。 递归并不是算法,它是和迭代对应的一种编程方法。只不过,我们通常借助递归去分解问题而已。比如我们定义一个递归函数 f(n),用 f(n) 来描述问题。就和使用普通动态规划 f[n] 描述问题是一样的,这里的 f 是 dp 数组。 什么是记忆化?为了大家能够更好地对本节内容进行理解,我们通过一个例子来切入: 一个人爬楼梯,每次只能爬 1 个或 2 个台阶,假设有 n 个台阶,那么这个人有多少种不同的爬楼梯方法? 思路: 由于第 n 级台阶一定是从 n - 1 级台阶或者 n - 2 级台阶来的,因此到第 n 级台阶的数目就是 到第 n - 1 级台阶的数目加上到第 n - 2 级台阶的数目。 递归代码: 12345function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; return climbStairs(n - 1) + climbStairs(n - 2);} 我们用一个递归树来直观感受以下(每一个圆圈表示一个子问题): 红色表示重复的计算。即 Fib(N-2) 和 Fib(N-3) 都被计算了两次,实际上计算一次就够了。比如第一次计算出了 Fib(N-2) 的值,那么下次再次需要计算 Fib(N-2)的时候,可以直接将上次计算的结果返回。之所以可以这么做的原因正是前文提到的我们的递归函数是数学中的函数,也就是说参数一定,那么返回值也一定不会变,因此下次如果碰到相同的参数,我们就可以将上次计算过的值直接返回,而不必重新计算。这样节省的时间就等价于重叠子问题的个数。 以这道题来说,本来需要计算 $2^n$ 次,而如果使用了记忆化,只需要计算 n 次,就是这么神奇。 代码上,我们可以使用一个 hashtable 去缓存中间计算结果,从而省去不必要的计算。 我们使用记忆化来改造上面的代码: 123456789memo = {}def climbStairs(n): if n == 1:return 1 if n == 2: return 2 if n in memo: return memo[n] ans = climbStairs(n - 1) + climbStairs(n-2) memo[n] = ans return ansclimbStairs(10) 这里我使用了一个名为 memo 的哈希表来存储递归函数的返回值,其中 key 为参数,value 为递归函数的返回值。 key 的形式为 (x, y),表示的是一个元祖。通常动态规划的参数有多个,我们就可以使用元祖的方式来记忆化。或者也可采取多维数组的形式。对于上图来说,就可使用二维数组来表示。 大家可以通过删除和添加代码中的 memo 来感受一下记忆化的作用。 小结使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。这里我列举了几道算法题目,这几道算法题目都可以用递归轻松写出来: 递归实现 sum 二叉树的遍历 走楼梯问题 汉诺塔问题 杨辉三角 递归中如果存在重复计算(我们称重叠子问题,下文会讲到),那就是使用记忆化递归(或动态规划)解题的强有力信号之一。可以看出动态规划的核心就是使用记忆化的手段消除重复子问题的计算,如果这种重复子问题的规模是指数或者更高规模,那么记忆化递归(或动态规划)带来的收益会非常大。 为了消除这种重复计算,我们可使用查表的方式。即一边递归一边使用“记录表”(比如哈希表或者数组)记录我们已经计算过的情况,当下次再次碰到的时候,如果之前已经计算了,那么直接返回即可,这样就避免了重复计算。下文要讲的动态规划中 DP 数组其实和这里“记录表”的作用是一样的。 如果你刚开始接触递归, 建议大家先去练习一下递归再往后看。一个简单练习递归的方式是将你写的迭代全部改成递归形式。比如你写了一个程序,功能是“将一个字符串逆序输出”,那么使用迭代将其写出来会非常容易,那么你是否可以使用递归写出来呢?通过这样的练习,可以让你逐步适应使用递归来写程序。 当你已经适应了递归的时候,那就让我们继续学习动态规划吧! 动态规划讲了这么多递归和记忆化,终于到了我们的主角登场了。 动态规划的基本概念我们先来学习动态规划最重要的两个概念:最优子结构和无后效性。 其中: 无后效性决定了是否可使用动态规划来解决。 最优子结构决定了具体如何解决。 最优子结构动态规划常常适用于有重叠子问题和最优子结构性质的问题。前面讲了重叠子问题,那么最优子结构是什么?这是我从维基百科找的定义: 1如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。 举个例子:如果考试中的分数定义为 f,那么这个问题就可以被分解为语文,数学,英语等子问题。显然子问题最优的时候,总分这个大的问题的解也是最优的。 再比如 01 背包问题:定义 f(weights, values, capicity)。如果我们想要求 f([1,2,3], [2,2,4], 10) 的最优解。我们可以将其划分为如下子问题: 将第三件物品装进背包,也就是 f([1,2], [2,2], 10) 和不将第三件物品装进背包,也就是 f([1,2,3], [2,2,4], 9) 显然这两个问题还是复杂,我们需要进一步拆解。不过,这里不是讲如何拆解的。 原问题 f([1,2,3], [2,2,4], 10) 等于以上两个子问题的最大值。只有两个子问题都是最优的时候整体才是最优的,这是因为子问题之间不会相互影响。 无后效性即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。 继续以上面两个例子来说。 数学考得高不能影响英语(现实其实可能影响,比如时间一定,投入英语多,其他科目就少了)。 背包问题中 f([1,2,3], [2,2,4], 10) 选择是否拿第三件物品,不应该影响是否拿前面的物品。比如题目规定了拿了第三件物品之后,第二件物品的价值就会变低或变高)。这种情况就不满足无后向性。 动态规划三要素状态定义动态规划的中心点是什么?如果让我说的话,那就是定义状态。 动态规划解题的第一步就是定义状态。定义好了状态,就可以画出递归树,聚焦最优子结构写转移方程就好了,因此我才说状态定义是动态规划的核心,动态规划问题的状态确实不容易看出。 但是一旦你能把状态定义好了,那就可以顺藤摸瓜画出递归树,画出递归树之后就聚焦最优子结构就行了。但是能够画出递归树的前提是:对问题进行划分,专业点来说就是定义状态。那怎么才能定义出状态呢? 好在状态的定义都有特点的套路。 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 也就是说状态的定义通常有不同的套路,大家可以在做题的过程中进行学习和总结。但是这种套路非常多,那怎么搞定呢? 说实话,只能多练习,在练习的过程中总结套路。具体的套路参考后面的动态规划的题型 部分内容。之后大家就可以针对不同的题型,去思考大概的状态定义方向。 两个例子 关于状态定义,真的非常重要,以至于我将其列为动态规划的核心。因此我觉得有必要举几个例子来进行说明。我直接从力扣的动态规划专题中抽取前两道给大家讲讲。 第一道题:《5. 最长回文子串》难度中等 123456789101112131415161718192021222324252627给你一个字符串 s,找到 s 中最长的回文子串。 示例 1:输入:s = "babad"输出:"bab"解释:"aba" 同样是符合题意的答案。示例 2:输入:s = "cbbd"输出:"bb"示例 3:输入:s = "a"输出:"a"示例 4:输入:s = "ac"输出:"a" 提示:1 <= s.length <= 1000s 仅由数字和英文字母(大写和/或小写)组成 这道题入参是一个字符串,那我们要将其转化为规模更小的子问题,那无疑就是字符串变得更短的问题,临界条件也应该是空字符串或者一个字符这样。 因此: 一种定义状态的方式就是 f(s1),含义是字符串 s1 的最长回文子串,其中 s1 就是题目中的字符串 s 的子串,那么答案就是 f(s)。 由于规模更小指的是字符串变得更短,而描述字符串我们也可以用两个变量来描述,这样实际上还省去了开辟字符串的开销。两个变量可以是起点索引 + 子串长度,也可以是终点索引 + 子串长度,也可以是起点坐标 + 终点坐标。随你喜欢,这里我就用起点坐标 + 终点坐标。那么状态定义就是 f(start, end),含义是子串 s[start:end+1]的最长回文子串,那么答案就是 f(0, len(s) - 1) s[start:end+1] 指的是包含 s[start],而不包含 s[end+1] 的连续子串。 这无疑是一种定义状态的方式,但是一旦我们这样去定义就会发现:状态转移方程会变得难以确定(实际上很多动态规划都有这个问题,比如最长上升子序列问题)。那究竟如何定义状态呢?我会在稍后的状态转移方程继续完成这道题。我们先来看下一道题。 第二道题:《10. 正则表达式匹配》难度困难 12345678910111213141516171819202122232425262728293031323334353637383940给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。'.' 匹配任意单个字符'*' 匹配零个或多个前面的那一个元素所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。 示例 1:输入:s = "aa" p = "a"输出:false解释:"a" 无法匹配 "aa" 整个字符串。示例 2:输入:s = "aa" p = "a*"输出:true解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。示例 3:输入:s = "ab" p = ".*"输出:true解释:".*" 表示可匹配零个或多个('*')任意字符('.')。示例 4:输入:s = "aab" p = "c*a*b"输出:true解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。示例 5:输入:s = "mississippi" p = "mis*is*p*."输出:false 提示:0 <= s.length <= 200 <= p.length <= 30s 可能为空,且只包含从 a-z 的小写字母。p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。保证每次出现字符 * 时,前面都匹配到有效的字符 这道题入参有两个, 一个是 s,一个是 p。沿用上面的思路,我们有两种定义状态的方式。 一种定义状态的方式就是 f(s1, p1),含义是 p1 是否可匹配字符串 s1,其中 s1 就是题目中的字符串 s 的子串,p1 就是题目中的字符串 p 的子串,那么答案就是 f(s, p)。 另一种是 f(s_start, s_end, p_start, p_end),含义是子串 p1[p_start:p_end+1] 是否可以匹配字符串 s[s_start:s_end+1],那么答案就是 f(0, len(s) - 1, 0, len(p) - 1) 而这道题实际上我们也可采用更简单的状态定义方式,不过基本思路都是差不多的。我仍旧卖个关子,后面讲转移方程再揭晓。 搞定了状态定义,你会发现时间空间复杂度都变得很明显了。这也是为啥我反复强调状态定义是动态规划的核心。 时间空间复杂度怎么个明显法了呢? 首先空间复杂度,我刚才说了动态规划其实就是查表的暴力法,因此动态规划的空间复杂度打底就是表的大小。再直白一点就是上面的哈希表 memo 的大小。而 memo的大小基本就是状态的个数。状态个数是多少呢? 这不就取决你状态怎么定义了么?比如上面的 f(s1, p1) 。状态的多少是多少呢?很明显就是每个参数的取值范围大小的笛卡尔积。s1 的所有可能取值有 len(s) 种,p1 的所有可能有 len(p)种,那么总的状态大小就是 len(s) * len(p)。那空间复杂度是 $O(m * n)$,其中 m 和 n 分别为 s 和 p 的大小。 我说空间复杂度打底是状态个数, 这里暂时先不考虑状态压缩的情况。 其次是时间复杂度。时间复杂度就比较难说了。但是由于我们无论如何都要枚举所有状态,因此时间复杂度打底就是状态总数。以上面的状态定义方式,时间复杂度打底就是$O(m * n)$。 如果你枚举每一个状态都需要和 s 的每一个字符计算一下,那时间复杂度就是 $O(m^2 * n)$。 以上面的爬楼梯的例子来说,我们定义状态 f(n) 表示到达第 n 级台阶的方法数,那么状态总数就是 n,空间复杂度和时间复杂度打底就是 $n$ 了。(仍然不考虑滚动数组优化) 再举个例子:62. 不同路径 12345一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径? 这道题是和上面的爬楼梯很像,只不过从一维变成了二维,我把它叫做二维爬楼梯,类似的换皮题还很多,大家慢慢体会。 这道题我定义状态为 f(i, j) 表示机器人到达点 (i,j) 的总的路径数。那么状态总数就是 i 和 j 的取值的笛卡尔积,也就是 m * n 。 总的来说,动态规划的空间和时间复杂度打底就是状态的个数,而状态的个数通常是参数的笛卡尔积,这是由动态规划的无后向性决定的。 临界条件是比较最容易的 当你定义好了状态,剩下就三件事了: 临界条件 状态转移方程 枚举状态 在上面讲解的爬楼梯问题中,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么: 12f(1) 与 f(2) 就是【边界】f(n) = f(n-1) + f(n-2) 就是【状态转移公式】 我用动态规划的形式表示一下: 12dp[0] 与 dp[1] 就是【边界】dp[n] = dp[n - 1] + dp[n - 2] 就是【状态转移方程】 可以看出记忆化递归和动态规划是多么的相似。 实际上临界条件相对简单,大家只有多刷几道题,里面就有感觉。困难的是找到状态转移方程和枚举状态。这两个核心点的都建立在已经抽象好了状态的基础上。比如爬楼梯的问题,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么 f(1), f(2), … 就是各个独立的状态。 搞定了状态的定义,那么我们来看下状态转移方程。 状态转移方程动态规划中当前阶段的状态往往是上一阶段状态和上一阶段决策的结果。这里有两个关键字,分别是 : 上一阶段状态 上一阶段决策 也就是说,如果给定了第 k 阶段的状态 s[k] 以及决策 choice(s[k]),则第 k+1 阶段的状态 s[k+1] 也就完全确定,用公式表示就是:s[k] + choice(s[k]) -> s[k+1], 这就是状态转移方程。需要注意的是 choice 可能有多个,因此每个阶段的状态 s[k+1]也会有多个。 继续以上面的爬楼梯问题来说,爬楼梯问题由于上第 n 级台阶一定是从 n - 1 或者 n - 2 来的,因此 上第 n 级台阶的数目就是 上 n - 1 级台阶的数目加上 n - 2 级台阶的数目。 上面的这个理解是核心, 它就是我们的状态转移方程,用代码表示就是 f(n) = f(n - 1) + f(n - 2)。 实际操作的过程,有可能题目和爬楼梯一样直观,我们不难想到。也可能隐藏很深或者维度过高。 如果你实在想不到,可以尝试画图打开思路,这也是我刚学习动态规划时候的方法。当你做题量上去了,你的题感就会来,那个时候就可以不用画图了。 比如我们定义了状态方程,据此我们定义初始状态和目标状态。然后聚焦最优子结构,思考每一个状态究竟如何进行扩展使得离目标状态越来越近。 如下图所示: 理论差不多先这样,接下来来几个实战消化一下。 ok,接下来是解密环节。上面两道题我们都没有讲转移方程,我们在这里补上。 第一道题:《5. 最长回文子串》难度中等。上面我们的两种状态定义都不好,而我可以在上面的基础上稍微变动一点就可以使得转移方程变得非常好写。这个技巧在很多动态题目都有体现,比如最长上升子序列等,需要大家掌握。 以上面提到的 f(start, end) 来说,含义是子串 s[start:end+1]的最长回文子串。表示方式我们不变,只是将含义变成子串 s[start:end+1]的最长回文子串,且必须包含 start 和 end。经过这样的定义,实际上我们也没有必要定义 f(start, end)的返回值是长度了,而仅仅是布尔值就行了。如果返回 true, 则最长回文子串就是 end - start + 1,否则就是 0。 这样转移方程就可以写为: 1f(i,j)=f(i+1,j−1) and s[i] == s[j] 第二道题:《10. 正则表达式匹配》难度困难。 以我们分析的 f(s_start, s_end, p_start, p_end) 来说,含义是子串 p1[p_start:p_end+1] 是否可以匹配字符串 s[s_start:s_end+1]。 实际上,我们可以定义更简单的方式,那就是 f(s_end, p_end),含义是子串 p1[:p_end+1] 是否可以匹配字符串 s[:s_end+1]。也就是说固定起点为索引 0,这同样也是一个很常见的技巧,请务必掌握。 这样转移方程就可以写为: if p[j] 是小写字母,是否匹配取决于 s[i] 是否等于 p[j]: f(i,j)=\\left\\{ \\begin{aligned} f(i-1, j-1) & & s[i] == p[j] \\\\ false & & s[i] != p[j] \\\\ \\end{aligned} \\right. if p[j] == ‘.’,一定可匹配: 1f(i,j)=f(i-1,j−1) if p[j] == ‘*‘,表示 p 可以匹配 s 第 j−1 个字符匹配任意次: f(i,j)=\\left\\{ \\begin{aligned} f(i-1, j) & & 匹配1次以上 \\\\ f(i, j - 2) & & 匹配0次 \\\\ \\end{aligned} \\right.相信你能分析到这里,写出代码就不是难事了。具体代码可参考我的力扣题解仓库,咱就不在这里讲了。 注意到了么?所有的状态转移方程我都使用了上述的数学公式来描述。没错,所有的转移方程都可以这样描述。我建议大家做每一道动态规划题目都写出这样的公式,起初你可能觉得很烦麻烦。不过相信我,你坚持下去,会发现自己慢慢变强大。就好像我强烈建议你每一道题都分析好复杂度一样。动态规划不仅要搞懂转移方程,还要自己像我那样完整地用数学公式写出来。 是不是觉得状态转移方程写起来麻烦?这里我给大家介绍一个小技巧,那就是使用 latex,latex 语法可以方便地写出这样的公式。另外西法还贴心地写了一键生成动态规划转移方程公式的功能,帮助大家以最快速度生成公诉处。 插件地址:https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template 状态转移方程实在是没有什么灵丹妙药,不同的题目有不同的解法。状态转移方程同时也是解决动态规划问题中最最困难和关键的点,大家一定要多多练习,提高题感。接下来,我们来看下不那么困难,但是新手疑问比较多的问题 - 如何枚举状态。 当然状态转移方程可能不止一个, 不同的转移方程对应的效率也可能大相径庭,这个就是比较玄学的话题了,需要大家在做题的过程中领悟。 如何枚举状态前面说了如何枚举状态,才能不重不漏是枚举状态的关键所在。 如果是一维状态,那么我们使用一层循环可以搞定。 12for i in range(1, n + 1): pass 如果是两维状态,那么我们使用两层循环可以搞定。 123for i in range(1, m + 1): for j in range(1, n + 1): pass 。。。 但是实际操作的过程有很多细节比如: 一维状态我是先枚举左边的还是右边的?(从左到右遍历还是从右到左遍历) 二维状态我是先枚举左上边的还是右上的,还是左下的还是右下的? 里层循环和外层循环的位置关系(可以互换么) 。。。 其实这个东西和很多因素有关,很难总结出一个规律,而且我认为也完全没有必要去总结规律。 不过这里我还是总结了一个关键点,那就是: 如果你没有使用滚动数组的技巧,那么遍历顺序取决于状态转移方程。比如: 12for i in range(1, n + 1): dp[i] = dp[i - 1] + 1 那么我们就需要从左到右遍历,原因很简单,因为 dp[i] 依赖于 dp[i - 1],因此计算 dp[i] 的时候, dp[i - 1] 需要已经计算好了。 二维的也是一样的,大家可以试试。 如果你使用了滚动数组的技巧,则怎么遍历都可以,但是不同的遍历意义通常不不同的。比如我将二维的压缩到了一维: 123for i in range(1, n + 1): for j in range(1, n + 1): dp[j] = dp[j - 1] + 1; 这样是可以的。 dp[j - 1] 实际上指的是压缩前的 dp[i][j - 1] 而: 1234for i in range(1, n + 1): # 倒着遍历 for j in range(n, 0, -1): dp[j] = dp[j - 1] + 1; 这样也是可以的。 但是 dp[j - 1] 实际上指的是压缩前的 dp[i - 1][j - 1]。因此实际中采用怎么样的遍历手段取决于题目。我特意写了一个 【完全背包问题】套路题(1449. 数位成本和为目标值的最大数字 文章,通过一个具体的例子告诉大家不同的遍历有什么实际不同,强烈建议大家看看,并顺手给个三连。 关于里外循环的问题,其实和上面原理类似。 这个比较微妙,大家可以参考这篇文章理解一下 0518.coin-change-2。 小结关于如何确定临界条件通常是比较简单的,多做几个题就可以快速掌握。 关于如何确定状态转移方程,这个其实比较困难。 不过所幸的是,这些套路性比较强, 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 这样遇到新的题目可以往上套, 实在套不出那就先老实画图,不断观察,提高题感。 关于如何枚举状态,如果没有滚动数组, 那么根据转移方程决定如何枚举即可。 如果用了滚动数组,那么要注意压缩后和压缩前的 dp 对应关系即可。 动态规划 VS 记忆化递归上面我们用记忆化递归的问题巧妙地解决了爬楼梯问题。 那么动态规划是怎么解决这个问题呢? 答案也是“查表”,我们平常写的 dp table 就是表,其实这个 dp table 和上面的 memo 没啥差别。 而一般我们写的 dp table,数组的索引通常对应记忆化递归的函数参数,值对应递归函数的返回值。 看起来两者似乎没任何思想上的差异,区别的仅仅是写法?? 没错。不过这种写法上的差异还会带来一些别的相关差异,这点我们之后再讲。 如果上面的爬楼梯问题,使用动态规划,代码是怎么样的呢?我们来看下: 1234567891011function climbStairs(n) { if (n == 1) return 1; const dp = new Array(n); dp[0] = 1; dp[1] = 2; for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 大家现在不会也没关系,我们将前文的递归的代码稍微改造一下。其实就是将函数的名字改一下: 12345function dp(n) { if (n === 1) return 1; if (n === 2) return 2; return dp(n - 1) + dp(n - 2);} 经过这样的变化。我们将 dp[n] 和 dp(n) 对比看,这样是不是有点理解了呢? 其实他们的区别只不过是递归用调用栈枚举状态, 而动态规划使用迭代枚举状态。 如果需要多个维度枚举,那么记忆化递归内部也可以使用迭代进行枚举,比如最长上升子序列问题。 动态规划的查表过程如果画成图,就是这样的: 虚线代表的是查表过程 滚动数组优化爬楼梯我们并没有必要使用一维数组,而是借助两个变量来实现的,空间复杂度是 O(1)。代码: 12345678910111213141516function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; let a = 1; let b = 2; let temp; for (let i = 3; i <= n; i++) { temp = a + b; a = b; b = temp; } return temp;} 之所以能这么做,是因为爬楼梯问题的状态转移方程中当前状态只和前两个状态有关,因此只需要存储这两个即可。 动态规划问题有很多这种讨巧的方式,这个技巧叫做滚动数组。 这道题目是动态规划中最简单的问题了,因为仅涉及到单个因素的变化,如果涉及到多个因素,就比较复杂了,比如著名的背包问题,挖金矿问题等。 对于单个因素的,我们最多只需要一个一维数组即可,对于如背包问题我们需要二维数组等更高纬度。 回答上面的问题:记忆化递归和动态规划除了一个用递归一个用迭代,其他没差别。那两者有啥区别呢?我觉得最大的区别就是记忆化递归无法使用滚动数组优化(不信你用上面的爬楼梯试一下),记忆化调用栈的开销比较大(复杂度不变,你可以认为空间复杂度常数项更大),不过几乎不至于 TLE 或者 MLE。因此我的建议就是没空间优化需求直接就记忆化,否则用迭代 dp。 再次强调一下: 如果说递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。 记忆化递归和动态规划没有本质不同。都是枚举状态,并根据状态直接的联系逐步推导求解。 动态规划性能通常更好。 一方面是递归的栈开销,一方面是滚动数组的技巧。 动态规划的基本类型 背包 DP(这个我们专门开了一个专题讲) 区间 DP 区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。令状态 $f(i,j)$ 表示将下标位置 $i$ 到 $j$ 的所有元素合并能获得的价值的最大值,那么 $f(i,j)=\\max\\{f(i,k)+f(k+1,j)+cost\\}$,$cost$ 为将这两组元素合并起来的代价。 区间 DP 的特点: 合并:即将两个或多个部分进行整合,当然也可以反过来; 特征:能将问题分解为能两两合并的形式; 求解:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。 推荐两道题: 877. 石子游戏 312. 戳气球 状压 DP 关于状压 DP 可以参考下我之前写过的一篇文章: 状压 DP 是什么?这篇题解带你入门 数位 DP 数位 DP 通常是这:给定一个闭区间 ,让你求这个区间中满足某种条件的数的总数。 推荐一道题 Increasing-Digits 计数 DP 和 概率 DP 这两个我就不多说。因为没啥规律。 之所以列举计数 DP 是因为两个原因: 让大家知道确实有这个题型。 计数是动态规划的副产物。 概率 DP 比较特殊,概率 DP 的状态转移公式一般是说一个状态有多大的概率从某一个状态转移过来,更像是期望的计算,因此也叫期望 DP。 推荐两道题: 91. 解码方法 837. 新 21 点 更多题目类型以及推荐题目见刷题插件的学习路线。插件获取方式:公众号力扣加加回复插件。 什么时候用记忆化递归? 从数组两端同时进行遍历的时候使用记忆化递归方便,其实也就是区间 DP(range dp)。比如石子游戏,再比如这道题 https://binarysearch.com/problems/Make-a-Palindrome-by-Inserting-Characters 如果区间 dp 你的遍历方式大概需要这样: 12345678910class Solution: def solve(self, s): n = len(s) dp = [[0] * n for _ in range(n)] # 右边界倒序遍历 for i in range(n - 1, -1, -1): # 左边界正序遍历 for j in range(i + 1, n): # do something return dp[0][m-1] # 一般都是使用这个区间作为答案 如果使用记忆化递归则不需考虑遍历方式的问题。 代码: 12345678910111213class Solution: def solve(self, s): @lru_cache(None) def helper(l, r): if l >= r: return 0 if s[l] == s[r]: return helper(l + 1, r - 1) return 1 + min(helper(l + 1, r), helper(l, r - 1)) return helper(0, len(s) - 1) 选择 比较离散的时候,使用记忆化递归更好。比如马走棋盘。 那什么时候不用记忆化递归呢?答案是其他情况都不用。因为普通的 dp table 有一个重要的功能,这个功能记忆化递归是无法代替的,那就是滚动数组优化。如果你需要对空间进行优化,那一定要用 dp table。 热身开始理论知识已经差不多了,我们拿一道题来试试手。 我们以一个非常经典的背包问题来练一下手。 题目:322. 零钱兑换 1234567891011给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。 示例 1:输入:coins = [1, 2, 5], amount = 11输出:3解释:11 = 5 + 5 + 1 这道题的参数有两个,一个是 coins,一个是 amount。 我们可以定义状态为 f(i, j) 表示用 coins 的前 i 项找 j 元需要的最少硬币数。那么答案就是 f(len(coins) - 1, amount)。 由组合原理,coins 的所有选择状态是 $2^n$。状态总数就是 i 和 j 的取值的笛卡尔积,也就是 2^len(coins) * (amount + 1)。 减 1 是因为存在 0 元的情况。 明确了这些,我们需要考虑的就是状态如何转移,也就是如何从寻常转移到 f(len(coins) - 1, amount)。 如何确定状态转移方程?我们需要: 聚焦最优子结构 做选择,在选择中取最优解(如果是计数 dp 则进行计数) 对于这道题来说,我们的选择有两种: 选择 coins[i] 不选择 coins[i] 这无疑是完备的。只不过仅仅是对 coins 中的每一项进行选择与不选择,这样的状态数就已经是 $2^n$ 了,其中 n 为 coins 长度。 如果仅仅是这样枚举肯定会超时,因为状态数已经是指数级别了。 而这道题的核心在于 coins[i] 选择与否其实没有那么重要,重要的其实是选择的 coins 一共有多少钱。 因此我们可以定义 f(i, j) 表示选择了 coins 的前 i 项(怎么选的不关心),且组成 j 元需要的最少硬币数。 举个例子来说,比如 coins = [1,2,3] 。那么选择 [1,2] 和 选择 [3] 虽然是不一样的状态,但是我们压根不关心。因为这两者没有区别,我们还是谁对结果贡献大就 pick 谁。 以 coins = [1,2,3], amount = 6 来说,我们可以画出如下的递归树。 (图片来自https://leetcode.com/problems/coin-change/solution/) 因此转移方程就是 min(dp[i][j], dp[i-1][j - coins[j]] + 1),含义就是: min(不选择 coins[j], 选择 coins[j]) 所需最少的硬币数。 用公式表示就是: dp[i]=\\left\\{ \\begin{aligned} min(dp[i][j], dp[i-1][j - coins[j]] + 1) & & j >= coins[j] \\\\ amount + 1 & & j < coins[j] \\\\ \\end{aligned} \\right. amount 表示无解。因为硬币的面额都是正整数,不可能存在一种需要 amount + 1 枚硬币的方案。 代码 记忆化递归: 123456789101112class Solution: def coinChange(self, coins: List[int], amount: int) -> int: @lru_cache(None) def dfs(amount): if amount < 0: return float('inf') if amount == 0: return 0 ans = float('inf') for coin in coins: ans = min(ans, 1 + dfs(amount - coin)) return ans ans = dfs(amount) return -1 if ans == float('inf') else ans 二维 dp: 12345678910111213141516171819202122class Solution: def coinChange(self, coins: List[int], amount: int) -> int: if amount < 0: return - 1 dp = [[amount + 1 for _ in range(len(coins) + 1)] for _ in range(amount + 1)] # 初始化第一行为0,其他为最大值(也就是amount + 1) for j in range(len(coins) + 1): dp[0][j] = 0 for i in range(1, amount + 1): for j in range(1, len(coins) + 1): if i - coins[j - 1] >= 0: dp[i][j] = min( dp[i][j - 1], dp[i - coins[j - 1]][j] + 1) else: dp[i][j] = dp[i][j - 1] return -1 if dp[-1][-1] == amount + 1 else dp[-1][-1] dp[i][j] 依赖于dp[i][j - 1]和 dp[i - coins[j - 1]][j] + 1) 这是一个优化的信号,我们可以将其优化到一维。 一维 dp(滚动数组优化): 1234567891011class Solution: def coinChange(self, coins: List[int], amount: int) -> int: dp = [amount + 1] * (amount + 1) dp[0] = 0 for j in range(len(coins)): for i in range(1, amount + 1): if i >= coins[j]: dp[i] = min(dp[i], dp[i - coins[j]] + 1) return -1 if dp[-1] == amount + 1 else dp[-1] 推荐练习题目最后推荐几道题目给大家,建议大家分别使用记忆化递归和动态规划来解决。如果使用动态规划,则尽可能使用滚动数组优化空间。 0091.decode-ways 0139.word-break 0198.house-robber 0309.best-time-to-buy-and-sell-stock-with-cooldown 0322.coin-change 0416.partition-equal-subset-sum 0518.coin-change-2 总结本篇文章总结了算法中比较常用的两个方法 - 递归和动态规划。递归的话可以拿树的题目练手,动态规划的话则将我上面推荐的刷完,再考虑去刷力扣的动态规划标签即可。 大家前期学习动态规划的时候,可以先尝试使用记忆化递归解决。然后将其改造为动态规划,这样多练习几次就会有感觉。之后大家可以练习一下滚动数组,这个技巧很有用,并且相对来说比较简单。 动态规划的核心在于定义状态,定义好了状态其他都是水到渠成。 动态规划的难点在于枚举所有状态(不重不漏) 和 寻找状态转移方程。 参考 oi-wiki - dp 这个资料推荐大家学习,非常全面。只不过更适合有一定基础的人,大家可以配合本讲义食用哦。 另外,大家可以去 LeetCode 探索中的 递归 I 中进行互动式学习。","categories":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/categories/动态规划/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"为何我刷了很多,遇到新的题还是唯唯诺诺,无法重拳出击?","slug":"out-of-science","date":"2021-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.845Z","comments":true,"path":"2021/04/06/out-of-science/","link":"","permalink":"https://lucifer.ren/blog/2021/04/06/out-of-science/","excerpt":"为何我刷了很多题,遇到新的题还是唯唯诺诺,无法重拳出击?为何一看就会一些就废?这其中又隐藏着怎样的秘密?究竟是道德的沦丧还是人性的扭曲?欢迎收看本期的 走出科学 特别栏目。 今天给大家带来了三位重量级选手,一位体重 98 公斤,一位体重 100 公斤,还有一位体重 98 斤,今天 lucifer 就带大家用同样的招式 KO 他们三位。","text":"为何我刷了很多题,遇到新的题还是唯唯诺诺,无法重拳出击?为何一看就会一些就废?这其中又隐藏着怎样的秘密?究竟是道德的沦丧还是人性的扭曲?欢迎收看本期的 走出科学 特别栏目。 今天给大家带来了三位重量级选手,一位体重 98 公斤,一位体重 100 公斤,还有一位体重 98 斤,今天 lucifer 就带大家用同样的招式 KO 他们三位。 第一位选手 - 84. 柱状图中最大的矩形题目地址https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ 题目描述给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。\b 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。 1234示例:输入:[2,1,5,6,2,3]输出:10 公司 阿里 腾讯 百度 字节 前置知识 单调栈 暴力枚举 - 左右端点法(TLE)思路我们暴力尝试所有可能的矩形。由于矩阵是二维图形, 我我们可以使用左右两个端点来唯一确认一个矩阵。因此我们使用双层循环枚举所有的可能性即可。 而矩形的面积等于(右端点坐标 - 左端点坐标 + 1) * 最小的高度,最小的高度我们可以在遍历的时候顺便求出。 代码1234567891011class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, ans = len(heights), 0 if n != 0: ans = heights[0] for i in range(n): height = heights[i] for j in range(i, n): height = min(height, heights[j]) ans = max(ans, (j - i + 1) * height) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(1)$ 暴力枚举 - 中心扩展法(TLE)思路我们仍然暴力尝试所有可能的矩形。只不过我们这一次从中心向两边进行扩展。对于每一个 i,我们计算出其左边第一个高度小于它的索引 p,同样地,计算出右边第一个高度小于它的索引 q。那么以 i 为最低点能够构成的面积就是(q - p - 1) * heights[i]。 这种算法毫无疑问也是正确的。 我们证明一下,假设 f(i) 表示求以 i 为最低点的情况下,所能形成的最大矩阵面积。那么原问题转化为max(f(0), f(1), f(2), ..., f(n - 1))。 具体算法如下: 我们使用 l 和 r 数组。l[i] 表示 左边第一个高度小于它的索引,r[i] 表示 右边第一个高度小于它的索引。 我们从前往后求出 l,再从后往前计算出 r。 再次遍历求出所有的可能面积,并取出最大的。 代码1234567891011121314151617class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j -= 1 l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j += 1 r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(N)$ 优化中心扩展法(Accepted)思路实际上我们内层循环没必要一步一步移动,我们可以直接将j -= 1 改成 j = l[j], j += 1 改成 j = r[j]。 代码123456789101112131415161718class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j = l[j] l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j = r[j] r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 单调栈(Accepted)思路实际上,读完第二种方法的时候,你应该注意到了。我们的核心是求左边第一个比 i 小的和右边第一个比 i 小的。 如果你熟悉单调栈的话,那么应该会想到这是非常适合使用单调栈来处理的场景。 从左到右遍历柱子,对于每一个柱子,我们想找到第一个高度小于它的柱子,那么我们就可以使用一个单调递增栈来实现。 如果柱子大于栈顶的柱子,那么说明不是我们要找的柱子,我们把它塞进去继续遍历,如果比栈顶小,那么我们就找到了第一个小于的柱子。 对于栈顶元素,其右边第一个小于它的就是当前遍历到的柱子,左边第一个小于它的就是栈中下一个要被弹出的元素,因此以当前栈顶为最小柱子的面积为当前栈顶的柱子高度 * (当前遍历到的柱子索引 - 1 - (栈中下一个要被弹出的元素索引 + 1) + 1),化简一下就是 当前栈顶的柱子高度 * (当前遍历到的柱子索引 - 栈中下一个要被弹出的元素索引 - 1)。 这种方法只需要遍历一次,并用一个栈。由于每一个元素最多进栈出栈一次,因此时间和空间复杂度都是$O(N)$。 为了统一算法逻辑,减少边界处理,我在 heights 首尾添加了两个哨兵元素,这样我们可以保证所有的柱子都会出栈。 代码代码支持: Python,CPP Python Code: 12345678class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans CPP Code: 123456789101112131415161718class Solution {public: int largestRectangleArea(vector<int>& A) { A.push_back(0); int N = A.size(), ans = 0; stack<int> s; for (int i = 0; i < N; ++i) { while (s.size() && A[s.top()] >= A[i]) { int h = A[s.top()]; s.pop(); int j = s.size() ? s.top() : -1; ans = max(ans, h * (i - j - 1)); } s.push(i); } return ans; }}; 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 2020-05-30 更新: 有的观众反应看不懂为啥需要两个哨兵。 其实末尾的哨兵就是为了将栈清空,防止遍历完成栈中还有没参与运算的数据。 而前面的哨兵有什么用呢? 我这里把上面代码进行了拆解: 1234567891011class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights),[0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: a = heights[st[-1]] st.pop() # 如果没有前面的哨兵,这里的 st[-1] 可能会越界。 ans = max(ans, a * (i - 1 - st[-1])) st.append(i) return ans 相关题目 42.trapping-rain-water 第二位选手 - 85. 最大矩形题目地址https://leetcode-cn.com/problems/maximal-rectangle/ 题目描述给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 示例: 输入: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 输出:6 前置知识 单调栈 公司 阿里 腾讯 百度 字节 思路我在 【84. 柱状图中最大的矩形】多种方法(Python3) 使用了多种方法来解决。 然而在这道题,我们仍然可以使用完全一样的思路去完成。本题解是基于那道题的题解来进行的。 拿题目给的例子来说: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 我们逐行扫描得到 84. 柱状图中最大的矩形 中的 heights 数组: 这样我们就可以使用84. 柱状图中最大的矩形 中的解法来进行了,这里我们使用单调栈来解。 下面的代码直接将 84 题的代码封装成 API 调用了。 代码代码支持:Python Python Code: 1234567891011121314151617181920212223class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans def maximalRectangle(self, matrix: List[List[str]]) -> int: m = len(matrix) if m == 0: return 0 n = len(matrix[0]) heights = [0] * n ans = 0 for i in range(m): for j in range(n): if matrix[i][j] == \"0\": heights[j] = 0 else: heights[j] += 1 ans = max(ans, self.largestRectangleArea(heights)) return ans 复杂度分析 时间复杂度:$O(M * N)$ 空间复杂度:$O(N)$ 第三位选手 - 221. 最大正方形题目地址https://leetcode-cn.com/problems/maximal-square/ 题目描述123456789101112在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。示例:输入:1 0 1 0 01 0 1 1 11 1 1 1 11 0 0 1 0输出: 4 前置知识 动态规划 递归 公司 阿里 腾讯 百度 字节 思路看到题看起来和上面的题目类似,只不过把长方形限定为了正方形嘛。 符合直觉的做法是暴力求解处所有的正方形,逐一计算面积,然后记录最大的。这种时间复杂度很高。 我们考虑使用动态规划,我们使用 dp[i][j]表示以 matrix[i][j]为右下角的顶点的可以组成的最大正方形的边长。那么我们只需要计算所有的 i,j 组合,然后求出最大值即可。 我们来看下 dp[i][j] 怎么推导。 首先我们要看 matrix[i][j], 如果 matrix[i][j]等于 0,那么就不用看了,直接等于 0。如果 matrix[i][j]等于 1,那么我们将 matrix[[i][j]分别往上和往左进行延伸,直到碰到一个 0 为止。 如图 dp[3][3] 的计算。 matrix[3][3]等于 1,我们分别往上和往左进行延伸,直到碰到一个 0 为止,上面长度为 1,左边为 3。dp[2][2]等于 1(之前已经计算好了),那么其实这里的瓶颈在于三者的最小值, 即Min(1, 1, 3), 也就是1。 那么 dp[3][3] 就等于Min(1, 1, 3) + 1。 dp[i - 1][j - 1]我们直接拿到,关键是往上和往左进行延伸, 最直观的做法是我们内层加一个循环去做就好了。但是我们仔细观察一下,其实我们根本不需要这样算。 我们可以直接用 dp[i - 1][j]和 dp[i][j -1]。具体就是Min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1。 事实上,这道题还有空间复杂度 O(N)的解法,其中 N 指的是列数。大家可以去这个leetcode 讨论看一下。 关键点解析 DP 递归公式可以利用 dp[i - 1][j]和 dp[i][j -1]的计算结果,而不用重新计算 空间复杂度可以降低到 O(n), n 为列数 代码代码支持:Python,JavaScript: Python Code: 1234567891011121314class Solution: def maximalSquare(self, matrix: List[List[str]]) -> int: res = 0 m = len(matrix) if m == 0: return 0 n = len(matrix[0]) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1 if matrix[i - 1][j - 1] == \"1\" else 0 res = max(res, dp[i][j]) return res ** 2 JavaScript Code: 12345678910111213141516171819202122232425262728293031323334353637/* * @lc app=leetcode id=221 lang=javascript * * [221] Maximal Square *//** * @param {character[][]} matrix * @return {number} */var maximalSquare = function (matrix) { if (matrix.length === 0) return 0; const dp = []; const rows = matrix.length; const cols = matrix[0].length; let max = Number.MIN_VALUE; for (let i = 0; i < rows + 1; i++) { if (i === 0) { dp[i] = Array(cols + 1).fill(0); } else { dp[i] = [0]; } } for (let i = 1; i < rows + 1; i++) { for (let j = 1; j < cols + 1; j++) { if (matrix[i - 1][j - 1] === \"1\") { dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1; max = Math.max(max, dp[i][j]); } else { dp[i][j] = 0; } } } return max * max;}; 复杂度分析 时间复杂度:$O(M * N)$,其中 M 为行数,N 为列数。 空间复杂度:$O(M * N)$,其中 M 为行数,N 为列数。 如果我还想用上面的方法可以么?当然可以! lucifer 我就专门给大家改写了一下,直接将上面的代码复制过来,改了一行就 AC 了。 1234567891011121314151617181920212223242526class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: # 就只改了下面这一行 ans = max(ans, min(heights[st.pop()], (i - st[-1] - 1)) ** 2) st.append(i) return ans def maximalSquare(self, matrix: List[List[str]]) -> int: m = len(matrix) if m == 0: return 0 n = len(matrix[0]) heights = [0] * n ans = 0 for i in range(m): for j in range(n): if matrix[i][j] == \"0\": heights[j] = 0 else: heights[j] += 1 ans = max(ans, self.largestRectangleArea(heights)) return ans 总结上面三道题我都用的是 largestRectangleArea 的核心逻辑,基本上调用下稍微改改就完事了。对于 largestRectangleArea 的解法最明显的莫过于平方级别的暴力,可如果你仔细分析就发现我们可以通过指针不回溯达到降低复杂度的效果。而这里的指针不回溯可以是单调栈,也可以是我给的双指针数组方法。不管用的哪种,区别的只是战术,战略思想是一样的。大家做题的时候也要注重战略,训练战术。 你说这招是不是很好使?学习算法不可贪多,不然嚼不烂。当你细细咀嚼后会发现,其实算法的思想和套路还真不多,至少应付大部分的面试和 OJ 题目不成问题。","categories":[{"name":"走出科学","slug":"走出科学","permalink":"https://lucifer.ren/blog/categories/走出科学/"}],"tags":[{"name":"走出科学","slug":"走出科学","permalink":"https://lucifer.ren/blog/tags/走出科学/"}]},{"title":"lucifer 的面试之路","slug":"interview-road","date":"2021-04-02T16:00:00.000Z","updated":"2023-01-05T12:24:50.186Z","comments":true,"path":"2021/04/03/interview-road/","link":"","permalink":"https://lucifer.ren/blog/2021/04/03/interview-road/","excerpt":"","text":"为什么有这个栏目?关注 lucifer 的同学可能知道,我之前在组织模拟面试。这个活动是我作为面试官虐别人的。很多胖友想看我是如何面试被虐的。可是我想我也没被面试虐过啊(逃~),就算被虐过你们也看不到,所以我也不会承认的。 所以我就打算开这个系列,从网上找一些靠谱的面经。把我自己当成面试者去回答面经的问题,让大家看看我是如何被虐的。 为了更好的节目效果(不会承认是自己懒),我就不提前准备了,直接拿起来面经就开始干了,这样大家的参考价值更高。 本期猎物 虾皮 题目面经来源:https://www.1point3acres.com/bbs/forum.php?mod=viewthread&tid=723973&extra=page%3D1%26filter%3Dsortid%26sortid%3D327%26sortid%3D327 一面 JS 给一个 array [“1”, “2”, “3”]转化为 string “1, 2, 3”,[“1”, “2”, [“1”, “2”], “3”] 转化为 string “1,2,1,2,3”,follow up: 输入的 array 可能有哪些异常情况,该如何处理 其实就是考察 flatten。我的大前端面试宝典收录了这个题。大前端面试宝典地址在文末。 经典问题实现函数柯里化 curry(add()),背了无数遍的实现,秒了 我的大前端面试宝典收录了这个题。大前端面试宝典地址在文末。 基础知识问答: html 渲染流程,follow up: defer 和 async 标签的区别 defer 和 async 都不会组织后面文档的渲染和执行。区别是文档中的 defer 标签会按照在文档中的声明顺序执行,async 则不会。 https 和 http, follow up: 什么是中间人攻击,数字签名证书,RSA 加密过程 JavaScript 有几种数据类型 JavaScript 代码的执行顺序(宏任务,微任务经典背诵) 什么是闭包 一定先搞明白作用域,然后提到词法作用域才是闭包产生的原因。最后讲下闭包的原理和应用。 二面一些 BQ,问的 LZ 有点懵 基础知识问答: React 大致介绍一下 React 的 virtual dom, follow up: 渲染中 reflow 和 repaint 的区别 React 的 render return 的是什么 React native 用过吗(答:没有,但我会用 Java 写 Android)follow up: Recycler View 介绍一下(说好的前端呢) 跑偏了,Java 的垃圾处理(LZ 从 JVM 的内存结构开始讲起,什么新生代老年代元空间,什么标记清除法) 一个像素占用内存大小,答:不知道。follow up:猜一下,LZ 猜测 RGB 三个通道 0-255,256 是 2 的 8 次方,所以一个通道 1byte,三个加上 3 byte。面试官表示还有个透明通道,加上一共 4byte 1 周以后 HR 通知过了 视频解析bilibili 在线观看:https://www.bilibili.com/video/BV1qV411n7Qc 参考 大前端面试宝典 不要再问我头像如何变灰了,试试这几种滤镜吧!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"},{"name":"虾皮","slug":"虾皮","permalink":"https://lucifer.ren/blog/categories/虾皮/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"},{"name":"虾皮","slug":"虾皮","permalink":"https://lucifer.ren/blog/tags/虾皮/"}]},{"title":"春招冲冲冲(网易)","slug":"school-03","date":"2021-03-27T16:00:00.000Z","updated":"2023-01-05T12:24:49.777Z","comments":true,"path":"2021/03/28/school-03/","link":"","permalink":"https://lucifer.ren/blog/2021/03/28/school-03/","excerpt":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是网易。来看看这两家的算法题难度几何吧! ​","text":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是网易。来看看这两家的算法题难度几何吧! ​ 视频地址:https://www.bilibili.com/video/BV14V411e7MF/ 题目来源:https://www.nowcoder.com/discuss/625915 题目一一组数据,判断能组成三角形最多的数,如果有多个,都写下来。 力扣原题 611. 有效三角形的个数 题目描述123456789101112131415给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。示例 1:输入: [2,2,3,4]输出: 3解释:有效的组合是:2,3,4 (使用第一个 2)2,3,4 (使用第二个 2)2,2,3注意:数组长度不超过1000。数组里整数的范围为 [0, 1000]。 前置知识 排序 双指针 二分法 三角形边的关系 暴力法(超时)思路首先要有一个数学前提: 如果三条线段中任意两条的和都大于第三边,那么这三条线段可以组成一个三角形。即给定三个线段 a,b,c,如果满足 a + b > c and a + c > b and b + c > a,则线段 a,b,c 可以构成三角形,否则不可以。 力扣中有一些题目是需要一些数学前提的,不过这些数学前提都比较简单,一般不会超过高中数学知识,并且也不会特别复杂。一般都是小学初中知识即可。 如果你在面试中碰到不知道的数学前提,可以寻求面试官提示试试。 关键点解析 三角形边的关系 三层循环确定三个线段 代码代码支持: Python 1234567891011121314class Solution: def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False if a + b > c and a + c > b and b + c > a: return True return False def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 for i in range(n - 2): for j in range(i + 1, n - 1): for k in range(j + 1, n): if self.is_triangle(nums[i], nums[j], nums[k]): ans += 1 return ans 复杂度分析 时间复杂度:$O(N ^ 3)$,其中 N 为 数组长度。 空间复杂度:$O(1)$ 优化的暴力法思路暴力法的时间复杂度为 $O(N ^ 3)$, 其中 $N$ 最大为 1000。一般来说, $O(N ^ 3)$ 的算法在数据量 <= 500 是可以 AC 的。1000 的数量级则需要考虑 $O(N ^ 2)$ 或者更好的解法。 OK,到这里了。我给大家一个干货。 应该是其他博主不太会提的。原因可能是他们不知道, 也可能是他们觉得太小儿科不需要说。 由于前面我根据数据规模推测到到了解法的复杂度区间是 $N ^ 2$, $N ^ 2 * logN$,不可能是 $N$ (WHY?)。 降低时间复杂度的方法主要有: 空间换时间 和 排序换时间(我们一般都是使用基于比较的排序方法)。而排序换时间仅仅在总体复杂度大于 $O(NlogN)$ 才适用(原因不用多说了吧?)。 这里由于总体的时间复杂度是 $O(N ^ 3)$,因此我自然想到了排序换时间。当我们对 nums 进行一次排序之后,我发现: is_triangle 函数有一些判断是无效的 12345def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False # a + c > b 和 b + c > a 是无效的判断,因为恒成立 if a + b > c and a + c > b and b + c > a: return True return False 因此我们的目标变为找到a + b > c即可,因此第三层循环是可以提前退出的。 123456for i in range(n - 2): for j in range(i + 1, n - 1): k = j + 1 while k < n and num[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 这也仅仅是减枝而已,复杂度没有变化。通过进一步观察,发现 k 没有必要每次都从 j + 1 开始。而是从上次找到的 k 值开始就行。原因很简单, 当 nums[i] + nums[j] > nums[k] 时,我们想要找到下一个满足 nums[i] + nums[j] > nums[k] 的 新的 k 值,由于进行了排序,因此这个 k 肯定比之前的大(单调递增性),因此上一个 k 值之前的数都是无效的,可以跳过。 123456for i in range(n - 2): k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 由于 K 不会后退,因此最内层循环总共最多执行 N 次,因此总的时间复杂度为 $O(N ^ 2)$。 这个复杂度分析有点像单调栈,大家可以结合起来理解。 关键点分析 排序 代码12345678910111213class Solution: def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 nums.sort() for i in range(n - 2): if nums[i] == 0: continue k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:取决于排序算法 题目二给你一个二叉树,实现固定值和的路径,优先层数低的,排在左边的 思路DFS 找出所有的满足和为 target 的,遍历过程维护层数较低的并返回即可。 函数签名为:dfs(node, target, depth),其中 node 为当前的节点, target 为目标和,减到 0 就找到目标路径了,depth 是深度,用于维护层数较低。 也可以使用 BFS 从左到右将 (node, target) 入队即可。 如果还不懂, 建议参考我的树专题 题目三一组数据,找出能组成和被 6 整除的最大值对应的集合 题目描述123456789101112131415161718192021222324给你一个整数数组 nums,请你找出并返回能被六整除的元素最大和。示例 1:输入:nums = [3,6,5,1,8]输出:18解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 6 整除的最大和)。示例 2:输入:nums = [4]输出:0解释:4 不能被 6 整除,所以无法选出数字,返回 0。示例 3:输入:nums = [1,2,3,4,4]输出:12解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 6 整除的最大和)。 提示:1 <= nums.length <= 4 * 10^41 <= nums[i] <= 10^4 前置知识 数组 回溯法 排序 公司 字节 网易有道 暴力法思路力扣类似题 1262. 可被三整除的最大和 这道题是 6 的倍数,而上面的是 3 的倍数。 实际上, 6 的倍数就是在满足三的倍数的条件下,再加上是偶数的条件即可。 这里以 3 的倍数为例,讲一下这道题。 一种方式是找出所有的能够被 3 整除的子集,然后挑选出和最大的。由于我们选出了所有的子集,那么时间复杂度就是 $O(2^N)$ , 毫无疑问会超时。这里我们使用回溯法找子集,如果不清楚回溯法,可以参考我之前的题解,很多题目都用到了,比如78.subsets。 更多回溯题目,可以访问上方链接查看(可以使用一套模板搞定): 代码12345678910111213141516class Solution: def maxSumDivThree(self, nums: List[int]) -> int: self.res = 0 def backtrack(temp, start): total = sum(temp) if total % 3 == 0: self.res = max(self.res, total) for i in range(start, len(nums)): temp.append(nums[i]) backtrack(temp, i + 1) temp.pop(-1) backtrack([], 0) return self.res 减法 + 排序减法的核心思想是,我们求出总和。如果总和不满足题意,我们尝试减去最小的数,使之满足题意。 思路这种算法的思想,具体来说就是: 我们将所有的数字加起来,我们不妨设为 total total 除以 3,得到一个余数 mod, mod 可能值有 0,1,2. 同时我们建立两个数组,一个是余数为 1 的数组 one,一个是余数为 2 的数组 two 如果 mod 为 0,我们直接返回即可。 如果 mod 为 1,我们可以减去 one 数组中最小的一个(如果有的话),或者减去两个 two 数组中最小的(如果有的话),究竟减去谁取决谁更小。 如果 mod 为 2,我们可以减去 two 数组中最小的一个(如果有的话),或者减去两个 one 数组中最小的(如果有的话),究竟减去谁取决谁更小。 由于我们需要取 one 和 two 中最小的一个或者两个,因此对数组 one 和 two 进行排序是可行的,如果基于排序的话,时间复杂度大致为 $O(NlogN)$,这种算法可以通过。 以题目中的例 1 为例: 以题目中的例 2 为例: 代码12345678910111213141516171819202122232425class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [] two = [] total = 0 for num in nums: total += num if num % 3 == 1: one.append(num) if num % 3 == 2: two.append(num) one.sort() two.sort() if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 减法 + 非排序思路上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。 代码123456789101112131415161718192021222324252627282930313233class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [float('inf')] * 2 two = [float('inf')] * 2 total = 0 for num in nums: total += num if num % 3 == 1: if num < one[0]: t = one[0] one[0] = num one[1] = t elif num < one[1]: one[1] = num if num % 3 == 2: if num < two[0]: t = two[0] two[0] = num two[1] = t elif num < two[1]: two[1] = num if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 有限状态机思路我在数据结构与算法在前端领域的应用 - 第二篇 中讲到了有限状态机。 状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。 状态机使用非常广泛,比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。 拿本题中来说,我们从左到右扫描数组的过程,将会不断改变状态机的状态。 我们使用 state 数组来表示本题的状态: state[0] 表示 mod 为 0 的 最大和 state[1] 表示 mod 为 1 的 最大和 state[2] 表示 mod 为 1 的 最大和 我们的状态转移方程就会很容易。说到状态转移方程,你可能会想到动态规划。没错!这种思路可以直接翻译成动态规划,算法完全一样。如果你看过我上面提到的文章,那么状态转移方程对你来说就会很容易。如果你不清楚,那么请往下看: 我们从左往右不断读取数字,我们不妨设这个数字为 num。 如果 num % 3 为 0。 那么我们的 state[0], state[1], state[2] 可以直接加上 num(题目限定了 num 为非负), 因为任何数字加上 3 的倍数之后,mod 3 的值是不变的。 如果 num % 3 为 1。 我们知道 state[2] + num 会变成一个能被三整除的数,但是这个数字不一定比当前的 state[0]大。 代码表示就是max(state[2] + num, state[0])。同理 state[1] 和 state[2] 的转移逻辑类似。 同理 num % 3 为 2 也是类似的逻辑。 最后我们返回 state[0]即可。 代码123456789101112131415161718class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: if num % 3 == 0: state = [state[0] + num, state[1] + num, state[2] + num] if num % 3 == 1: a = max(state[2] + num, state[0]) b = max(state[0] + num, state[1]) c = max(state[1] + num, state[2]) state = [a, b, c] if num % 3 == 2: a = max(state[1] + num, state[0]) b = max(state[2] + num, state[1]) c = max(state[0] + num, state[2]) state = [a, b, c] return state[0] 当然这个代码还可以简化: 1234567891011class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: temp = [0] * 3 for i in range(3): temp[(i + num) % 3] = max(state[(i + num) % 3], state[i] + num) state = temp return state[0] 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 关键点解析 贪婪法 状态机 数学分析 扩展实际上,我们可以采取加法(贪婪策略),感兴趣的可以试一下。 另外如果题目改成了请你找出并返回能被x整除的元素最大和,你只需要将我的解法中的 3 改成 x 即可。 题目四题目描述编辑距离变种,定义了编辑距离和两组字符串长度的比值,参考 https://leetcode-cn.com/problems/edit-distance/ ,只不过增删距离为 1,改距离为 2 思路力扣原题变种 72. 编辑距离 这道题我太熟悉了,这道题用不同的语言我写了不下十次,提供了大概四五种写法(基本思路类似,写法不同)。然而笔试推荐大家记忆化递归来写,毕竟大多数笔试题解题速度比代码运行速度更重要。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122class Solution: @lru_cache(None) def helper(self, word1: str, s1: int, e1: int, word2: str, s2: int, e2: int) -> int: if s1 > e1: return e2 - s2 + 1 elif s2 > e2: return e1 - s1 + 1 c1 = word1[s1] c2 = word2[s2] if c1 == c2: return self.helper(word1, s1 + 1, e1, word2, s2 + 1, e2) else: return ( min( self.helper(word1, s1 + 1, e1, word2, s2, e2) + 1, # delete or add self.helper(word1, s1, e1, word2, s2 + 1, e2) + 1, # delete or add self.helper(word1, s1 + 1, e1, word2, s2 + 1, e2) + 2, # replace ) ) def minDistance(self, word1: str, word2: str) -> int: return self.helper(word1, 0, len(word1) - 1, word2, 0, len(word2) - 1) 复杂度分析 令 m 和 n 分别为两个字符串的长度。 时间复杂度:$O(m * n)$ 空间复杂度:$O(max(m, n))$","categories":[{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/categories/春招/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/tags/春招/"}]},{"title":"几乎刷完了力扣所有的二分题,我发现了这些东西。。。(下)","slug":"binary-search-2","date":"2021-03-22T16:00:00.000Z","updated":"2023-01-07T12:20:39.949Z","comments":true,"path":"2021/03/23/binary-search-2/","link":"","permalink":"https://lucifer.ren/blog/2021/03/23/binary-search-2/","excerpt":"前言大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下) 几乎刷完了力扣所有的二分题,我发现了这些东西。。。(上)","text":"前言大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下) 几乎刷完了力扣所有的二分题,我发现了这些东西。。。(上) 本专题预计分两部分两进行。上一节主要讲述基本概念 和 一个中心。这一节我们继续学习两种二分类型 和四大应用。没有看过上篇的建议先看一下上篇,地址在上面。 如果觉得文章有用,请点赞留言转发一下,让我有动力继续做下去。 上篇回顾上篇主要就是带大家了解几个概念,这些概念对做题极为重要,请务必掌握。接下来讲解了二分法的中心 - 折半,这个中心需要大家做任何二分都要放到脑子中。 二分法的精髓正如开篇提到的二分法是一种让未知世界无机可乘的算法。二分法无论如何我们都可以舍弃一半解,也就是无论如何都可以将解空间砍半。难点就是上面提到的两点:什么条件 和 舍弃哪部分。 接下来,我们继续下篇。下篇注主要内容是两种类型和四大应用。 其中两种类型主要解决的的是:这道题我的解空间以及明确出来了,如何用代码找出具体的值。而四大应用主要解决的是:如何构造解空间(更多的情况则是如何构建有序序列)以及一些变体。 这两部分都是实操性很强的内容。这里我提醒大家,在理解这两部分内容的同时,请大家务必牢记一个中心折半。 两种类型问题定义 这里的问题定义是一个狭义的问题。而如果你理解了这个问题之后,可以将这个具体的问题进行推广以适应更复杂的问题。关于推广,我们之后再谈。 给定一个由数字组成的有序数组 nums,并给你一个数字 target。问 nums 中是否存在 target。如果存在, 则返回其在 nums 中的索引。如果不存在,则返回 - 1。 这是二分查找中最简单的一种形式。当然二分查找也有很多的变形,这也是二分查找容易出错,难以掌握的原因。 常见变体有: 如果存在多个满足条件的元素,返回最左边满足条件的索引。 如果存在多个满足条件的元素,返回最右边满足条件的索引。 数组不是整体有序的。 比如先升序再降序,或者先降序再升序。 将一维数组变成二维数组。 。。。 接下来,我们逐个进行查看。 前提 数组是有序的(如果无序,我们也可以考虑排序,不过要注意排序的复杂度) 这个有序的数组可能是题目直接给的,也可能是你自己构造的。比如求数组的逆序数就可以在自己构造的有序序列上做二分。 术语为了后面描述问题方便,有必要引入一些约定和术语。 二分查找中使用的术语: target —— 要查找的值 index —— 当前位置 l 和 r —— 左右指针 mid —— 左右指针的中点,用来确定我们应该向左查找还是向右查找的索引(其实就是收缩解空间) 值得注意的是,除了 target 是固定不变的,其他都是动态变化的。其中 l 和 r 指的是解空间的上下界,mid 是上下界的中间值, index 是遍历指针,用于控制遍历过程。 查找一个数前面我们已经对问题进行了定义。接下来,我们需要对定义的问题进行分析和求解。 为了更好理解接下来的内容,我们解决最简单的类型 - 查找某一个具体值 。 算法描述: 先从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束; 如果目标元素大于中间元素,那么数组中小于中间元素的值都可以排除(由于数组有序,那么相当于是可以排除数组左侧的所有值),解空间可以收缩为 [mid+1, r]。 如果目标元素小于中间元素,那么数组中大于中间元素的值都可以排除(由于数组有序,那么相当于是可以排除数组右侧的所有值),解空间可以收缩为 [l, mid - 1]。 如果在某一步骤解空间为空,则代表找不到。 举一个具体的例子方便大家增加代入感。假设 nums 为 [1,3,4,6,7,8,10,13,14], target 为 4·。 刚开始数组中间的元素为 7 7 > 4 ,由于 7 右边的数字都大于 7 ,因此不可能是答案。我们将范围缩写到了 7 的左侧。 解空间变成了 [1,3,4,6],此时中间元素为 3。 3 < 4,由于 3 左边的数字都小于 3 ,因此不可能是答案。我们将范围缩写到了 3 的右侧。 解空间变成了 [4,6],此时中间元素为 4,正好是我们要找的,返回其索引 2 即可。 复杂度分析 由于这种搜索算法每一次比较都使搜索范围缩小一半,是典型的二分查找。 平均时间复杂度: $O(logN)$ 最坏时间复杂度: $O(logN)$ 空间复杂度 迭代: $O(1)$ 递归: $O(logN)$(无尾调用消除) 后面的复杂度也是类似的,不再赘述。 思维框架如何将上面的算法转换为容易理解的可执行代码呢? 大家不要小看这样的一个算法。就算是这样一个简简单单,朴实无华的二分查找, 不同的人写出来的差别也是很大的。 如果没有一个思维框架指导你,不同的时间你可能会写出差异很大的代码。这样的话,犯错的几率会大大增加。这里给大家介绍一个我经常使用的思维框架和代码模板。 首先定义解空间为 [left, right],注意是左右都闭合,之后会用到这个点 你可以定义别的解空间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的解空间。 由于定义的解空间为 [left, right],因此当 left <= right 的时候,解空间都不为空,此时我们都需要继续搜索。 也就是说终止搜索条件应该为 left <= right。 举个例子容易明白一点。 比如对于区间 [4,4],其包含了一个元素 4,因此解空间不为空,需要继续搜索(试想 4 恰好是我们要找的 target,如果不继续搜索, 会错过正确答案)。而当解空间为 [left, right) 的时候,同样对于 [4,4],这个时候解空间却是空的,因为这样的一个区间不存在任何数字·。 循环体内,我们不断计算 mid ,并将 nums[mid] 与 目标值比对。 如果 nums[mid] 等于目标值, 则提前返回 mid(只需要找到一个满足条件的即可) 如果 nums[mid] 小于目标值, 说明目标值在 mid 右侧,这个时候解空间可缩小为 [mid + 1, right] (mid 以及 mid 左侧的数字被我们排除在外) 如果 nums[mid] 大于目标值, 说明目标值在 mid 左侧,这个时候解空间可缩小为 [left, mid - 1] (mid 以及 mid 右侧的数字被我们排除在外) 循环结束都没有找到,则说明找不到,返回 -1 表示未找到。 代码模板Java123456789101112131415161718public int binarySearch(int[] nums, int target) { // 左右都闭合的区间 [l, r] int left = 0; int right = nums.length - 1; while(left <= right) { int mid = left + (right - left) / 2; if(nums[mid] == target) return mid; if (nums[mid] < target) // 解空间变为 [mid+1, right] left = mid + 1; if (nums[mid] > target) // 解空间变为 [left, mid - 1] right = mid - 1; } return -1;} Python1234567891011def binarySearch(nums, target): # 左右都闭合的区间 [l, r] l, r = 0, len(nums) - 1 while l <= r: mid = (left + right) >> 1 if nums[mid] == target: return mid # 解空间变为 [mid+1, right] if nums[mid] < target: l = mid + 1 # 解空间变为 [left, mid - 1] if nums[mid] > target: r = mid - 1 return -1 JavaScript123456789101112131415function binarySearch(nums, target) { let left = 0; let right = nums.length - 1; while (left <= right) { const mid = Math.floor(left + (right - left) / 2); if (nums[mid] == target) return mid; if (nums[mid] < target) // 解空间变为 [mid+1, right] left = mid + 1; if (nums[mid] > target) // 解空间变为 [left, mid - 1] right = mid - 1; } return -1;} C++1234567891011121314151617int binarySearch(vector<int>& nums, int target){ if(nums.size() == 0) return -1; int left = 0, right = nums.size() - 1; while(left <= right){ int mid = left + ((right - left) >> 1); if(nums[mid] == target){ return mid; } // 解空间变为 [mid+1, right] else if(nums[mid] < target) left = mid + 1; // 解空间变为 [left, mid - 1] else right = mid - 1; } return -1;} 寻找最左插入位置上面我们讲了寻找满足条件的值。如果找不到,就返回 -1。那如果不是返回 -1,而是返回应该插入的位置,使得插入之后列表仍然有序呢? 比如一个数组 nums: [1,3,4],target 是 2。我们应该将其插入(注意不是真的插入)的位置是索引 1 的位置,即 [1,2,3,4]。因此寻找最左插入位置应该返回 1,而寻找满足条件的位置 应该返回-1。 另外如果有多个满足条件的值,我们返回最左侧的。 比如一个数组 nums: [1,2,2,2,3,4],target 是 2,我们应该插入的位置是 1。 思维框架具体算法: 首先定义解空间为 [left, right],注意是左右都闭合,之后会用到这个点。 你可以定义别的解空间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的解空间。 由于我们定义的解空间为 [left, right],因此当 left <= right 的时候,解空间都不为空。 也就是说我们的终止搜索条件为 left <= right。 当 A[mid] >= x,说明找到一个备胎,我们令 r = mid - 1 将 mid 从解空间排除,继续看看有没有更好的备胎。 当 A[mid] < x,说明 mid 根本就不是答案,直接更新 l = mid + 1,从而将 mid 从解空间排除。 最后解空间的 l 就是最好的备胎,备胎转正。 代码模板Python12345678910def bisect_left(nums, x): # 内置 api bisect.bisect_left(nums, x) # 手写 l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 if A[mid] >= x: r = mid - 1 else: l = mid + 1 return l 寻找最右插入位置思维框架具体算法: 首先定义解空间为 [left, right],注意是左右都闭合,之后会用到这个点。 你可以定义别的解空间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的解空间。 由于我们定义的解空间为 [left, right],因此当 left <= right 的时候,解空间都不为空。 也就是说我们的终止搜索条件为 left <= right。 当 A[mid] > x,说明找到一个备胎,我们令 r = mid - 1 将 mid 从解空间排除,继续看看有没有更好的备胎。 当 A[mid] <= x,说明 mid 根本就不是答案,直接更新 l = mid + 1,从而将 mid 从解空间排除。 最后解空间的 l 就是最好的备胎,备胎转正。 代码模板Python1234567891011def bisect_right(nums, x): # 内置 api bisect.bisect_right(nums, x) # 手写 l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 if A[mid] <= x: l = mid + 1 else: r = mid - 1 return l 以上就是两种二分的基本形式了。而在实际的写代码过程中,我不会使用寻找满足条件的值模板,而是直接使用最左 或者 最右 插入模板。为什么呢?因为后者包含了前者,并还有前者实现不了的功能。比如我要实现寻找满足条件的值,就可直接使用最左插入模板找到插入索引 i,只不过最后判断一下 nums[i] 是否等于 target 即可,如果不等于则返回 -1,否则返回 i。这也是为什么我将二分分为两种类型,而不是三种甚至四种的原因。 另外最左插入和最右插入可以结合使用从而求出有序序列中和 target 相等的数的个数,这在有些时候会是一个考点。代码表示: 1234nums = [1,2,2,2,3,4]i = bisect.bisect_left(nums, 2) # get 1j = bisect.bisect_right(nums, 2) # get 4# j - i 就是 nums 中 2 的个数 为了描述方便,以后所有的最左插入二分我都会简称最左二分,代码上直接用 bisect.bisect_left 表示,而最右插入二分我都会简称最右二分,代码上用 bisect.bisect_right 或者 bisect.bisect 表示。 小结对于二分题目首先要明确解空间,然后根据一定条件(通常是和中间值比较),舍弃其中一半的解。大家可以先从查找满足条件的值的二分入手,进而学习最左和最右二分。同时大家只需要掌握最左和最右二分即可,因为后者功能大于前者。 对于最左和最右二分,简单用两句话总结一下: 最左二分不断收缩右边界,最终返回左边界 最右二分不断收缩左边界,最终返回右边界 四大应用基础知识铺垫了差不多了。接下来,我们开始干货技巧。 接下来要讲的: 能力检测和计数二分本质差不多,都是普通二分 的泛化。 前缀和二分和插入排序二分,本质都是在构建有序序列。 那让我们开始吧。 能力检测二分能力检测二分一般是:定义函数 possible, 参数是 mid,返回值是布尔值。外层根据返回值调整”解空间”。 示例代码(以最左二分为例): 12345678910def ability_test_bs(nums): def possible(mid): pass l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 # 只有这里和最左二分不一样 if possible(mid): l = mid + 1 else: r = mid - 1 return l 和最左最右二分这两种最最基本的类型相比,能力检测二分只是将 while 内部的 if 语句调整为了一个函数罢了。因此能力检测二分也分最左和最右两种基本类型。 基本上大家都可以用这个模式来套。明确了解题的框架,我们最后来看下能力检测二分可以解决哪些问题。这里通过三道题目带大家感受一下,类似的题目还有很多,大家课后自行体会。 875. 爱吃香蕉的珂珂(中等)题目地址https://leetcode-cn.com/problems/koko-eating-bananas/description/ 题目描述1234567891011121314151617181920212223242526272829珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。 珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。 示例 1:输入: piles = [3,6,7,11], H = 8输出: 4示例 2:输入: piles = [30,11,23,4,20], H = 5输出: 30示例 3:输入: piles = [30,11,23,4,20], H = 6输出: 23 提示:1 <= piles.length <= 10^4piles.length <= H <= 10^91 <= piles[i] <= 10^9 前置知识 二分查找 公司 字节 思路题目是让我们求H 小时内吃掉所有香蕉的最小速度。 符合直觉的做法是枚举所有可能的速度,找出所有的可以吃完香蕉的速度,接下来选择最小的速度即可。由于需要返回最小的速度,因此选择从小到大枚举会比较好,因为可以提前退出。 这种解法的时间复杂度比较高,为 $O(N * M)$,其中 N 为 piles 长度, M 为 Piles 中最大的数(也就是解空间的最大值)。 观察到需要检测的解空间是个有序序列,应该想到可能能够使用二分来解决,而不是线性枚举。可以使用二分解决的关键和前面我们简化的二分问题并无二致,关键点在于如果速度 k 吃不完所有香蕉,那么所有小于等于 k 的解都可以被排除。 二分解决的关键在于: 明确解空间。 对于这道题来说, 解空间就是 [1,max(piles)]。 如何收缩解空间。关键点在于如果速度 k 吃不完所有香蕉,那么所有小于等于 k 的解都可以被排除。 综上,我们可以使用最左二分,即不断收缩右边界。 香蕉堆的香蕉个数上限是 10^9, 珂珂这也太能吃了吧? 关键点解析 二分查找模板 代码代码支持:Python,JavaScript Python Code: 1234567891011121314151617class Solution: def solve(self, piles, k): def possible(mid): t = 0 for pile in piles: t += (pile + mid - 1) // mid return t <= k l, r = 1, max(piles) while l <= r: mid = (l + r) // 2 if possible(mid): r = mid - 1 else: l = mid + 1 return l JavaScript Code: 12345678910111213141516171819202122232425262728function canEatAllBananas(piles, H, mid) { let h = 0; for (let pile of piles) { h += Math.ceil(pile / mid); } return h <= H;}/** * @param {number[]} piles * @param {number} H * @return {number} */var minEatingSpeed = function (piles, H) { let lo = 1, hi = Math.max(...piles); // [l, r) , 左闭右开的好处是如果能找到,那么返回 l 和 r 都是一样的,因为最终 l 等于 r。 while (lo <= hi) { let mid = lo + ((hi - lo) >> 1); if (canEatAllBananas(piles, H, mid)) { hi = mid - 1; } else { lo = mid + 1; } } return lo; // 不能选择hi}; 复杂度分析 时间复杂度:$O(max(N, N * logM))$,其中 N 为 piles 长度, M 为 Piles 中最大的数。 空间复杂度:$O(1)$ 最小灯半径(困难)题目描述123456789101112You are given a list of integers nums representing coordinates of houses on a 1-dimensional line. You have 3 street lights that you can put anywhere on the coordinate line and a light at coordinate x lights up houses in [x - r, x + r], inclusive. Return the smallest r required such that we can place the 3 lights and all the houses are lit up.Constraintsn ≤ 100,000 where n is the length of numsExample 1Inputnums = [3, 4, 5, 6]Output0.5ExplanationIf we place the lamps on 3.5, 4.5 and 5.5 then with r = 0.5 we can light up all 4 houses. 前置知识 排序 二分法 二分法思路本题和力扣 475. 供暖器 类似。 这道题的意思是给你一个数组 nums,让你在 [min(nums),max(nums)] 范围内放置 3 个灯,每个灯覆盖半径都是 r,让你求最小的 r。 之所以不选择小于 min(nums) 的位置和大于 max(nums) 的位置是因为没有必要。比如选取了小于 min(nums) 的位置 pos,那么选取 pos 一定不比选择 min(nums) 位置结果更优。 这道题的核心点还是一样的思维模型,即: 确定解空间。这里的解空间其实就是 r。不难看出 r 的下界是 0, 上界是 max(nums) - min(nums)。 没必要十分精准,只要不错过正确解即可,这个我们在前面讲过,这里再次强调一下。 对于上下界之间的所有可能 x 进行枚举(不妨从小到大枚举),检查半径为 x 是否可以覆盖所有,返回第一个可以覆盖所有的 x 即可。 注意到我们是在一个有序序列进行枚举,因此使用二分就应该想到。可使用二分的核心点在于:如果 x 不行,那么小于 x 的所有半径都必然不行。 接下来的问题就是给定一个半径 x,判断其是否可覆盖所有的房子。 判断其是否可覆盖就是所谓的能力检测,我定义的函数 possible 就是能力检测。 首先对 nums 进行排序,这在后面会用到。 然后从左开始模拟放置灯。先在 nums[0] + r 处放置一个灯,其可以覆盖 [0, 2 r]。由于 nums 已经排好序了,那么这个等可以覆盖到的房间其实就是 nums 中坐标小于等于 2 \\ r 所有房间,使用二分查找即可。对于 nums 右侧的所有的房间我们需要继续放置灯,采用同样的方式即可。 能力检测核心代码: 12345678910def possible(diameter): start = nums[0] end = start + diameter for i in range(LIGHTS): idx = bisect_right(nums, end) if idx >= N: return True start = nums[idx] end = start + diameter return False 由于我们想要找到满足条件的最小值,因此可直接套用最左二分模板。 代码代码支持:Python3 Python3 Code: 123456789101112131415161718192021222324252627class Solution: def solve(self, nums): nums.sort() N = len(nums) if N <= 3: return 0 LIGHTS = 3 # 这里使用的是直径,因此最终返回需要除以 2 def possible(diameter): start = nums[0] end = start + diameter for i in range(LIGHTS): idx = bisect_right(nums, end) if idx >= N: return True start = nums[idx] end = start + diameter return False l, r = 0, nums[-1] - nums[0] while l <= r: mid = (l + r) // 2 if possible(mid): r = mid - 1 else: l = mid + 1 return l / 2 复杂度分析 令 n 为数组长度。 时间复杂度:由于进行了排序, 因此时间复杂度大约是 $O(nlogn)$ 空间复杂度:取决于排序的空间消耗 778. 水位上升的泳池中游泳(困难)题目地址https://leetcode-cn.com/problems/swim-in-rising-water 题目描述123456789101112131415161718192021222324252627282930313233在一个 N x N 的坐标方格 grid 中,每一个方格的值 grid[i][j] 表示在位置 (i,j) 的平台高度。现在开始下雨了。当时间为 t 时,此时雨水导致水池中任意位置的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。你从坐标方格的左上平台 (0,0) 出发。最少耗时多久你才能到达坐标方格的右下平台 (N-1, N-1)?示例 1:输入: [[0,2],[1,3]]输出: 3解释:时间为 0 时,你位于坐标方格的位置为 (0, 0)。此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置示例 2:输入: [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]输出: 16解释:0 1 2 3 424 23 22 21 512 13 14 15 1611 17 18 19 2010 9 8 7 6最终的路线用加粗进行了标记。我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的提示:2 <= N <= 50.grid[i][j] 位于区间 [0, ..., N*N - 1] 内。 前置知识 DFS 二分 思路首先明确一下解空间。不难得出,解空间是[0, max(grid)],其中 max(grid) 表示 grid 中的最大值。 因此一个简单的思路是一个个试。 试试 a 可以不 试试 a+1 可以不 。。。 试试 x 是否可行就是能力检测。 实际上,如果 x 不可以,那么小于 x 的所有值都是不可以的,这正是本题的突破口。基于此,我们同样可使用讲义中的最左二分模板解决。 伪代码: 123456789def test(x): passwhile l <= r: mid = (l + r) // 2 if test(mid, 0, 0): r = mid - 1 else: l = mid + 1return l 这个模板会在很多二分中使用。比如典型的计数型二分,典型的就是计算小于等于 x 的有多少,然后根据答案更新解空间。 明确了这点,剩下要做的就是完成能力检测部分 (test 函数) 了。其实这个就是一个普通的二维网格 dfs,我们从 (0,0) 开始在一个二维网格中搜索,直到无法继续或达到 (N-1,N-1),如果可以达到 (N-1,N-1),我们返回 true,否则返回 False 即可。对二维网格的 DFS 不熟悉的同学可以看下我之前写的小岛专题 代码1234567891011121314151617181920212223242526class Solution: def swimInWater(self, grid: List[List[int]]) -> int: l, r = 0, max([max(vec) for vec in grid]) seen = set() def test(mid, x, y): if x > len(grid) - 1 or x < 0 or y > len(grid[0]) - 1 or y < 0: return False if grid[x][y] > mid: return False if (x, y) == (len(grid) - 1, len(grid[0]) - 1): return True if (x, y) in seen: return False seen.add((x, y)) ans = test(mid, x + 1, y) or test(mid, x - 1, y) or test(mid, x, y + 1) or test(mid, x, y - 1) return ans while l <= r: mid = (l + r) // 2 if test(mid, 0, 0): r = mid - 1 else: l = mid + 1 seen = set() return l 复杂度分析 时间复杂度:$O(NlogM)$,其中 M 为 grid 中的最大值, N 为 grid 的总大小。 空间复杂度:$O(N)$,其中 N 为 grid 的总大小。 计数二分计数二分和上面的思路已经代码都基本一致。 直接看代码会清楚一点: 12345678910def count_bs(nums, k): def count_not_greater(mid): pass l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 # 只有这里和最左二分不一样 if count_not_greater(mid) > k: r = mid - 1 else: l = mid + 1 return l 可以看出只是将 possible 变成了 count_not_greater,返回值变成了数字而已。 实际上,我们可以将上面的代码稍微改造一下,使得两者更像: 12345678910def count_bs(nums, k): def possible(mid, k): # xxx return cnt > k l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 if possible(mid, k): r = mid - 1 else: l = mid + 1 return l 是不是基本一致了? 由于和上面基本一致, 因此这里直接推荐一个题目,大家用我的思路练习一下,看看我的技巧灵不灵。 第 k 小的距离对 前缀和二分前面说了:如果数组全是正的,那么其前缀和就是一个严格递增的数组,基于这个特性,我们可以在其之上做二分。类似的有单调栈/队列。这种题目类型很多,为了节省篇幅就不举例说明了。提出前缀和二分的核心的点在于让大家保持对有序序列的敏感度。 插入排序二分除了上面的前缀和之外,我们还可以自行维护有序序列。一般有两种方式: 直接对序列排序。 代码表示: 123nums.sort()bisect.bisect_left(nums, x) # 最左二分bisect.bisect_right(nums, x) # 最右二分 遍历过程维护一个新的有序序列,有序序列的内容为已经遍历过的值的集合。 比如无序数组 [3,2,10,5],遍历到索引为 2 的项(也就是值为 10 的项)时,我们构建的有序序列为 [2,3,10]。 注意我描述的是有序序列,并不是指数组,链表等具体的数据结构。而实际上,这个有序序列很多情况下是平衡二叉树。后面题目会体现这一点。 代码表示: 123d = SortedList()for a in A: d.add(a) # 将 a 添加到 d,并维持 d 中数据有序 上面代码的 d 就是有序序列。 理论知识到此为止,接下来通过一个例子来说明。 327. 区间和的个数(困难)题目地址https://leetcode-cn.com/problems/count-of-range-sum 题目描述1234567891011121314151617181920212223242526272829给定一个整数数组 nums 。区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。请你以下标 i (0 <= i <= nums.length )为起点,元素个数逐次递增,计算子数组内的元素和。当元素和落在范围 [lower, upper] (包含 lower 和 upper)之内时,记录子数组当前最末元素下标 j ,记作 有效 区间和 S(i, j) 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 有效 区间和的个数。 注意:最直观的算法复杂度是 O(n2) ,请在此基础上优化你的算法。 示例:输入:nums = [-2,5,-1], lower = -2, upper = 2,输出:3解释:下标 i = 0 时,子数组 [-2]、[-2,5]、[-2,5,-1],对应元素和分别为 -2、3、2 ;其中 -2 和 2 落在范围 [lower = -2, upper = 2] 之间,因此记录有效区间和 S(0,0),S(0,2) 。下标 i = 1 时,子数组 [5]、[5,-1] ,元素和 5、4 ;没有满足题意的有效区间和。下标 i = 2 时,子数组 [-1] ,元素和 -1 ;记录有效区间和 S(2,2) 。故,共有 3 个有效区间和。 提示:0 <= nums.length <= 10^4 思路题目很好理解。 由前缀和的性质知道:区间 i 到 j(包含)的和 sum(i,j) = pre[j] - pre[i-1],其中 pre[i] 为数组前 i 项的和 0 <= i < n。 但是题目中的数字可能是负数,前缀和不一定是单调的啊?这如何是好呢?答案是手动维护前缀和的有序性。 比如 [-2,5,-1] 的前缀和 为 [-2,3,2],但是我们可以将求手动维护为 [-2,2,3],这样就有序了。但是这丧失了索引信息,因此这个技巧仅适用于无需考虑索引,也就是不需要求具体的子序列,只需要知道有这么一个子序列就行了,具体是哪个,我们不关心。 比如当前的前缀和是 cur,那么前缀和小于等于 cur - lower 有多少个,就说明以当前结尾的区间和大于等于 lower 的有多少个。类似地,前缀和小于等于 cur - upper 有多少个,就说明以当前结尾的区间和大于等于 upper 的有多少个。 基于这个想法,我们可使用二分在 $logn$ 的时间快速求出这两个数字,使用平衡二叉树代替数组可使得插入的时间复杂度降低到 $O(logn)$。Python 可使用 SortedList 来实现, Java 可用 TreeMap 代替。 代码123456789from sortedcontainers import SortedListclass Solution: def countRangeSum(self, A: List[int], lower: int, upper: int) -> int: ans, pre, cur = 0, [0], 0 for a in A: cur += a ans += pre.bisect_right(cur - lower) - pre.bisect_left(cur - upper) pre.add(cur) return ans 复杂度分析 令 n 为数组长度。 时间复杂度:$O(nlogn)$ 空间复杂度:$O(nlogn)$ 493. 翻转对(困难)题目地址https://leetcode-cn.com/problems/reverse-pairs/ 题目描述1234567891011121314151617181920给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。你需要返回给定数组中的重要翻转对的数量。示例 1:输入: [1,3,2,3,1]输出: 2示例 2:输入: [2,4,3,5,1]输出: 3注意:给定数组的长度不会超过50000。输入数组中的所有数字都在32位整数的表示范围内。 前置知识 二分 公司 暂无 思路我们可以一边遍历一边维护一个有序序列 d,其中 d 为已经遍历过的值的集合。对于每一个位置 0 <= i < n,我们统计 d 中大于 2 * A[i] 的个数,这个个数就是题目要求的翻转对。这里的关键在于 d 中的值是比当前索引小的全部值。 我们当然可以线性遍历 d,求出个数。一个更好的方法是在遍历的同时维持 d 是有序的,这样我们就可以用二分了。和上面题目一样,使用平衡二叉树代替数组可使得插入的时间复杂度降低到 $O(logn)$。 关键点 插入排序二分 代码 语言支持:Python3 Python3 Code: 123456789from sortedcontainers import SortedListclass Solution: def reversePairs(self, A: List[int]) -> int: d = SortedList() ans = 0 for a in A: ans += len(d) - d.bisect_right(2*a) d.add(a) return ans 复杂度分析 令 n 为数组长度。 时间复杂度:$O(nlogn)$ 空间复杂度:$O(n)$ 小结四个应用讲了两种构造有序序列的方式,分别是前缀和,插入排序,插入排序的部分其实也可以看下我之前写的最长上升子序列系列,那里面的贪心解法就是自己构造有序序列再二分的。 另外理论上单调栈/队列也是有序的,也可是用来做二分,但是相关题目太少了,因此大家只要保持对有序序列的敏感度即可。 能力检测二分很常见,不过其仅仅是将普通二分的 if 部分改造成了函数而已。而对于计数二分,其实就是能力检测二分的特例,只不过其太常见了,就将其单独提取出来了。 另外,有时候有序序列也会给你稍微变化一种形式。比如二叉搜索树,大家都知道可以在 $logn$ 的时间完成查找,这个查找过程本质也是二分。二叉查找树有有序序列么?有的!二叉查找树的中序遍历恰好就是一个有序序列。因此如果一个数比当前节点值小,一定在左子树(也就是有序序列的左侧),如果一个数比当前节点值大,一定在右子树(也就是有序序列的右侧)。 总结本文主要讲了两种二分类型:最左和最右,模板已经给大家了,大家只需要根据题目调整解空间和判断条件即可。关于四种应用更多的还是让大家理解二分的核心折半。表面上来看,二分就是对有序序列的查找。其实不然,只不过有序序列很容易做二分罢了。因此战术上大家保持对有序序列的敏感度,战略上要明确二分的本质是折半,核心在于什么时候将哪一半折半。 一个问题能否用二分解决的关键在于检测一个值的时候是否可以排除解空间中的一半元素。比如我前面反复提到的如果 x 不行,那么解空间中所有小于等于 x 的值都不行。 对于简单题目,通常就是给你一个有序序列,让你在上面找满足条件的位置。顶多变化一点,比如数组局部有序,一维变成二维等。对于这部分可以看下我写的91 算法 - 二分查找讲义 中等题目可能需要让你自己构造有序序列。 困难题则可能是二分和其他专题的结合,比如上面的 778. 水位上升的泳池中游泳(困难),就是二分和搜索(我用的是 DFS)的结合。 以上就是本文的全部内容了, 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。我是 lucifer,维护西湖区最好的算法题解,Github 超 40K star 。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 另外我整理的 1000 多页的电子书已限时免费下载,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/categories/二分/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/tags/二分/"}]},{"title":"力扣刷题插件近期更新盘点","slug":"leetcode-cheat-update-1","date":"2021-03-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.826Z","comments":true,"path":"2021/03/16/leetcode-cheat-update-1/","link":"","permalink":"https://lucifer.ren/blog/2021/03/16/leetcode-cheat-update-1/","excerpt":"刷题插件更新日志。","text":"刷题插件更新日志。 手撕算法系列插件增加了手撕算法系列。那么作为第一篇手撕算法上线的就是我们的排序算法。 排序算法目前我提供了五种排序算法,它们分别是: 归并排序(推荐!其他排序方法都不推荐在竞赛中使用) 快速排序 插入排序 选择排序 冒泡排序 每一种排序都对数组和链表两种数据结构进行了支持。 同时,为了对新手更加优化, 对于归并排序和快速排序都提供了乞丐版。乞丐版的代码更容易理解,对于你看清算法的主脉络至关重要。 之后,力扣加加还会为大家带来更多的手撕算法,敬请期待吧!如果你有什么想看的内容,也可以直接和我讲,我会尽量满足大家的。 力扣官方题解配色刷题插件更新新功能啦。本期新增“力扣官方题解”主题。效果图: 学习路线增加二分二分的两种类型,以及四种技巧都包含在内了哦~ 动态规划接下来会进一步完善,比如增加区间二分等。 修复无法复制测试用例 bug力扣官网更新之后和我使用的 antd 的 message 组件冲突,导致代码无法正常运行,部分功能直接失效。目前紧急修复,直接去掉了 antd 的 message 组件。 目前复制测试用例 后并不会弹层提示了(因为 message 组件被我删除了 ^_^)。 插件获取方式公众号力扣加加后台回复插件。 如果你已经了在线版插件只需要等待更新即可,如果按照了离线版,那么可以去仓库下载最新的 crx(仓库地址也是公众号回复插件获取)。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"},{"name":"刷题","slug":"刷题","permalink":"https://lucifer.ren/blog/tags/刷题/"}]},{"title":"春招冲冲冲(钉钉+腾讯)","slug":"school-02","date":"2021-03-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.796Z","comments":true,"path":"2021/03/11/school-02/","link":"","permalink":"https://lucifer.ren/blog/2021/03/11/school-02/","excerpt":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是钉钉和腾讯。来看看这两家的算法题难度几何吧! ​","text":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是钉钉和腾讯。来看看这两家的算法题难度几何吧! ​ 视频地址:https://www.bilibili.com/video/BV1Qy4y187Em/ 钉钉 比较版本号(力扣题号 165.比较版本号) 一次遍历即可,唯一需要注意的是补全再比较(逻辑补全即可,并不一定需要物理上真的去补全),时间复杂度 $O(m + n)$,其中 m 和 n 分别为两个版本号的长度 随机字符串生成(随机生成一个长度为 8 的字符串,要求只能是小写字母和数字,字母和数字可重复,但是生成的随机字符串不能重复) 随机生成一个长度为 8 的字符并将其存到哈希表中,下次生成后判断是否已经在哈希表中了。如果存在,说明之前生成过了,继续生成。注意这种算法存在一直拒绝的可能,代码会无限循环。 日志上报(实现效果:没有数据不上报,有数据每 100ms 批量上报一次) 维护一个窗口,当窗口内有数据才触发请求批量上传,一个窗口的长度为 100m(可能大于) 内的所有请求 腾讯 字符串反转 头尾双指针,不断交换两个指针的字符即可。 链表的倒数第 k 个数 快慢双指针典型题目。 设计求一个数的 n 次开方 典型的二分题目,不会的建议看下我的二分讲义。我的刷题仓库或者公众号搜 二分 就行。 LRU 算法 稍微有点难度了,这个题很常见,难度不小,建议刷。我之前写过题解了,直接甩给大家吧 146. LRU 缓存机制 我给了 JS, Go, PHP, Python3 四种语言,有你的菜么? 手撕一下,就是一个小车给定坐标位置,和当前面朝方向(NSWE),再输入前进转向情况和前进步数,输出小车的坐标位置和面朝方向。 没啥难度,直接模拟。 链表相加 链表和数组本质没有不同,只是具体操作不一样。因此掌握链表基本操作就行了。链表基本操作有哪些?需要注意什么?我的链表专题都给大家总结好了,建议阅读。 leetcode 1567 乘积为正数的最长子数组长度。 题目是:给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。请你返回乘积为正数的最长子数组长度。 这道题是求连续的乘积为正数的最长子数组长度。这里需要一个小学知识,两个符号相同的相乘为正数,两个符号不同的数相乘为负数(先不考虑 0)。 于是直接使用一维 DP + 一层循环即可。 定义状态 positive[i] 为 nums[i] 结尾的乘积为正数的最长子数组长度,negative[i] 为 nums[i] 结尾的乘积为负数的最长子数组长度,于是答案就是 max(positive)。 接下来,遍历 nums,对 nums[i] 的不同取值分类讨论即可: nums[i] > 0 positive[i]=positive[i−1]+1 negative[i]=\\left\\{ \\begin{aligned} negative[i-1] + 1 & & negative[i-1] > 0 \\\\ 0 & & negative[i-1] = 0 \\\\ \\end{aligned} \\right. nums[i] < 0 negative[i]=positive[i−1]+1 positive[i]=\\left\\{ \\begin{aligned} negative[i-1] + 1 & & negative[i-1] > 0 \\\\ 0 & & negative[i-1] = 0 \\\\ \\end{aligned} \\right. nums[i] == 0 negative[i]=positive[i] = 0状态定义一般两种套路: 使用一个二维数组,定义 dp[i][0] 和 定义 dp[i][1] 使用两个一维数组,定义 dp1[i] 和 dp2[i] 两种方式思路一样,只是实操不一样而已。我个人倾向于第二种。比如股票题,我就喜欢定义一个 buy 和 一个 sell 数组。再比如摆动数组,我就喜欢定义一个 up 和 一个 down 数组。 另外如果题目没有限定连续,则需要两层循环和一维 DP(滚动数组优化)。 总结我个人觉得算法题难度是中等,都非常常规,没有什么难以读懂的题目或者冷门知识。 另外我组建了春招群,大家面试遇到不会的题都可以问哦。想进群的可在公众号力扣加加后台回复春招获取小秘书的微信,通过之后再次回复春招入群。 最后祝大家 offer 多多。","categories":[{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/categories/春招/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/tags/春招/"}]},{"title":"几乎刷完了力扣所有的二分题,我发现了这些东西。。。(上)","slug":"binary-search-1","date":"2021-03-07T16:00:00.000Z","updated":"2023-01-05T12:24:49.621Z","comments":true,"path":"2021/03/08/binary-search-1/","link":"","permalink":"https://lucifer.ren/blog/2021/03/08/binary-search-1/","excerpt":"前言 大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下)","text":"前言 大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下) 本专题预计分两部分两进行。第一部分主要讲述基本概念 和 一个中心。有了这些基础知识之后,第二部分我们继续学习两种二分类型 和四大应用。 本文内容已经同步到我的刷题插件的 RoadMap 中,结合刷题插件食用味道更佳哦~ 插件的获取方式可以在我的公众号力扣加加中回复插件查看。 如果觉得文章有用,请点赞留言转发一下,让我有动力继续做下去。 前言为了准备这个专题,我不仅肝完了力扣的所有二分题目,还肝完了另外一个 OJ 网站 - Binary Search 的所有二分题目,一共100 多道。大家看完如果觉得有用,可以通过点赞转发的方式告诉我,如果喜欢的人多,我继续尽快出下篇哦~ 二分查找又称折半搜索算法。 狭义地来讲,二分查找是一种在有序数组查找某一特定元素的搜索算法。这同时也是大多数人所知道的一种说法。实际上, 广义的二分查找是将问题的规模缩小到原有的一半。类似的,三分法就是将问题规模缩小为原来的 1/3。 本文给大家带来的内容则是狭义地二分查找,如果想了解其他广义上的二分查找可以查看我之前写的一篇博文 从老鼠试毒问题来看二分法 尽管二分查找的基本思想相对简单,但细节可以令人难以招架 … — 高德纳 当乔恩·本特利将二分搜索问题布置给专业编程课的学生时,百分之 90 的学生在花费数小时后还是无法给出正确的解答,主要因为这些错误程序在面对边界值的时候无法运行,或返回错误结果。1988 年开展的一项研究显示,20 本教科书里只有 5 本正确实现了二分搜索。不仅如此,本特利自己 1986 年出版的《编程珠玑》一书中的二分搜索算法存在整数溢出的问题,二十多年来无人发现。Java 语言的库所实现的二分搜索算法中同样的溢出问题存在了九年多才被修复。 可见二分查找并不简单, 本文就试图带你走近 ta,明白 ta 的底层逻辑,并提供模板帮助大家写书 bug free 的二分查找代码。看完讲义后建议大家结合 LeetCode Book 二分查找 练习一下。 基本概念首先我们要知道几个基本概念。这些概念对学习二分有着很重要的作用,之后遇到这些概念就不再讲述了,默认大家已经掌握。 解空间解空间指的是题目所有可能的解构成的集合。比如一个题目所有解的可能是 1,2,3,4,5,但具体在某一种情况只能是其中某一个数(即可能是 1,2,3,4,5 中的一个数)。那么这里的解空间就是 1,2,3,4,5 构成的集合,在某一个具体的情况下可能是其中任意一个值,我们的目标就是在某个具体的情况判断其具体是哪个。如果线性枚举所有的可能,就枚举这部分来说时间复杂度就是 $O(n)$。 举了例子: 如果让你在一个数组 nums 中查找 target,如果存在则返回对应索引,如果不存在则返回 -1。那么对于这道题来说其解空间是什么? 很明显解空间是区间 [-1, n-1],其中 n 为 nums 的长度。 需要注意的是上面题目的解空间只可能是区间 [-1,n-1] 之间的整数。而诸如 1.2 这样的小数是不可能存在的。这其实也是大多数二分的情况。 但也有少部分题目解空间包括小数的。如果解空间包括小数,就可能会涉及到精度问题,这一点大家需要注意。 比如让你求一个数 x 的平方根,答案误差在 $10^-6$ 次方都认为正确。这里容易知道其解空间大小可定义为 [1,x](当然可以定义地更精确,之后我们再讨论这个问题),其中解空间应该包括所有区间的实数,不仅仅是整数而已。这个时候解题思路和代码都没有太大变化,唯二需要变化的是: 更新答案的步长。 比如之前的更新是 l = mid + 1,现在可能就不行了,因此这样可能会错过正确解,比如正确解恰好就在区间 [mid,mid+1] 内的某一个小数。 判断条件时候需要考虑误差。由于精度的问题,判断的结束条件可能就要变成 与答案的误差在某一个范围内。 对于搜索类题目,解空间一定是有限的,不然问题不可解。对于搜索类问题,第一步就是需要明确解空间,这样你才能够在解空间内进行搜索。这个技巧不仅适用于二分法,只要是搜索问题都可以使用,比如 DFS,BFS 以及回溯等。只不过对于二分法来说,明确解空间显得更为重要。如果现在还不理解这句话也没关系,看完本文或许你就理解了。 定义解空间的时候的一个原则是: 可以大但不可以小。因为如果解空间偏大(只要不是无限大)无非就是多做几次运算,而如果解空间过小则可能错失正确解,导致结果错误。比如前面我提到的求 x 的平方根,我们当然可以将解空间定义的更小,比如定义为 [1,x/2],这样可以减少运算的次数。但如果设置地太小,则可能会错过正确解。这是新手容易犯错的点之一。 有的同学可能会说我看不出来怎么办呀。我觉得如果你实在拿不准也完全没有关系,比如求 x 的平方根,就可以甚至为 [1,x],就让它多做几次运算嘛。我建议你给上下界设置一个宽泛的范围。等你对二分逐步了解之后可以卡地更死一点。 序列有序我这里说的是序列,并不是数组,链表等。也就是说二分法通常要求的序列有序,不一定是数组,链表,也有可能是其他数据结构。另外有的序列有序题目直接讲出来了,会比较容易。而有些则隐藏在题目信息之中。乍一看,题目并没有有序关键字,而有序其实就隐藏在字里行间。比如题目给了数组 nums,并且没有限定 nums 有序,但限定了 nums 为非负。这样如果给 nums 做前缀和或者前缀或(位运算或),就可以得到一个有序的序列啦。 更多技巧在四个应用部分展开哦。 虽然二分法不意味着需要序列有序,但大多数二分题目都有有序这个显著特征。只不过: 有的是题目直接限定了有序。这种题目通常难度不高,也容易让人想到用二分。 有的是需要你自己构造有序序列。这种类型的题目通常难度不低,需要大家有一定的观察能力。 比如Triple Inversion。题目描述如下: 12345678910Given a list of integers nums, return the number of pairs i < j such that nums[i] > nums[j] * 3.Constraints: n ≤ 100,000 where n is the length of numsExample 1Input:nums = [7, 1, 2]Output:2Explanation:We have the pairs (7, 1) and (7, 2) 这道题并没有限定数组 nums 是有序的,但是我们可以构造一个有序序列 d,进而在 d 上做二分。代码: 12345678910class Solution: def solve(self, A): d = [] ans = 0 for a in A: i = bisect.bisect_right(d, a * 3) ans += len(d) - i bisect.insort(d, a) return ans 如果暂时不理解代码也没关系,大家先留个印象,知道有这么一种类型题即可,大家可以看完本章的所有内容(上下两篇)之后再回头做这道题。 极值类似我在堆专题 提到的极值。只不过这里的极值是静态的,而不是动态的。这里的极值通常指的是求第 k 大(或者第 k 小)的数。 堆的一种很重要的用法是求第 k 大的数,而二分法也可以求第 k 大的数,只不过二者的思路完全不同。使用堆求第 k 大的思路我已经在前面提到的堆专题里详细解释了。那么二分呢?这里我们通过一个例子来感受一下:这道题是 Kth Pair Distance,题目描述如下: 123456789101112131415161718192021Given a list of integers nums and an integer k, return the k-th (0-indexed) smallest abs(x - y) for every pair of elements (x, y) in nums. Note that (x, y) and (y, x) are considered the same pair.Constraints:n ≤ 100,000 where n is the length of numsExample 1Input:nums = [1, 5, 3, 2]k = 3Output:2Explanation:Here are all the pair distances:abs(1 - 5) = 4abs(1 - 3) = 2abs(1 - 2) = 1abs(5 - 3) = 2abs(5 - 2) = 3abs(3 - 2) = 1Sorted in ascending order we have [1, 1, 2, 2, 3, 4]. 简单来说,题目就是给的一个数组 nums,让你求 nums 第 k 大的任意两个数的差的绝对值。当然,我们可以使用堆来做,只不过使用堆的时间复杂度会很高,导致无法通过所有的测试用例。这道题我们可以使用二分法来降维打击。 对于这道题来说,解空间就是从 0 到数组 nums 中最大最小值的差,用区间表示就是 [0, max(nums) - min(nums)]。明确了解空间之后,我们就需要对解空间进行二分。对于这道题来说,可以选当前解空间的中间值 mid ,然后计算小于等于这个中间值的任意两个数的差的绝对值有几个,我们不妨令这个数字为 x。 如果 x 大于 k,那么解空间中大于等于 mid 的数都不可能是答案,可以将其舍弃。 如果 x 小于 k,那么解空间中小于等于 mid 的数都不可能是答案,可以将其舍弃。 如果 x 等于 k,那么 mid 就是答案。 基于此,我们可使用二分来解决。这种题型,我总结为计数二分。我会在后面的四大应用部分重点讲解。 代码: 1234567891011121314151617181920class Solution: def solve(self, A, k): A.sort() def count_not_greater(diff): i = ans = 0 for j in range(1, len(A)): while A[j] - A[i] > diff: i += 1 ans += j - i return ans l, r = 0, A[-1] - A[0] while l <= r: mid = (l + r) // 2 if count_not_greater(mid) > k: r = mid - 1 else: l = mid + 1 return l 如果暂时不理解代码也没关系,大家先留个印象,知道有这么一种类型题即可,大家可以看完本章的所有内容(上下两篇)之后再回头做这道题。 一个中心二分法的一个中心大家一定牢牢记住。其他(比如序列有序,左右双指针)都是二分法的手和脚,都是表象,并不是本质,而折半才是二分法的灵魂。 前面已经给大家明确了解空间的概念。而这里的折半其实就是解空间的折半。 比如刚开始解空间是 [1, n](n 为一个大于 n 的整数)。通过某种方式,我们确定 [1, m] 区间都不可能是答案。那么解空间就变成了 (m,n],持续此过程知道解空间变成平凡(直接可解)。 注意区间 (m,n] 左侧是开放的,表示 m 不可能取到。 显然折半的难点是根据什么条件舍弃哪一步部分。这里有两个关键字: 什么条件 舍弃哪部分 几乎所有的二分的难点都在这两个点上。如果明确了这两点,几乎所有的二分问题都可以迎刃而解。幸运的是,关于这两个问题的答案通常都是有限的,题目考察的往往就是那几种。这其实就是所谓的做题套路。关于这些套路,我会在之后的四个应用部分给大家做详细介绍。 二分法上篇小结上篇主要就是带大家了解几个概念,这些概念对做题极为重要,请务必掌握。接下来讲解了二分法的中心 - 折半,这个中心需要大家做任何二分都要放到脑子中。 如果让我用一句话总结二分法,我会说二分法是一种让未知世界无机可乘的算法。即二分法无论如何我们都可以舍弃一半解,也就是无论如何都可以将解空间砍半。难点就是上面提到的两点:什么条件 和 舍弃哪部分。这是二分法核心要解决的问题。 以上就是《二分专题(上篇)》的所有内容。如果觉得文章有用,请点赞留言转发一下,让我有动力继续出下集。 下集预告上集介绍的是基本概念。下一集我们介绍两种二分的类型以及四种二分的应用。 下集目录: 两种类型 最左插入 最右插入 四大应用 能力检测二分 前缀和二分 插入排序二分(不是你理解的插入排序哦) 计数二分 其中两种类型(最左和最右插入)主要解决的的是:解空间已经明确出来了,如何用代码找出具体的解。 而四大应用主要解决的是:如何构造解空间。更多的情况则是如何构建有序序列。 这两部分都是实操性很强的内容,在理解这两部分内容的同时,请大家务必牢记一个中心折半。那我们下篇见喽~","categories":[{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/categories/二分/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/tags/二分/"}]},{"title":"春招冲冲冲","slug":"school-01","date":"2021-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.894Z","comments":true,"path":"2021/03/04/school-01/","link":"","permalink":"https://lucifer.ren/blog/2021/03/04/school-01/","excerpt":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是虾皮。来看看虾皮的算法题难度几何吧! ​","text":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是虾皮。来看看虾皮的算法题难度几何吧! ​ 视频地址:https://www.bilibili.com/video/BV1bp4y1H7KT/ 视频中涉及到的知识有: dfs 回溯 滑动窗口 排序 平衡二叉树 二分 我个人觉得算法题难度是中等,都非常常规,没有什么难以读懂的题目或者冷门知识。 另外我组建了春招群,大家面试遇到不会的题都可以问哦。 如果显示满了或者过期可在公众号后台回复春招获取小秘书的微信,通过之后再次回复春招入群。 最后祝大家 offer 多多。","categories":[{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/categories/春招/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/tags/春招/"}]},{"title":"91 天学算法第三期视频会议总结","slug":"91meeting-season-3-1","date":"2021-02-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.888Z","comments":true,"path":"2021/03/01/91meeting-season-3-1/","link":"","permalink":"https://lucifer.ren/blog/2021/03/01/91meeting-season-3-1/","excerpt":"这是 91 天学算法第三期视频会议的一个文字版总结。想要参加第三期的小伙伴可在公众号后台回复 91 查看参与方式。 大家好,我是 lucifer。前几天给 91 的学员开了一场视频会议,关于目前学习的情况,西法给大家的学习建议以及下期的规划做了简单的讨论。 ​","text":"这是 91 天学算法第三期视频会议的一个文字版总结。想要参加第三期的小伙伴可在公众号后台回复 91 查看参与方式。 大家好,我是 lucifer。前几天给 91 的学员开了一场视频会议,关于目前学习的情况,西法给大家的学习建议以及下期的规划做了简单的讨论。 ​ 时间 2021-02-28 10:00 参与人员 91 第三期的部分学员 在线观看 https://www.bilibili.com/video/BV1qK4y1J7DD/ 会议主题基础篇回顾基础篇内容如下: 【91 算法-基础篇】01.数组,栈,队列 【91 算法-基础篇】02.链表 【91 算法-基础篇】03.树 【91 算法-基础篇】04.哈希表 【91 算法-基础篇】05.双指针 【91 算法-基础篇】06.图(加餐) 【待定】模拟和枚举(加餐) 除了加餐,其他都会给大家每日一题的练习时间。对于加餐来说,目前还没有完结,等到完结之后会第一时间在群里通知。 对于基础篇的内容需要大家重点掌握的是队列和栈。理由如下: 数组没啥好讲的,大家用的太多了,唯一需要注意的是各种操作的复杂度。 链表和树我写的两篇专题已经非常清楚了,几乎涉及了所有考点。大家如果对这两部分内容没信息,建议看看我公众号的几乎刷完了系列。 哈希表从战略上考察点基本就是空间换时间,战术上就是统计频率,计算最近或者最远,分桶等几个操作而已,具体可看讲义。 双指针我们会在专题篇分两个小专题讲解,分别是 二分法 和 滑动窗口,因此基础篇没太搞懂也不要紧。 通过几个题目,讲解更有如何有效率的解题以下是视频中提到的题目,每个题目都有一定代表性。 https://binarysearch.com/problems/Split-List-to-Minimize-Largest-Sum 第一题主要告诉大家做题的时候一定要结合讲义,我们出的题通常都是和讲义中的一个或者多个知识点有关。出题的目的就是强化理解讲义中的内容。因此如果看完题目,你完全不知道讲义中有挂内容,很有可能是你自己对讲义的理解不到位。这个时候需要再次阅读讲义或者求助群友和群主。否则你的进步会很慢,这也是一些同学向我反馈跟了很久却没有效果的最主要原因。 https://binarysearch.com/problems/Kth-Largest-Pair-Product/editorials/3567227 这道题其实是想告诉大家: 2.1. 如果一道题没思路。可以通过最最暴力的解法入手,然后找算法瓶颈,根据瓶颈使用适当的数据结构和算法进行优化2.2. 讲义中提到的点,一定要牢记。比如我们讲义中提到了固定小顶堆求第 k 大的数,这道题就是求第 k 大的数,你就应该想到。作为新手我对你们的期望是要想到,不管最终能不能用,想到是必须的。 https://leetcode-cn.com/problems/widest-vertical-area-between-two-points-containing-no-points/ 这道题主要告诉大家题目如果看不懂, 那练习再多都没用,建议大家通过几个文字很多的题目或者带图的题目练练手,提供阅读理解能力。 算法面试题(非编程题)epoll 以一个具体的例子: 《epoll 是如何优化我们的程序的?》 为例,讲解算法面试中问答题。如果你不想八股文式面试, 那么就需要大家对基本的数据结构与算法有很深的理解。而这恰好是基础篇的内容。 下期(专题篇)预告专题篇目录: 二分专题 滑动窗口专题 位运算专题 搜索(BFS、DFS、回溯)专题 背包 动态规划 分治 贪心 这部分的重点是:搜索 和 动态规划(含背包)。为什么这两个重要? 搜索篇重要的原因是其范围太广了,涉及到的知识很多。 动态规划不仅容易考,而且大家普遍认为比较难。(我承认它要比搜索篇难) 而: 二分法,滑动窗口相信你看了我的讲义,不会有太大问题。 位运算则考察频率不那么高,大家可以将优先级适当放低。 分治和贪心算法下限很低,基本看了我的讲义跟着做做题入门是可以的。但是要想精通可是相当难的,但是如果你掌握了搜索篇以及动态规划,那么对你掌握这两个专题会有极大的帮助,这也是为啥我特别强调要掌握搜索篇和动态规划的原因。 插件有什么用? 插件源码:https://github.com/leetcode-pp/leetcode-cheat 插件功能介绍: https://lucifer.ren/blog/2020/08/16/leetcode-cheat/ 插件获取方式:公众号《力扣加加》回复插件。 很多人不会用我的插件功能,尤其是复制测试用例。他们好奇为啥官方有了运行内置用例功能,我还弄个复制所有测试用例的功能。这是因为官方的是运行内置用例,并不会自动复制到自定义用例的输入框中。 这有什么问题呢?比如我执行了内置的用例报错了,报错的用例是 [1,2,3,4],而这个用例不在内置用例中。我肯定要调整代码,调整完毕后继续运行。而我这个时候期望的是运行所有的测试用例 和 [1,2,3,4]这个上次出错的用例。因为我可能为了改这个而导致其他本来可以过的用例现在过不了。 如果这个时候又挂在 [3,2,1] 这个用例。我肯定希望运行所有内置的用例和 [1,2,3,4] 以及 [3,2,1]。而这是官方的功能不具备的。如果想实现这个效果,你需要先点击执行内置用例,再点击执行自定义用例。而我的复制所有内置用例可以很好的解决这个问题。 答疑回答了几个比较具有代表性的问题。有一个问题是:我想往 java 后端发展,求推荐学习路线。 答:关于 java,其实我并不权威,尽管我是 java 入行,但毕竟几年不碰了。但是任何计算机相关岗位的学习我都建议先学基础,比如网络,算法。然后学操作系统和汇编(操作系统和汇编用到了大量数据结构与算法)。接下来可以学习编程语言的语法,语法熟悉就是语言内置的库,然后是三方库,然后是运行时,比如 java 就是 jvm。最后从大的方向将知识串起来。比如你可以从一个经典的问题浏览器输入 url 发生了什么入手来验证自己知识的掌握程度。 其他问题可看视频。 以上就是本文的全部内容了, 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。我是 lucifer,维护西湖区最好的算法题解,Github 超 40K star 。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"如何自动同步博客到 Github 主页?","slug":"github-blog-auto","date":"2021-02-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.568Z","comments":true,"path":"2021/02/27/github-blog-auto/","link":"","permalink":"https://lucifer.ren/blog/2021/02/27/github-blog-auto/","excerpt":"前言Github 支持通过创建同名仓库的形式自定义主页。比如我的 Github 用户名是 azl397985856,那么新建一个名为 azl397985856 的仓库即可。接下来你可以通过修改此仓库的 README 文件来自定义 Github 主页。也就是说,你想要自定义主页就新建一个同名仓库并修改 README 就行了。 修改 README 能玩出什么花样呢?请接着往下看。","text":"前言Github 支持通过创建同名仓库的形式自定义主页。比如我的 Github 用户名是 azl397985856,那么新建一个名为 azl397985856 的仓库即可。接下来你可以通过修改此仓库的 README 文件来自定义 Github 主页。也就是说,你想要自定义主页就新建一个同名仓库并修改 README 就行了。 修改 README 能玩出什么花样呢?请接着往下看。 装修效果先上一下我的装修效果: 开始动手添加数据统计上图的那几个 Github 数据统计以及奖杯使用的是一个外部服务。想要显示哪个就添加相应代码即可: 数据统计: 1<img src=\"https://github-readme-stats.vercel.app/api?username=azl397985856&show_icons=true\" alt=\"logo\" height=\"160\" align=\"right\" style=\"margin: 5px; margin-bottom: 20px;\" /> 注意将 username 改成自己的用户名哦(下面也是一样,不再赘述),不然就显示的 lucifer 我的信息啦。 奖杯: 1<img src=\"https://github-profile-trophy.vercel.app/?username=azl397985856&theme=flat&column=7\" alt=\"logo\" height=\"160\" align=\"center\" style=\"margin: auto; margin-bottom: 20px;\" /> 自动更新博客如上图我的装修主页,其中博客的文章列表不是写死的,而是每隔一个小时定时读取我的博客 内容,并提取前 5 篇文章。 如果你也想要这个功能,就在 README 中添加如下代码即可: 1234## 📕 Latest Blog Posts<!-- BLOG-POST-LIST:START --><!-- BLOG-POST-LIST:END --> 之后读取的博客列表会填充在两个注释之间,也就是说你可以通过改变注释的位置,将其放到页面任意位置。 为了实现每个小时定时更新的功能,我们可以使用 Github Action 的定时任务来实现。 具体操作步骤如下: 接下来将如下内容复制粘贴进去: 123456789101112131415161718name: Blog Postson: # Run workflow automatically schedule: # Runs every hour, on the hour - cron: \"0 * * * *\"jobs: update-readme-with-blog: name: Update this repo's README with latest blog posts runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: gautamkrishnar/blog-post-workflow@master with: # comma-separated list of RSS feed urls feed_list: \"https://lucifer.ren/blog/atom.xml\" 注意:这里的 cron: "0 * * * *" 的意思是每个小时进行一次,并且是每个小时的 0 分。 因为你需要等到下一个整点才能看到效果,有时候 Github 会有延时,晚几分钟也正常,大家不要着急,耐心等待即可。 请将 feed_list 替换为你自己的 RSS 订阅地址。如果有多个订阅地址,则用英文半角逗号分割。 如果你的博客没有 RSS 或者你不知道自己的 RSS 地址就无法使用了哦。我的博客是用 hexo 生成的,因此添加 RSS 就很容易了,如果你的博客是挂到第三方的,也会提供 RSS 地址。比如 CSDN 就提供了 RSS 地址: 由于大家的博客可能都不相同,因此具体大家可以自行搜索。 完整源代码本文所有的代码都可以在如下的代码仓库中找到。 仓库地址:https://github.com/azl397985856/azl397985856 如果在使用过程中碰到其他问题,也欢迎私信我哦~ 最后祝大家都有一个高大上的 Github 主页。","categories":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/categories/Github/"}],"tags":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/tags/Github/"},{"name":"持续集成","slug":"持续集成","permalink":"https://lucifer.ren/blog/tags/持续集成/"}]},{"title":"我是如何用「最大公约数」秒杀算法题的","slug":"gcd","date":"2021-02-23T16:00:00.000Z","updated":"2023-01-07T13:54:06.085Z","comments":true,"path":"2021/02/24/gcd/","link":"","permalink":"https://lucifer.ren/blog/2021/02/24/gcd/","excerpt":"关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。但是却有一些间接需要你求解最大公约数的题目。 比如: 914. 卡牌分组 365. 水壶问题 1071. 字符串的最大公因子 因此如何求解最大公约数就显得重要了。","text":"关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。但是却有一些间接需要你求解最大公约数的题目。 比如: 914. 卡牌分组 365. 水壶问题 1071. 字符串的最大公因子 因此如何求解最大公约数就显得重要了。 如何求最大公约数?定义法123456def GCD(a: int, b: int) -> int: smaller = min(a, b) while smaller: if a % smaller == 0 and b % smaller == 0: return smaller smaller -= 1 复杂度分析 时间复杂度:最好的情况是执行一次循环体,最坏的情况是循环到 smaller 为 1,因此总的时间复杂度为 $O(N)$,其中 N 为 a 和 b 中较小的数。 空间复杂度:$O(1)$。 辗转相除法如果我们需要计算 a 和 b 的最大公约数,运用辗转相除法的话。首先,我们先计算出 a 除以 b 的余数 c,把问题转化成求出 b 和 c 的最大公约数;然后计算出 b 除以 c 的余数 d,把问题转化成求出 c 和 d 的最大公约数;再然后计算出 c 除以 d 的余数 e,把问题转化成求出 d 和 e 的最大公约数。….. 以此类推,逐渐把两个较大整数之间的运算转化为两个较小整数之间的运算,直到两个数可以整除为止。 12def GCD(a: int, b: int) -> int: return a if b == 0 else GCD(b, a % b) 复杂度分析 时间复杂度:$O(log(max(a, b)))$ 空间复杂度:空间复杂度取决于递归的深度,因此空间复杂度为 $O(log(max(a, b)))$ 更相减损术辗转相除法如果 a 和 b 都很大的时候,a % b 性能会较低。在中国,《九章算术》中提到了一种类似辗转相减法的 更相减损术。它的原理是:两个正整数 a 和 b(a>b),它们的最大公约数等于 a-b 的差值 c 和较小数 b 的最大公约数。。 123456def GCD(a: int, b: int) -> int: if a == b: return a if a < b: return GCD(b - a, a) return GCD(a - b, b) 上面的代码会报栈溢出。原因在于如果 a 和 b 相差比较大的话,递归次数会明显增加,要比辗转相除法递归深度增加很多,最坏时间复杂度为 O(max(a, b)))。这个时候我们可以将辗转相除法和更相减损术做一个结合,从而在各种情况都可以获得较好的性能。 形象化解释下面我们对上面的过程进行一个表形象地讲解,实际上这也是教材里面的讲解方式,我只是照搬过来,增加一下自己的理解罢了。我们来通过一个例子来讲解: 假如我们有一块 1680 米 * 640 米 的土地,我们希望讲起分成若干正方形的土地,且我们想让正方形土地的边长尽可能大,我们应该如何设计算法呢? 实际上这正是一个最大公约数的应用场景,我们的目标就是求解 1680 和 640 的最大公约数。 将 1680 米 * 640 米 的土地分割,相当于对将 400 米 * 640 米 的土地进行分割。 为什么呢? 假如 400 米 * 640 米分割的正方形边长为 x,那么有 640 % x == 0,那么肯定也满足剩下的两块 640 米 * 640 米的。 我们不断进行上面的分割: 直到边长为 80,没有必要进行下去了。 实例解析题目描述1234567891011给你三个数字 a,b,c,你需要找到第 n 个(n 从 0 开始)有序序列的值,这个有序序列是由 a,b,c 的整数倍构成的。比如:n = 8a = 2b = 5c = 7由于 2,5,7 构成的整数倍构成的有序序列为 [1, 2, 4, 5, 6, 7, 8, 10, 12, ...],因此我们需要返回 12。注意:我们约定,有序序列的第一个永远是 1。 思路大家可以通过 这个网站 在线验证。 一个简单的思路是使用堆来做,唯一需要注意的是去重,我们可以使用一个哈希表来记录出现过的数字,以达到去重的目的。 代码: 1234567891011121314ss Solution: def solve(self, n, a, b, c): seen = set() h = [(a, a, 1), (b, b, 1), (c, c, 1)] heapq.heapify(h) while True: cur, base, times = heapq.heappop(h) if cur not in seen: n -= 1 seen.add(cur) if n == 0: return cur heapq.heappush(h, (base * (times + 1), base, times + 1)) 对于此解法不理解的可先看下我之前写的 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第二弹) 然而这种做法时间复杂度太高,有没有更好的做法呢? 实际上,我们可对搜索空间进行二分。首先思考一个问题,如果给定一个数字 x,那么有序序列中小于等于 x 的值有几个。 答案是 x // a + x // b + x // c 吗? // 是地板除 可惜不是的。比如 a = 2, b = 4, n = 4,答案显然不是 4 // 2 + 4 // 4 = 3,而是 2。这里出错的原因在于 4 被计算了两次,一次是 $2 2 = 4$,另一次是 $4 1 = 4$。 为了解决这个问题,我们可以通过集合论的知识。 一点点集合知识: 如果把有序序列中小于等于 x 的可以被 x 整除,且是 a 的倍数的值构成的集合为 SA,集合大小为 A 如果把有序序列中小于等于 x 的可以被 x 整除,且是 b 的倍数的值构成的集合为 SB,集合大小为 B 如果把有序序列中小于等于 x 的可以被 x 整除,且是 c 的倍数的值构成的集合为 SC,集合大小为 C 那么最终的答案就是 SA ,SB,SC 构成的大的集合(需要去重)的中的数字的个数,也就是: A + B + C - sizeof(SA \\cap SB) - sizeof(SB \\cap SC) - sizeof(SA \\cap SC) + sizeof(SA \\cap SB \\cap SC)问题转化为 A 和 B 集合交集的个数如何求? A 和 B,B 和 C, A 和 C ,甚至是 A,B,C 的交集求法都是一样的。 实际上, SA 和 SB 的交集个数就是 x // lcm(A, B),其中 lcm 为 A 和 B 的最小公倍数。而最小公倍数则可以通过最大公约数计算出来: 12def lcm(x, y): return x * y // gcd(x, y) 接下来就是二分套路了,二分部分看不懂的建议看下我的二分专题。 代码(Python3)123456789101112131415161718192021class Solution: def solve(self, n, a, b, c): def gcd(x, y): if y == 0: return x return gcd(y, x % y) def lcm(x, y): return x * y // gcd(x, y) def possible(mid): return (mid // a + mid // b + mid // c - mid // lcm(a, b) - mid // lcm(b, c) - mid // lcm(a, c) + mid // lcm(a, lcm(b, c))) >= n l, r = 1, n * max(a, b, c) while l <= r: mid = (l + r) // 2 if possible(mid): r = mid - 1 else: l = mid + 1 return l 复杂度分析 时间复杂度:$logn$。 空间复杂度:gcd 和 lcm 的递归树深度,基本可忽略不计。 总结通过这篇文章,我们不仅明白了最大公约数的概念以及求法。也形象化地感知到了最大公约数计算的原理。最大公约数和最小公倍数是两个相似的概念, 关于最大公约数和最小公倍数的题目在力扣中不算少,大家可以通过数学标签找到这些题。更多关于算法中的数学知识,可以参考这篇文章刷算法题必备的数学考点汇总 这篇文章的第二篇也马上要发布了。","categories":[{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/categories/数学/"},{"name":"算法","slug":"数学/算法","permalink":"https://lucifer.ren/blog/categories/数学/算法/"}],"tags":[{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"最大公约数","slug":"最大公约数","permalink":"https://lucifer.ren/blog/tags/最大公约数/"}]},{"title":"如何求二维数组的前缀和?","slug":"2d-pre","date":"2021-02-19T16:00:00.000Z","updated":"2023-01-07T12:17:01.143Z","comments":true,"path":"2021/02/20/2d-pre/","link":"","permalink":"https://lucifer.ren/blog/2021/02/20/2d-pre/","excerpt":"一维前缀和很容易求,那二维前缀和你会吗?","text":"一维前缀和很容易求,那二维前缀和你会吗? 什么是前缀和?前缀和是一种重要的预处理,能大大降低查询的时间复杂度。我们可以简单理解为“数列的前 n 项的和”。这个概念其实很容易理解,即一个数组中,第 n 位存储的是数组前 n 个数字的和。 通过一个例子来进行说明会更清晰。题目描述:有一个长度为 N 的整数数组 A,要求返回一个新的数组 B,其中 B 的第 i 个数 B[i]是原数组 A 前 i 项和。 这道题实际就是让你求数组 A 的前缀和。对 [1,2,3,4,5,6] 来说,其前缀和可以是 pre=[1,3,6,10,15,21]。我们可以使用公式 pre[𝑖]=pre[𝑖−1]+nums[𝑖]得到每一位前缀和的值,从而通过前缀和进行相应的计算和解题。其实前缀和的概念很简单,但困难的是如何在题目中使用前缀和以及如何使用前缀和的关系来进行解题。实际的题目更多不是直接让你求前缀和,而是你需要自己使用前缀和来优化算法的某一个性能瓶颈。 而如果数组是正数的话,前缀和数组会是一个单调不递减序列,因此前缀和 + 二分也会是一个考点,不过这种题目难度一般是力扣的困难难度。关于这个知识点,我会在之后的二分专题方做更多介绍。 简单的二维前缀和上面提到的例子是一维数组的前缀和,简称一维前缀和。那么二维前缀和实际上就是二维数组上的前缀和了。一维数组的前缀和也是一个一维数组,同样地,二维数组的前缀和也是一个二维的数组。 比如对于如下的一个二维矩阵: 121 2 3 45 6 7 8 定义二维前缀和矩阵 $pres$,$pres{x,y} = \\sum\\limits_{i=1}^x \\sum\\limits_{j=1}^y a_{i,j}$。经过这样的处理,上面矩阵的二维前缀和就变成了: 121 3 6 106 14 24 36 那么如何用代码计算二维数组的前缀和呢?简单的二维前缀和的求解方法是基于容斥原理的。 比如我们想求如图中灰色部分的和。 一种方式就是用下图中两个绿色部分的矩阵加起来(之所以用绿色部分相加是因为这两部分已经通过上面预处理计算好了,可以在 $O(1)$ 的时间得到),这样我们就会多加一块区域,这块区域就是如图黄色部分,我们再减去黄色部分就好了,最后再加上当前位置本身就行了。 比如我们想要求 $sum_{i,j}$,则可以通过 $sum_{i - 1,j} + sum_{i,j - 1} - sum_{i - 1,j - 1} + a_{i,j}$ 的方式来实现。这样我就可以通过 $O(m n)$ 的预处理计算二维前缀和矩阵(m 和 n 分别为矩阵的长和宽),再通过 $O(1)$ 的时间计算出*任意小矩阵的和。其底层原理就是上面提到的容斥原理,大家可以通过画图的方式来感受一下。 如何将二维前缀和转化为一维前缀和然而实际上,我们也可不构建一个前缀和数组,而是直接原地修改。 一维前缀和同样可以采用这一技巧。 比如我们可以先不考虑行之间的关联,而是预先计算出每一行的前缀和。对于计算每一行的前缀和就是一维前缀和啦。接下来通过固定两个列的端点的方式计算每一行的区域和。代码上,我们可以通过三层循环来实现, 其中两层循环用来固定列端点,另一层用于枚举所有行。 其实也可以反过来。即固定行的左右端点并枚举列,下面的题目会提到这一点。 代码表示: 1234# 预先构建行的前缀和for row in matrix: for i in range(n - 1): row[i + 1] += row[i] 比如矩阵: 121 2 3 45 6 7 8 则会变为: 121 3 6 105 11 18 26 接下来: 12345678910# 固定列的两个端点,即枚举所有列的组合for i in range(n): for j in range(i, n): pres = [0] pre = 0 # 枚举所有行 for k in range(m): # matrix[k] 其实已经是上一步预处理的每一行的前缀和了。因此 matrix[k][j] - (matrix[k][i - 1] 就是每一行 [i, j] 的区域和。 pre += matrix[k][j] - (matrix[k][i - 1] if i > 0 else 0) pres.append(pre) 上面代码做的事情形象来看,就是先在水平方向计算前缀和,然后在竖直方向计算前缀和,而不是同时在两个方向计算。 如果把 [i, j] 的区域和看出是一个数的话,问题就和一维前缀和一样了。代码: 12345678910for i in range(n): for j in range(i, n): pres = [0] pre = 0 # 枚举所有行 for k in range(m): # 其中 a 为[i, j] 的区域和 pre += a pres.append(pre) 题目推荐有了上面的知识,我们就可以来解决下面两道题。虽然下面两道题的难度都是 hard,不过总体难度并不高。这两道题之所以是 hard, 是因为其考察了不止一个知识点。这也是 hard 题目的一种类型,即同时考察多个知识点。 363. 矩形区域不超过 K 的最大数值和题目地址https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/ 题目描述12345678910111213给定一个非空二维矩阵 matrix 和一个整数 k,找到这个矩阵内部不大于 k 的最大矩形和。示例:输入: matrix = [[1,0,1],[0,-2,3]], k = 2输出: 2解释: 矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。说明:矩阵内的矩形区域面积必须大于 0。如果行数远大于列数,你将如何解答呢? 前置知识 二维前缀和 二分法 思路前面提到了由于非负数数组的二维前缀和是一个非递减的数组,因此常常和二分结合考察。实际上即使数组不是非负的,我们仍然有可能构建一个有序的前缀和,从而使用二分,这道题就是一个例子。 首先我们可以用上面提到的技巧计算二维数组的前缀和,这样我们就可以计算快速地任意子矩阵的和了。注意到上面我们计算的 pres 数组是一个一维数组,但矩阵其实可能为负数,因此不满足单调性。这里我们可以手动维护 pres 单调递增,这样就可以使用二分法在 $logN$ 的时间求出以当前项 i 结尾的不大于 k 的最大矩形和,那么答案就是所有的以任意索引 x 结尾的不大于 k 的最大矩形和的最大值。 之所以可以手动维护 pres 数组单调增也可得到正确结果的原因是题目只需要求子矩阵和,而不是求具体的子矩阵。 代码上,当计算出 pres 后,我们其实需要寻找大于等于 pre - k 的最小数 x。这样矩阵和 pre - x 才能满足 pre - x <= k,使用最左插入二分模板即可解决。 关键点 典型的二维前缀和 + 二分题目 代码 语言支持:Python3 Python3 Code: 1234567891011121314151617181920212223242526from sortedcontainers import SortedListclass Solution: def maxSumSubmatrix(self, matrix: List[List[int]], K: int) -> int: m, n = len(matrix), len(matrix[0]) for i in range(m): for j in range(1, n): matrix[i][j] += matrix[i][j - 1] ans = float(\"-inf\") for i in range(n): for j in range(i, n): pres = SortedList([0]) pre = 0 for k in range(m): pre += matrix[k][j] - (0 if i == 0 else matrix[k][i - 1]) # 寻找小于等于 pre - k 的最大数。 # 为了达到这个目的,可以使用 bisect_left 来完成。(使用 bisect_right 不包含等号) idx = pres.bisect_left(pre - K) # 如果 i == len(pre) 表示无解 if idx < len(pres): ans = max(ans, pre - pres[idx]) pres.add(pre) return ans 复杂度分析 令 n 为数组长度。 时间复杂度:$O(m*n ^ 2logm)$ 空间复杂度:$O(m)$ 题目给了一个 follow up:如果行数远大于列数,你将如何解答呢? 实际上,如果行数远大于列数,由复杂度分析可知空间复杂度会很高。我们可以将行列兑换,这样空间复杂度是 $O(n)$。换句话说,我们可以通过行列的调换做到空间复杂度为 $O(min(m, n))$。 1074. 元素和为目标值的子矩阵数量题目地址https://leetcode-cn.com/problems/number-of-submatrices-that-sum-to-target/ 题目描述123456789101112131415161718192021222324252627282930给出矩阵 matrix 和目标值 target,返回元素总和等于目标值的非空子矩阵的数量。子矩阵 x1, y1, x2, y2 是满足 x1 <= x <= x2 且 y1 <= y <= y2 的所有单元 matrix[x][y] 的集合。如果 (x1, y1, x2, y2) 和 (x1', y1', x2', y2') 两个子矩阵中部分坐标不同(如:x1 != x1'),那么这两个子矩阵也不同。 示例 1:输入:matrix = [[0,1,0],[1,1,1],[0,1,0]], target = 0输出:4解释:四个只含 0 的 1x1 子矩阵。示例 2:输入:matrix = [[1,-1],[-1,1]], target = 0输出:5解释:两个 1x2 子矩阵,加上两个 2x1 子矩阵,再加上一个 2x2 子矩阵。 提示:1 <= matrix.length <= 3001 <= matrix[0].length <= 300-1000 <= matrix[i] <= 1000-10^8 <= target <= 10^8 前置知识 二维前缀和 思路和上面题目类似。不过这道题是求子矩阵和刚好等于某个目标值的数目。 我们不妨先对问题进行简化。比如题目要求的是一维数组中,子数组(连续)的和等于目标值 target 的数目。我们该如何做? 这很容易,我们只需要: 边遍历边计算前缀和。 比如当前的前缀和是 cur,那么我们要找的前缀和 x 应该满足 cur - x = target,因为这样当前位置和 x 的之间的子数组和才是 target。即我们需要找前缀和为 cur - target 的数目。 这提示我们使用哈希表记录每一种前缀和出现的次数。 由于仅仅是求数目,不涉及到求具体的子矩阵信息,因此使用类似上面的解法求出二维前缀和。接下来,使用和一维前缀和同样的方法即可求出答案。 关键点 主要考察一维前缀和到二维前缀和的过渡是否掌握 代码 语言支持:Python3 Python3 Code: 1234567891011121314151617class Solution: def numSubmatrixSumTarget(self, matrix, target): m, n = len(matrix), len(matrix[0]) for row in matrix: for i in range(n - 1): row[i + 1] += row[i] ans = 0 for i in range(n): for j in range(i, n): c = collections.defaultdict(int) cur, c[0] = 0, 1 for k in range(m): cur += matrix[k][j] - (matrix[k][i - 1] if i > 0 else 0) ans += c[cur - target] c[cur] += 1 return ans 复杂度分析 时间复杂度:$O(m * n ^ 2)$ 空间复杂度:$O(m)$ 和上面一样,我们可以将行列对换,这样空间复杂度是 $O(n)$。换句话说,我们可以通过行列的调换做到空间复杂度为 $O(min(m, n))$。 更多题目 面试题 17.24. 最大子矩阵 (用西法的套路一下子就做出来了) 这道题就是二维前缀和 + 一维最大子序和的知识就可以 AC。而这两个西法我都写过文章了,不懂的建议看看。 参考代码: 1234567891011121314151617181920212223class Solution: def getMaxMatrix(self, matrix: List[List[int]]) -> List[int]: max_area = float(\"-inf\") ans = [] m, n = len(matrix), len(matrix[0]) for i in range(m): for j in range(1, n): matrix[i][j] += matrix[i][j - 1] for i in range(n): for j in range(i, n): pre = min_pre = min_pre_idx = 0 for k in range(m): if pre < min_pre: min_pre = pre min_pre_idx = k pre += matrix[k][j] - (matrix[k][i - 1] if i > 0 else 0) if pre - min_pre > max_area: max_area = pre - min_pre ans = [min_pre_idx, i, k, j] return ans 力扣的小伙伴可以关注我,这样就会第一时间收到我的动态啦~ 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/categories/前缀和/"},{"name":"二维前缀和","slug":"前缀和/二维前缀和","permalink":"https://lucifer.ren/blog/categories/前缀和/二维前缀和/"}],"tags":[{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"}]},{"title":"一个让你的 YouTube 丝滑般柔顺的插件","slug":"youtube-extesion","date":"2021-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.758Z","comments":true,"path":"2021/02/20/youtube-extesion/","link":"","permalink":"https://lucifer.ren/blog/2021/02/20/youtube-extesion/","excerpt":"Youtube 和 B 站是很多人浏览视频的两个地方。 一开始我还挺喜欢 Youtube 的,不仅是因为可以看到一些其他人看不到的东西,也因为体验比较棒。而现在,我已经彻底失望了。每次看一个十分钟的视频被跳出来四次广告的痛苦你了解么?如果你在看一个需要集中精力思考的视频,那么这种打断真的是非常让人恼怒。如果不是实在无法忍受这种行为,我也不会使用一些广告屏蔽的插件,毕竟看广告其实也是对 up 主的另外一种支持。 另外 YouTube 竟然没有小窗播放。比如我下滑到评论区,视频并不会小窗悬浮播放,差评!","text":"Youtube 和 B 站是很多人浏览视频的两个地方。 一开始我还挺喜欢 Youtube 的,不仅是因为可以看到一些其他人看不到的东西,也因为体验比较棒。而现在,我已经彻底失望了。每次看一个十分钟的视频被跳出来四次广告的痛苦你了解么?如果你在看一个需要集中精力思考的视频,那么这种打断真的是非常让人恼怒。如果不是实在无法忍受这种行为,我也不会使用一些广告屏蔽的插件,毕竟看广告其实也是对 up 主的另外一种支持。 另外 YouTube 竟然没有小窗播放。比如我下滑到评论区,视频并不会小窗悬浮播放,差评! 废话不多说了。今天给大家推荐的这个插件名字叫 Enhancer for YouTube。安装地址:https://chrome.google.com/webstore/detail/enhancer-for-youtube/ponfpcnoihfmfllpaingbgckeeldkhle 这个插件有 400,000+用户,超过 9000 人做出了评价,评分是五分(满分五分)。 它提供了很多功能以及众多的配置项,从外观的配置到功能的配置应有尽有。 还可以配置主题,我把主题换成了暗黑模式。 主要功能有: 使用鼠标滚轮控制音量和播放速度 视频去广告(自动或手动) 频道白名单(不自动去广告) 屏蔽注解(自动或手动) 自动切换视频清晰度(可以设定为 4K、HD 或任意清晰度) 循环播放视频(完整循环或部分循环) 使用自定义主题样式 自动拉伸播放器 查看评论时将播放器固定在右下角 执行自定义脚本 。。。 更多功能等待大家的探索。","categories":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"},{"name":"浏览器插件","slug":"插件/浏览器插件","permalink":"https://lucifer.ren/blog/categories/插件/浏览器插件/"}],"tags":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"}]},{"title":"一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~","slug":"删除问题","date":"2021-02-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.714Z","comments":true,"path":"2021/02/20/删除问题/","link":"","permalink":"https://lucifer.ren/blog/2021/02/20/删除问题/","excerpt":"我花了几天时间,从力扣中精选了四道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 这就是接下来要给大家讲的四个题,其中 1081 和 316 题只是换了说法而已。 316. 去除重复字母(困难) 321. 拼接最大数(困难) 402. 移掉 K 位数字(中等) 1081. 不同字符的最小子序列(中等)","text":"我花了几天时间,从力扣中精选了四道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 这就是接下来要给大家讲的四个题,其中 1081 和 316 题只是换了说法而已。 316. 去除重复字母(困难) 321. 拼接最大数(困难) 402. 移掉 K 位数字(中等) 1081. 不同字符的最小子序列(中等) 402. 移掉 K 位数字(中等)我们从一个简单的问题入手,识别一下这种题的基本形式和套路,为之后的三道题打基础。 题目描述1234567891011121314151617181920212223给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。注意:num 的长度小于 10002 且 ≥ k。num 不会包含任何前导零。示例 1 :输入: num = "1432219", k = 3输出: "1219"解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。示例 2 :输入: num = "10200", k = 1输出: "200"解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。示例 3 :输入: num = "10", k = 2输出: "0"解释: 从原数字移除所有的数字,剩余为空就是 0。 前置知识 数学 思路这道题让我们从一个字符串数字中删除 k 个数字,使得剩下的数最小。也就说,我们要保持原来的数字的相对位置不变。 以题目中的 num = 1432219, k = 3 为例,我们需要返回一个长度为 4 的字符串,问题在于: 我们怎么才能求出这四个位置依次是什么呢? (图 1) 暴力法的话,我们需要枚举C_n^(n - k) 种序列(其中 n 为数字长度),并逐个比较最大。这个时间复杂度是指数级别的,必须进行优化。 一个思路是: 从左到右遍历 对于每一个遍历到的元素,我们决定是丢弃还是保留 问题的关键是:我们怎么知道,一个元素是应该保留还是丢弃呢? 这里有一个前置知识:对于两个数 123a456 和 123b456,如果 a > b, 那么数字 123a456 大于 数字 123b456,否则数字 123a456 小于等于数字 123b456。也就说,两个相同位数的数字大小关系取决于第一个不同的数的大小。 因此我们的思路就是: 从左到右遍历 对于遍历到的元素,我们选择保留。 但是我们可以选择性丢弃前面相邻的元素。 丢弃与否的依据如上面的前置知识中阐述中的方法。 以题目中的 num = 1432219, k = 3 为例的图解过程如下: (图 2) 由于没有左侧相邻元素,因此没办法丢弃。 (图 3) 由于 4 比左侧相邻的 1 大。如果选择丢弃左侧的 1,那么会使得剩下的数字更大(开头的数从 1 变成了 4)。因此我们仍然选择不丢弃。 (图 4) 由于 3 比左侧相邻的 4 小。 如果选择丢弃左侧的 4,那么会使得剩下的数字更小(开头的数从 4 变成了 3)。因此我们选择丢弃。 。。。 后面的思路类似,我就不继续分析啦。 然而需要注意的是,如果给定的数字是一个单调递增的数字,那么我们的算法会永远选择不丢弃。这个题目中要求的,我们要永远确保丢弃 k 个矛盾。 一个简单的思路就是: 每次丢弃一次,k 减去 1。当 k 减到 0 ,我们可以提前终止遍历。 而当遍历完成,如果 k 仍然大于 0。不妨假设最终还剩下 x 个需要丢弃,那么我们需要选择删除末尾 x 个元素。 上面的思路可行,但是稍显复杂。 (图 5) 我们需要把思路逆转过来。刚才我的关注点一直是丢弃,题目要求我们丢弃 k 个。反过来说,不就是让我们保留 $n - k$ 个元素么?其中 n 为数字长度。 那么我们只需要按照上面的方法遍历完成之后,再截取前n - k个元素即可。 按照上面的思路,我们来选择数据结构。由于我们需要保留和丢弃相邻的元素,因此使用栈这种在一端进行添加和删除的数据结构是再合适不过了,我们来看下代码实现。 代码(Python)12345678910class Solution(object): def removeKdigits(self, num, k): stack = [] remain = len(num) - k for digit in num: while k and stack and stack[-1] > digit: stack.pop() k -= 1 stack.append(digit) return ''.join(stack[:remain]).lstrip('0') or '0' _复杂度分析_ 时间复杂度:虽然内层还有一个 while 循环,但是由于每个数字最多仅会入栈出栈一次,因此时间复杂度仍然为 $O(N)$,其中 $N$ 为数字长度。 空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 $O(N)$,其中 $N$ 为数字长度。 提示: 如果题目改成求删除 k 个字符之后的最大数,我们只需要将 stack[-1] > digit 中的大于号改成小于号即可。 316. 去除重复字母(困难)题目描述12345678910给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。示例 1:输入: "bcabc"输出: "abc"示例 2:输入: "cbacdcbc"输出: "acdb" 前置知识 字典序 数学 思路与上面题目不同,这道题没有一个全局的删除次数 k。而是对于每一个在字符串 s 中出现的字母 c 都有一个 k 值。这个 k 是 c 出现次数 - 1。 沿用上面的知识的话,我们首先要做的就是计算每一个字符的 k,可以用一个字典来描述这种关系,其中 key 为 字符 c,value 为其出现的次数。 具体算法: 建立一个字典。其中 key 为 字符 c,value 为其出现的剩余次数。 从左往右遍历字符串,每次遍历到一个字符,其剩余出现次数 - 1. 对于每一个字符,如果其对应的剩余出现次数大于 1,我们可以选择丢弃(也可以选择不丢弃),否则不可以丢弃。 是否丢弃的标准和上面题目类似。如果栈中相邻的元素字典序更大,那么我们选择丢弃相邻的栈中的元素。 还记得上面题目的边界条件么?如果栈中剩下的元素大于 $n - k$,我们选择截取前 $n - k$ 个数字。然而本题中的 k 是分散在各个字符中的,因此这种思路不可行的。 不过不必担心。由于题目是要求只出现一次。我们可以在遍历的时候简单地判断其是否在栈上即可。 代码: 123456789101112class Solution: def removeDuplicateLetters(self, s) -> int: stack = [] remain_counter = collections.Counter(s) for c in s: if c not in stack: while stack and c < stack[-1] and remain_counter[stack[-1]] > 0: stack.pop() stack.append(c) remain_counter[c] -= 1 return ''.join(stack) _复杂度分析_ 时间复杂度:由于判断当前字符是否在栈上存在需要 $O(N)$ 的时间,因此总的时间复杂度就是 $O(N ^ 2)$,其中 $N$ 为字符串长度。 空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 $O(N)$,其中 $N$ 为字符串长度。 查询给定字符是否在一个序列中存在的方法。根本上来说,有两种可能: 有序序列: 可以二分法,时间复杂度大致是 $O(N)$。 无序序列: 可以使用遍历的方式,最坏的情况下时间复杂度为 $O(N)$。我们也可以使用空间换时间的方式,使用 $N$的空间 换取 $O(1)$的时间复杂度。 由于本题中的 stack 并不是有序的,因此我们的优化点考虑空间换时间。而由于每种字符仅可以出现一次,这里使用 hashset 即可。 代码(Python)1234567891011121314class Solution: def removeDuplicateLetters(self, s) -> int: stack = [] seen = set() remain_counter = collections.Counter(s) for c in s: if c not in seen: while stack and c < stack[-1] and remain_counter[stack[-1]] > 0: seen.discard(stack.pop()) seen.add(c) stack.append(c) remain_counter[c] -= 1 return ''.join(stack) _复杂度分析_ 时间复杂度:$O(N)$,其中 $N$ 为字符串长度。 空间复杂度:我们使用了额外的栈和 hashset,因此空间复杂度为 $O(N)$,其中 $N$ 为字符串长度。 LeetCode 《1081. 不同字符的最小子序列》 和本题一样,不再赘述。 321. 拼接最大数(困难)题目描述123456789101112131415161718192021222324252627282930给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。说明: 请尽可能地优化你算法的时间和空间复杂度。示例 1:输入:nums1 = [3, 4, 6, 5]nums2 = [9, 1, 2, 5, 8, 3]k = 5输出:[9, 8, 6, 5, 3]示例 2:输入:nums1 = [6, 7]nums2 = [6, 0, 4]k = 5输出:[6, 7, 6, 0, 4]示例 3:输入:nums1 = [3, 9]nums2 = [8, 9]k = 3输出:[9, 8, 9] 前置知识 分治 数学 思路和第一道题类似,只不不过这一次是两个数组,而不是一个,并且是求最大数。 最大最小是无关紧要的,关键在于是两个数组,并且要求从两个数组选取的元素个数加起来一共是 k。 然而在一个数组中取 k 个数字,并保持其最小(或者最大),我们已经会了。但是如果问题扩展到两个,会有什么变化呢? 实际上,问题本质并没有发生变化。 假设我们从 nums1 中取了 k1 个,从 num2 中取了 k2 个,其中 k1 + k2 = k。而 k1 和 k2 这 两个子问题我们是会解决的。由于这两个子问题是相互独立的,因此我们只需要分别求解,然后将结果合并即可。 假如 k1 和 k2 个数字,已经取出来了。那么剩下要做的就是将这个长度分别为 k1 和 k2 的数字,合并成一个长度为 k 的数组合并成一个最大的数组。 以题目的 nums1 = [3, 4, 6, 5] nums2 = [9, 1, 2, 5, 8, 3] k = 5 为例。 假如我们从 num1 中取出 1 个数字,那么就要从 nums2 中取出 4 个数字。 运用第一题的方法,我们计算出应该取 nums1 的 [6],并取 nums2 的 [9,5,8,3]。 如何将 [6] 和 [9,5,8,3],使得数字尽可能大,并且保持相对位置不变呢? 实际上这个过程有点类似归并排序中的治,而上面我们分别计算 num1 和 num2 的最大数的过程类似归并排序中的分。 (图 6) 代码: 我们将从 num1 中挑选的 k1 个数组成的数组称之为 A,将从 num2 中挑选的 k2 个数组成的数组称之为 B, 1234567def merge(A, B): ans = [] while A or B: bigger = A if A > B else B ans.append(bigger[0]) bigger.pop(0) return ans 这里需要说明一下。 在很多编程语言中:如果 A 和 B 是两个数组,当前仅当 A 的首个元素字典序大于 B 的首个元素,A > B 返回 true,否则返回 false。 比如: 1234567A = [1,2]B = [2]A < B # TrueA = [1,2]B = [1,2,3]A < B # False 以合并 [6] 和 [9,5,8,3] 为例,图解过程如下: (图 7) 具体算法: 从 nums1 中 取 $min(i, len(nums1))$ 个数形成新的数组 A(取的逻辑同第一题),其中 i 等于 0,1,2, … k。 从 nums2 中 对应取 $min(j, len(nums2))$ 个数形成新的数组 B(取的逻辑同第一题),其中 j 等于 k - i。 将 A 和 B 按照上面的 merge 方法合并 上面我们暴力了 k 种组合情况,我们只需要将 k 种情况取出最大值即可。 代码(Python)12345678910111213141516171819202122class Solution: def maxNumber(self, nums1, nums2, k): def pick_max(nums, k): stack = [] drop = len(nums) - k for num in nums: while drop and stack and stack[-1] < num: stack.pop() drop -= 1 stack.append(num) return stack[:k] def merge(A, B): ans = [] while A or B: bigger = A if A > B else B ans.append(bigger[0]) bigger.pop(0) return ans return max(merge(pick_max(nums1, i), pick_max(nums2, k-i)) for i in range(k+1) if i <= len(nums1) and k-i <= len(nums2)) _复杂度分析_ 时间复杂度:pick_max 的时间复杂度为 $O(M + N)$ ,其中 $M$ 为 nums1 的长度,$N$ 为 nums2 的长度。 merge 的时间复杂度为 $O(k)$,再加上外层遍历所有的 k 中可能性。因此总的时间复杂度为 $O(k^2 * (M + N))$。 空间复杂度:我们使用了额外的 stack 和 ans 数组,因此空间复杂度为 $O(max(M, N, k))$,其中 $M$ 为 nums1 的长度,$N$ 为 nums2 的长度。 总结这四道题都是删除或者保留若干个字符,使得剩下的数字最小(或最大)或者字典序最小(或最大)。而解决问题的前提是要有一定数学前提。而基于这个数学前提,我们贪心地删除栈中相邻的字符。如果你会了这个套路,那么这四个题目应该都可以轻松解决。 316. 去除重复字母(困难),我们使用 hashmap 代替了数组的遍历查找,属于典型的空间换时间方式,可以认识到数据结构的灵活使用是多么的重要。背后的思路是怎么样的?为什么想到空间换时间的方式,我在文中也进行了详细的说明,这都是值得大家思考的问题。然而实际上,这些题目中使用的栈也都是空间换时间的思想。大家下次碰到需要空间换取时间的场景,是否能够想到本文给大家介绍的栈和哈希表呢? 321. 拼接最大数(困难)则需要我们能够对问题进行分解,这绝对不是一件简单的事情。但是对难以解决的问题进行分解是一种很重要的技能,希望大家能够通过这道题加深这种分治思想的理解。 大家可以结合我之前写过的几个题解练习一下,它们分别是: 【简单易懂】归并排序(Python) 一文看懂《最大子序列和问题》 最后推荐类似的题目供大家练习: 1673. 找出最具竞争力的子序列 2030. 含特定字母的最小子序列 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/tags/经验分享/"},{"name":"困难","slug":"困难","permalink":"https://lucifer.ren/blog/tags/困难/"},{"name":"中等","slug":"中等","permalink":"https://lucifer.ren/blog/tags/中等/"},{"name":"删除 k 个字符","slug":"删除-k-个字符","permalink":"https://lucifer.ren/blog/tags/删除-k-个字符/"}]},{"title":"如何将 github 上的代码一键部署到服务器?","slug":"deploy-button","date":"2021-02-13T16:00:00.000Z","updated":"2023-01-07T13:54:06.068Z","comments":true,"path":"2021/02/14/deploy-button/","link":"","permalink":"https://lucifer.ren/blog/2021/02/14/deploy-button/","excerpt":"在 Github 上看到一些不错的仓库,想要贡献代码怎么办? 在 Github 上看到一些有用的网站,想部署到自己的服务器怎么办? 。。。 我想很多人都碰到过这个问题。 如果要贡献代码,之前我的做法通常是将代码克隆到本地,然后在本地的编辑器中修改并提交 pr。 如果想部署到自己的服务器,之前我的做法通常是克隆到本地,然后本地修改一下部署的配置,最后部署到自己的服务器或者第三方的云服务器(比如 Github Pages)。 而现在随着云技术的普及,我们没有必要将代码克隆到本地进行操作,而是直接在云端编辑器中完成修改,开发,并直接部署到云服务器。今天就给大家推荐一个工具,一键将代码部署到云服务器。 ​","text":"在 Github 上看到一些不错的仓库,想要贡献代码怎么办? 在 Github 上看到一些有用的网站,想部署到自己的服务器怎么办? 。。。 我想很多人都碰到过这个问题。 如果要贡献代码,之前我的做法通常是将代码克隆到本地,然后在本地的编辑器中修改并提交 pr。 如果想部署到自己的服务器,之前我的做法通常是克隆到本地,然后本地修改一下部署的配置,最后部署到自己的服务器或者第三方的云服务器(比如 Github Pages)。 而现在随着云技术的普及,我们没有必要将代码克隆到本地进行操作,而是直接在云端编辑器中完成修改,开发,并直接部署到云服务器。今天就给大家推荐一个工具,一键将代码部署到云服务器。 ​ 什么是一键部署?今天给大家介绍的就是一键部署。那什么是一键部署呢?顾名思义,就是有一个按钮,点击一下就能完成部署工作。 如下是一个拥有一键部署按钮的项目: 点击之后进入如下页面,你可以对一些默认配置进行修改(也可以直接使用默认配置): 修改后点击Deploy app 即可。部署成功之后就可以通过类似如下的地址访问啦~ 图中演示地址是:https://leetcode-cheat.herokuapp.com/ 大家可以直接进我的仓库 https://github.com/leetcode-pp/leetcode-cheat,点击部署按钮试试哦。 它是如何实现的呢?我是一个喜欢探究事物原理的人,当然对它们的原理了如指掌才行。其实它的原理很容易,我们从头开始说。 1. 如何在 Github 中显示发布按钮。上面的部署按钮就是如下的一个 Markdown 内容渲染的: 1[![Deploy](https://p.ipic.vip/hefqvb.jpg)](https://p.ipic.vip/v0aqts.jpg) 上面内容会被渲染成如下的 DOM: 1234567<a href=\"https://heroku.com/deploy\" rel=\"nofollow\" ><img src=\"https://camo.githubusercontent.com/6979881d5a96b7b18a057083bb8aeb87ba35fc279452e29034c1e1c49ade0636/68747470733a2f2f7777772e6865726f6b7563646e2e636f6d2f6465706c6f792f627574746f6e2e737667\" alt=\"Deploy\" data-canonical-src=\"https://www.herokucdn.com/deploy/button.svg\" style=\"max-width:100%;\"/></a> 也就是说其实就是一个被 a 标签包裹的 svg 图片,点击之后会完成 url 跳转。 2. 云服务厂商如何获取默认配置?这里以 heroku 为例,其他厂商(比如腾讯)原理都差不多。 由于上面的原因,实际上我们传递给第三方云厂商的方式只可能是 url。因此我们可以直接将配置通过 ur 的方式传输。比如 https://heroku.com/deploy?a=1&b=2&c=3 。 这种方式对于少量数据是足够的,那如何数据量很大呢?我们知道浏览器 url 的长度是有限的,而且不同的浏览器限制也不尽相同。 那怎么解决呢?现在比较流行的思路是约定。以 heroku 来说,就约定根目录的 app.json 文件中存配置,这种约定的方式我个人强烈推荐。 比如我的仓库的 app.json 就是: 12345678910111213141516171819202122{ \"name\": \"LeetCode Cheatsheet\", \"description\": \"力扣加加,或许是西湖区最好的算法题解\", \"repository\": \"https://github.com/leetcode-pp/leetcode-cheat\", \"logo\": \"https://tva1.sinaimg.cn/large/008eGmZEly1gnm68epc0kj30u00tsaav.jpg\", \"keywords\": [\"github\", \"leetcode\", \"cheatsheet\", \"91algo\", \"algorithm\"], \"env\": { \"REACT_APP_BUILD_TARGET\": { \"description\": \"枚举值:extension 和 web\", \"value\": null }, \"PUBLIC_URL\": { \"description\": \"静态资源存放位置(可使用 cdn 加速)\", \"value\": \"https://cdn.jsdelivr.net/gh/leetcode-pp/leetcode-cheat@gh-pages/\" } }, \"buildpacks\": [ { \"url\": \"https://buildpack-registry.s3.amazonaws.com/buildpacks/mars/create-react-app.tgz\" } ]} 可以看出,除了配置仓库,logo,描述这些常规信息,我还配置了环境变量和 buidpacks。buildpacks 简单来说就是构建应用的方式, 关于 buildpacks 的更多信息可以参考 heroku 官方文档 大家可能还有疑问,为啥上面的链接是 https://heroku.com/deploy。可以看出 url 中也没有任何参数信息,那为什么它就知道从哪来的呢?我觉得 ta 应该利用的是浏览器的 referer,用它可以判断从哪里过来的,进而搜索对应项目根目录的 app.json 文件。你可以通过右键在新的无痕模式中打开来验证。你会发现右键在新的无痕模式中打开是无法正常部署的。 这有什么用呢?一键部署意味着部署的门槛更低,不仅是技巧上的,而且是成本上的。比如 heroku 就允许你直接免费一键部署若干个应用,直接生成网站,域名可以直接访问。如果你觉得域名不喜欢也可以自定义。如果你想修改源码重新构建也是可以的。 比如我看到别人的博客很漂亮。如果 ta 提供了一键部署,那么就可以直接部署到自己的云服务器,生成自己的 url。关联自己的 git 之后,推送还能自动部署(CD)。而且这一切都可以是免费的,至少我现在用的是免费的。 而如果 ta 没有提供一键部署,就需要你自己手动完成了。如果你对这些熟悉还好,无非就是多花点时间。而如果你是技术小白,我可能仅仅是想部署一下,用自己的域名访问之类,没有一键部署就很不友好啦。 相关技术gitpod 是我一直在用的一个工具,它可以帮助我直接在云端编辑一些内容。或者有一些环境问题,需要虚拟主机的,也可以用它来解决。 它不仅仅提供了在线 IDE 的所有功能,还集成了 CI 和 CD,用起来也是非常方便。 同样地,你也可以在你的仓库中增加在 Gitpod 一键打开的功能。 小技巧一些开源项目你不知道怎么贡献。其实可以另辟蹊径,比如给他们贡献一个 logo,再比如贡献一键部署功能。这或许是你迈向开源事业的第一步。 更多资料 heroku-button cloudbase 一键部署","categories":[{"name":"CD","slug":"CD","permalink":"https://lucifer.ren/blog/categories/CD/"}],"tags":[{"name":"CD","slug":"CD","permalink":"https://lucifer.ren/blog/tags/CD/"},{"name":"GitHub","slug":"GitHub","permalink":"https://lucifer.ren/blog/tags/GitHub/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第三期)","slug":"91-algo-3","date":"2021-01-18T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2021/01/19/91-algo-3/","link":"","permalink":"https://lucifer.ren/blog/2021/01/19/91-algo-3/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。 刚好碰上寒假,大家难道不想在寒假中给自己充下电么? 活动时间2021-02-01 至 2021-05-02 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少参与一次打卡 违反上述条件的人员会被强制清退 课程大纲 往期公开讲义: 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划(部分内容) 【91 算法-专题篇】二分法 三期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 自习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇(30 天) 数组,队列,栈 链表 树与递归 哈希表 双指针 图(加餐) 专题篇(31 天) 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 进阶篇(30 天) 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定私有仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 第二天会对前一天的题目进行讲解。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书,科学上网兑换码等 连续打卡七天可以获得补签卡一张哦 冲鸭采用 微信群的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元 101 - 500 收费 30 元 现在已经超过 100 名同学报名参加啦,因此需要发 30 元红包给我,拉你进群,我的微信号:DevelopeEngineer 熟悉 91 的小伙伴可能发现第三期涨价了。 其实涨价的目的是提供更好的服务,包括但不限于发放奖品,完善讲义,购买服务器(后期考虑),还望大家理解。 如果你经济实在困难可以参加下面的返现活动哦。 分享返现如果你没有抢到免费的学习机会也不要气馁。我们贴心地为大家搞了分享返现活动,手慢照样可以免费参加哦~ 活动规则: 发送宣传海报到你的朋友圈不屏蔽好友保留三天,三天之后加 lucifer 微信好友(微信号:DevelopeEngineer)进行验证,验证通过全额返现。 不到三天就没必要联系我验证了,必须不屏蔽好友满三天才可以验证。 朋友圈文案统一为: 91 天,遇见更好的自己。发送本海报到朋友圈,不屏蔽好友保留三天即可免费学习(文案需保留)。快扫描下方二维码报名吧! 海报: 朋友圈分享海报示例: FAQ Q:第三期和前两期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到仓库,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在仓库中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和仓库,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在仓库和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/ Q:仓库在哪里?怎么进? A:进群之后会在活动开始之前(2021-02-01),通过群公告的形式通知大家,大家耐心等待即可。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第二弹)","slug":"heap-2","date":"2021-01-18T16:00:00.000Z","updated":"2023-01-05T12:24:49.460Z","comments":true,"path":"2021/01/19/heap-2/","link":"","permalink":"https://lucifer.ren/blog/2021/01/19/heap-2/","excerpt":"一点题外话上次在我的公众号给大家做了一个小调查《投出你想要的题解编程语言吧~》。以下是调查的结果: 而关于其他,则大多数是 Go 语言。 由于 Java 和 Python 所占比例已经超过了 60%,这次我尝试一下 Java 和 Python 双语言来写,感谢 @CaptainZ 提供的 Java 代码。同时为了不让文章又臭又长,我将 Java 本文所有代码(Java 和 Python)都放到了力扣加加官网上,网站地址:https://leetcode-solution.cn/solution-code 如果不科学上网的话,可能打开会很慢。 正文 大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第一弹)","text":"一点题外话上次在我的公众号给大家做了一个小调查《投出你想要的题解编程语言吧~》。以下是调查的结果: 而关于其他,则大多数是 Go 语言。 由于 Java 和 Python 所占比例已经超过了 60%,这次我尝试一下 Java 和 Python 双语言来写,感谢 @CaptainZ 提供的 Java 代码。同时为了不让文章又臭又长,我将 Java 本文所有代码(Java 和 Python)都放到了力扣加加官网上,网站地址:https://leetcode-solution.cn/solution-code 如果不科学上网的话,可能打开会很慢。 正文 大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第一弹) 本次是下篇,没有看过上篇的同学强烈建议先阅读上篇几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第一弹) 这是第二部分,后面的内容更加干货,分别是三个技巧和四大应用。这两个主题是专门教你怎么解题的。掌握了它,力扣中的大多数堆的题目都不在话下(当然我指的仅仅是题目中涉及到堆的部分)。 警告: 本章的题目基本都是力扣 hard 难度,这是因为堆的题目很多标记难度都不小,关于这点在前面也介绍过了。 一点说明在上主菜之前,先给大家来个开胃菜。 这里给大家介绍两个概念,分别是元组和模拟大顶堆 。之所以进行这些说明就是防止大家后面看不懂。 元组使用堆不仅仅可以存储单一值,比如 [1,2,3,4] 的 1,2,3,4 分别都是单一值。除了单一值,也可以存储复合值,比如对象或者元组等。 这里我们介绍一种存储元组的方式,这个技巧会在后面被广泛使用,请务必掌握。比如 [(1,2,3), (4,5,6), (2,1,3),(4,2,8)]。 1234567h = [(1,2,3), (4,5,6), (2,1,3),(4,2,8)]heapq.heappify(h) # 堆化(小顶堆)heapq.heappop() # 弹出 (1,2,3)heapq.heappop() # 弹出 (2,1,3)heapq.heappop() # 弹出 (4,2,8)heapq.heappop() # 弹出 (4,5,6) 用图来表示堆结构就是下面这样: 简单解释一下上面代码的执行结果。 使用元组的方式,默认将元组第一个值当做键来比较。如果第一个相同,继续比较第二个。比如上面的 (4,5,6) 和 (4,2,8),由于第一个值相同,因此继续比较后一个,又由于 5 比 2 大,因此 (4,2,8)先出堆。 使用这个技巧有两个作用: 携带一些额外的信息。 比如我想求二维矩阵中第 k 小数,当然是以值作为键。但是处理过程又需要用到其行和列信息,那么使用元组就很合适,比如 (val, row, col)这样的形式。 想根据两个键进行排序,一个主键一个副键。这里面又有两种典型的用法, 2.1 一种是两个都是同样的顺序,比如都是顺序或者都是逆序。 2.2 另一种是两个不同顺序排序,即一个是逆序一个是顺序。 由于篇幅原因,具体就不再这里展开了,大家在平时做题过程中留意可以一下,有机会我会单独开一篇文章讲解。 如果你所使用的编程语言没有堆或者堆的实现不支持元组,那么也可以通过简单的改造使其支持,主要就是自定义比较逻辑即可。 模拟大顶堆由于 Python 没有大顶堆。因此我这里使用了小顶堆进行模拟实现。即将原有的数全部取相反数,比如原数字是 5,就将 -5 入堆。经过这样的处理,小顶堆就可以当成大顶堆用了。不过需要注意的是,当你 pop 出来的时候, 记得也要取反,将其还原回来哦。 代码示例: 123456789h = []A = [1,2,3,4,5]for a in A: heapq.heappush(h, -a)-1 * heapq.heappop(h) # 5-1 * heapq.heappop(h) # 4-1 * heapq.heappop(h) # 3-1 * heapq.heappop(h) # 2-1 * heapq.heappop(h) # 1 用图来表示就是下面这样: 铺垫就到这里,接下来进入正题。 三个技巧技巧一 - 固定堆这个技巧指的是固定堆的大小 k 不变,代码上可通过每 pop 出去一个就 push 进来一个来实现。而由于初始堆可能是 0,我们刚开始需要一个一个 push 进堆以达到堆的大小为 k,因此严格来说应该是维持堆的大小不大于 k。 固定堆一个典型的应用就是求第 k 小的数。其实求第 k 小的数最简单的思路是建立小顶堆,将所有的数先全部入堆,然后逐个出堆,一共出堆 k 次。最后一次出堆的就是第 k 小的数。 然而,我们也可不先全部入堆,而是建立大顶堆(注意不是上面的小顶堆),并维持堆的大小为 k 个。如果新的数入堆之后堆的大小大于 k,则需要将堆顶的数和新的数进行比较,并将较大的移除。这样可以保证堆中的数是全体数字中最小的 k 个,而这最小的 k 个中最大的(即堆顶)不就是第 k 小的么?这也就是选择建立大顶堆,而不是小顶堆的原因。 简单一句话总结就是固定一个大小为 k 的大顶堆可以快速求第 k 小的数,反之固定一个大小为 k 的小顶堆可以快速求第 k 大的数。比如力扣 2020-02-24 的周赛第三题5663. 找出第 K 大的异或坐标值就可以用固定小顶堆技巧来实现(这道题让你求第 k 大的数)。 这么说可能你的感受并不强烈,接下来我给大家举两个例子来帮助大家加深印象。 295. 数据流的中位数题目描述1234567891011121314151617181920212223中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。例如,[2,3,4] 的中位数是 3[2,3] 的中位数是 (2 + 3) / 2 = 2.5设计一个支持以下两种操作的数据结构:void addNum(int num) - 从数据流中添加一个整数到数据结构中。double findMedian() - 返回目前所有元素的中位数。示例:addNum(1)addNum(2)findMedian() -> 1.5addNum(3)findMedian() -> 2进阶:如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法? 思路这道题实际上可看出是求第 k 小的数的特例了。 如果列表长度是奇数,那么 k 就是 (n + 1) / 2,中位数就是第 k 个数,。比如 n 是 5, k 就是 (5 + 1)/ 2 = 3。 如果列表长度是偶数,那么 k 就是 (n + 1) / 2 和 (n + 1) / 2 + 1,中位数则是这两个数的平均值。比如 n 是 6, k 就是 (6 + 1)/ 2 = 3 和 (6 + 1) / 2 + 1 = 4。 因此我们的可以维护两个固定堆,固定堆的大小为 $(n + 1) \\div 2$ 和 $n - (n + 1)\\div2$,也就是两个堆的大小最多相差 1,更具体的就是 $ 0 <= (n + 1) \\div 2 - (n - (n + 1) \\div 2) <= 1$。 基于上面提到的知识,我们可以: 建立一个大顶堆,并存放最小的 $(n + 1) \\div 2$ 个数,这样堆顶的数就是第 $(n + 1) \\div 2$ 小的数,也就是奇数情况的中位数。 建立一个小顶堆,并存放最大的 n - $(n + 1) \\div 2$ 个数,这样堆顶的数就是第 n - $(n + 1) \\div 2$ 大的数,结合上面的大顶堆,可求出偶数情况的中位数。 有了这样一个知识,剩下的只是如何维护两个堆的大小了。 如果大顶堆的个数比小顶堆少,那么就将小顶堆中最小的转移到大顶堆。而由于小顶堆维护的是最大的 k 个数,大顶堆维护的是最小的 k 个数,因此小顶堆堆顶一定大于等于大顶堆堆顶,并且这两个堆顶是此时的中位数。 如果大顶堆的个数比小顶堆的个数多 2,那么就将大顶堆中最大的转移到小顶堆,理由同上。 至此,可能你已经明白了为什么分别建立两个堆,并且需要一个大顶堆一个小顶堆。这其中的原因正如上面所描述的那样。 固定堆的应用常见还不止于此,我们继续看一道题。 代码12345678910111213141516class MedianFinder: def __init__(self): self.min_heap = [] self.max_heap = [] def addNum(self, num: int) -> None: if not self.max_heap or num < -self.max_heap[0]: heapq.heappush(self.max_heap, -num) else: heapq.heappush(self.min_heap, num) if len(self.max_heap) > len(self.min_heap) + 1: heappush(self.min_heap, -heappop(self.max_heap)) elif len(self.min_heap) > len(self.max_heap): heappush(self.max_heap, -heappop(self.min_heap)) def findMedian(self) -> float: if len(self.min_heap) == len(self.max_heap): return (self.min_heap[0] - self.max_heap[0]) / 2 return -self.max_heap[0] (代码 1.3.1) 857. 雇佣 K 名工人的最低成本题目描述12345678910111213141516171819202122232425262728有 N 名工人。 第 i 名工人的工作质量为 quality[i] ,其最低期望工资为 wage[i] 。现在我们想雇佣 K 名工人组成一个工资组。在雇佣 一组 K 名工人时,我们必须按照下述规则向他们支付工资:对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。工资组中的每名工人至少应当得到他们的最低期望工资。返回组成一个满足上述条件的工资组至少需要多少钱。 示例 1:输入: quality = [10,20,5], wage = [70,50,30], K = 2输出: 105.00000解释: 我们向 0 号工人支付 70,向 2 号工人支付 35。示例 2:输入: quality = [3,1,10,10,1], wage = [4,8,2,2,7], K = 3输出: 30.66667解释: 我们向 0 号工人支付 4,向 2 号和 3 号分别支付 13.33333。 提示:1 <= K <= N <= 10000,其中 N = quality.length = wage.length1 <= quality[i] <= 100001 <= wage[i] <= 10000与正确答案误差在 10^-5 之内的答案将被视为正确的。 思路题目要求我们选择 k 个人,按其工作质量与同组其他工人的工作质量的比例来支付工资,并且工资组中的每名工人至少应当得到他们的最低期望工资。 换句话说,同一组的 k 个人他们的工作质量和工资比是一个固定值才能使支付的工资最少。请先理解这句话,后面的内容都是基于这个前提产生的。 我们不妨定一个指标工作效率,其值等于 q / w。前面说了这 k 个人的 q / w 是相同的才能保证工资最少,并且这个 q / w 一定是这 k 个人最低的(短板),否则一定会有人得不到最低期望工资。 于是我们可以写出下面的代码: 123456789101112131415161718class Solution: def mincostToHireWorkers(self, quality: List[int], wage: List[int], K: int) -> float: eff = [(q / w, q, w) for a, b in zip(quality, wage)] eff.sort(key=lambda a: -a[0]) ans = float('inf') for i in range(K-1, len(eff)): h = [] k = K - 1 rate, _, total = eff[i] # 找出工作效率比它高的 k 个人,这 k 个人的工资尽可能低。 # 由于已经工作效率倒序排了,因此前面的都是比它高的,然后使用堆就可得到 k 个工资最低的。 for j in range(i): heapq.heappush(h, eff[j][1] / rate) while k > 0: total += heapq.heappop(h) k -= 1 ans = min(ans, total) return ans (代码 1.3.2) 这种做法每次都 push 很多数,并 pop k 次,并没有很好地利用堆的动态特性,而只利用了其求极值的特性。 一个更好的做法是使用固定堆技巧。 这道题可以换个角度思考。其实这道题不就是让我们选 k 个人,工作效率比取他们中最低的,并按照这个最低的工作效率计算总工资,找出最低的总工资么? 因此这道题可以固定一个大小为 k 的大顶堆,通过一定操作保证堆顶的就是第 k 小的(操作和前面的题类似)。 并且前面的解法中堆使用了三元组 (q / w, q, w),实际上这也没有必要。因为已知其中两个,可推导出另外一个,因此存储两个就行了,而又由于我们需要根据工作效率比做堆的键,因此任意选一个 q 或者 w 即可,这里我选择了 q,即存 (q/2, q) 二元组。 具体来说就是:以 rate 为最低工作效率比的 k 个人的总工资 = $\\displaystyle \\sum_{n=1}^{k}{q}_{n}/rate$,这里的 rate 就是当前的 q / w,同时也是 k 个人的 q / w 的最小值。 代码123456789101112131415class Solution: def mincostToHireWorkers(self, quality: List[int], wage: List[int], K: int) -> float: effs = [(q / w, q) for q, w in zip(quality, wage)] effs.sort(key=lambda a: -a[0]) ans = float('inf') h = [] total = 0 for rate, q in effs: heapq.heappush(h, -q) total += q if len(h) > K: total += heapq.heappop(h) if len(h) == K: ans = min(ans, total / rate) return ans (代码 1.3.3) 技巧二 - 多路归并这个技巧其实在前面讲超级丑数的时候已经提到了,只是没有给这种类型的题目一个名字。 其实这个技巧,叫做多指针优化可能会更合适,只不过这个名字实在太过朴素且容易和双指针什么的混淆,因此我给 ta 起了个别致的名字 - 多路归并。 多路体现在:有多条候选路线。代码上,我们可使用多指针来表示。 归并体现在:结果可能是多个候选路线中最长的或者最短,也可能是第 k 个 等。因此我们需要对多条路线的结果进行比较,并根据题目描述舍弃或者选取某一个或多个路线。 这样描述比较抽象,接下来通过几个例子来加深一下大家的理解。 这里我给大家精心准备了四道难度为 hard 的题目。 掌握了这个套路就可以去快乐地 AC 这四道题啦。 1439. 有序矩阵中的第 k 个最小数组和题目描述123456789101112131415161718192021222324252627282930313233343536给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。你可以从每一行中选出 1 个元素形成一个数组。返回所有可能数组中的第 k 个 最小 数组和。 示例 1:输入:mat = [[1,3,11],[2,4,6]], k = 5输出:7解释:从每一行中选出一个元素,前 k 个和最小的数组分别是:[1,2], [1,4], [3,2], [3,4], [1,6]。其中第 5 个的和是 7 。示例 2:输入:mat = [[1,3,11],[2,4,6]], k = 9输出:17示例 3:输入:mat = [[1,10,10],[1,4,5],[2,3,6]], k = 7输出:9解释:从每一行中选出一个元素,前 k 个和最小的数组分别是:[1,1,2], [1,1,3], [1,4,2], [1,4,3], [1,1,6], [1,5,2], [1,5,3]。其中第 7 个的和是 9 。示例 4:输入:mat = [[1,1,10],[2,2,9]], k = 7输出:12 提示:m == mat.lengthn == mat.length[i]1 <= m, n <= 401 <= k <= min(200, n ^ m)1 <= mat[i][j] <= 5000mat[i] 是一个非递减数组 思路其实这道题就是给你 m 个长度均相同的一维数组,让我们从这 m 个数组中分别选出一个数,即一共选取 m 个数,求这 m 个数的和是所有选取可能性中和第 k 小的。 一个朴素的想法是使用多指针来解。对于这道题来说就是使用 m 个指针,分别指向 m 个一维数组,指针的位置表示当前选取的是该一维数组中第几个。 以题目中的 mat = [[1,3,11],[2,4,6]], k = 5 为例。 先初始化两个指针 p1,p2,分别指向两个一维数组的开头,代码表示就是全部初始化为 0。 此时两个指针指向的数字和为 1 + 2 = 3,这就是第 1 小的和。 接下来,我们移动其中一个指针。此时我们可以移动 p1,也可以移动 p2。 那么第 2 小的一定是移动 p1 和 移动 p2 这两种情况的较小值。而这里移动 p1 和 p2 实际上都会得到 5,也就是说第 2 和第 3 小的和都是 5。 到这里已经分叉了,出现了两种情况(注意看粗体的位置,粗体表示的是指针的位置): [1,3,11],[2,4,6] 和为 5 [1,3,11],[2,4,6] 和为 5 接下来,这两种情况应该齐头并进,共同进行下去。 对于情况 1 来说,接下来移动又有两种情况。 [1,3,11],[2,4,6] 和为 13 [1,3,11],[2,4,6] 和为 7 对于情况 2 来说,接下来移动也有两种情况。 [1,3,11],[2,4,6] 和为 7 [1,3,11],[2,4,6] 和为 7 我们通过比较这四种情况,得出结论: 第 4,5,6 小的数都是 7。但第 7 小的数并不一定是 13。原因和上面类似,可能第 7 小的就隐藏在前面的 7 分裂之后的新情况中,实际上确实如此。因此我们需要继续执行上述逻辑。 进一步,我们可以将上面的思路拓展到一般情况。 上面提到了题目需要求的其实是第 k 小的和,而最小的我们是容易知道的,即所有的一维数组首项和。我们又发现,根据最小的,我们可以推导出第 2 小,推导的方式就是移动其中一个指针,这就一共分裂出了 n 种情况了,其中 n 为一维数组长度,第 2 小的就在这分裂中的 n 种情况中,而筛选的方式是这 n 种情况和最小的,后面的情况也是类似。不难看出每次分裂之后极值也发生了变化,因此这是一个明显的求动态求极值的信号,使用堆是一个不错的选择。 那代码该如何书写呢? 上面说了,我们先要初始化 m 个指针,并赋值为 0。对应伪代码: 12345678# 初始化堆h = []# sum(vec[0] for vec in mat) 是 m 个一维数组的首项和# [0] * m 就是初始化了一个长度为 m 且全部填充为 0 的数组。# 我们将上面的两个信息组装成元祖 cur 方便使用cur = (sum(vec[0] for vec in mat), [0] * m)# 将其入堆heapq.heappush(h, cur) 接下来,我们每次都移动一个指针,从而形成分叉出一条新的分支。每次从堆中弹出一个最小的,弹出 k 次就是第 k 小的了。伪代码: 123456789101112for 1 to K: # acc 当前的和, pointers 是指针情况。 acc, pointers = heapq.heappop(h) # 每次都粗暴地移动指针数组中的一个指针。每移动一个指针就分叉一次, 一共可能移动的情况是 n,其中 n 为一维数组的长度。 for i, pointer in enumerate(pointers): # 如果 pointer == len(mat[0]) - 1 说明到头了,不能移动了 if pointer != len(mat[0]) - 1: # 下面两句话的含义是修改 pointers[i] 的指针 为 pointers[i] + 1 new_pointers = pointers.copy() new_pointers[i] += 1 # 将更新后的 acc 和指针数组重新入堆 heapq.heappush(h, (acc + mat[i][pointer + 1] - mat[i][pointer], new_pointers)) 这是多路归并问题的核心代码,请务必记住。 代码看起来很多,其实去掉注释一共才七行而已。 上面的伪代码有一个问题。比如有两个一维数组,指针都初始化为 0。第一次移动第一个一维数组的指针,第二次移动第二个数组的指针,此时指针数组为 [1, 1],即全部指针均指向下标为 1 的元素。而如果第一次移动第二个一维数组的指针,第二次移动第一个数组的指针,此时指针数组仍然为 [1, 1]。这实际上是一种情况,如果不加控制会被计算两次导致出错。 一个可能的解决方案是使用 hashset 记录所有的指针情况,这样就避免了同样的指针被计算多次的问题。为了做到这一点,我们需要对指针数组的使用做一些微调,即使用元组代替数组。原因在于数组是无法直接哈希化的。具体内容请参考代码区。 多路归并的题目,思路和代码都比较类似。为了后面的题目能够更高地理解,请务必搞定这道题,后面我们将不会这么详细地进行分析。 代码123456789101112131415161718class Solution: def kthSmallest(self, mat, k: int) -> int: h = [] cur = (sum(vec[0] for vec in mat), tuple([0] * len(mat))) heapq.heappush(h, cur) seen = set(cur) for _ in range(k): acc, pointers = heapq.heappop(h) for i, pointer in enumerate(pointers): if pointer != len(mat[0]) - 1: t = list(pointers) t[i] = pointer + 1 tt = tuple(t) if tt not in seen: seen.add(tt) heapq.heappush(h, (acc + mat[i][pointer + 1] - mat[i][pointer], tt)) return acc (代码 1.3.4) 719. 找出第 k 小的距离对题目描述12345678910111213141516171819给定一个整数数组,返回所有数对之间的第 k 个最小距离。一对 (A, B) 的距离被定义为 A 和 B 之间的绝对差值。示例 1:输入:nums = [1,3,1]k = 1输出:0解释:所有数对如下:(1,3) -> 2(1,1) -> 0(3,1) -> 2因此第 1 个最小距离的数对是 (1,1),它们之间的距离为 0。提示:2 <= len(nums) <= 10000.0 <= nums[i] < 1000000.1 <= k <= len(nums) * (len(nums) - 1) / 2. 思路不难看出所有的数对可能共 $C_n^2$ 个,也就是 $n\\times(n-1)\\div2$。 因此我们可以使用两次循环找出所有的数对,并升序排序,之后取第 k 个。 实际上,我们可使用固定堆技巧,维护一个大小为 k 的大顶堆,这样堆顶的元素就是第 k 小的,这在前面的固定堆中已经讲过,不再赘述。 12345678910111213class Solution: def smallestDistancePair(self, nums: List[int], k: int) -> int: h = [] for i in range(len(nums)): for j in range(i + 1, len(nums)): a, b = nums[i], nums[j] # 维持堆大小不超过 k if len(h) == k and -abs(a - b) > h[0]: heapq.heappop(h) if len(h) < k: heapq.heappush(h, -abs(a - b)) return -h[0] (代码 1.3.5) 不过这种优化意义不大,因为算法的瓶颈在于 $N^2$ 部分的枚举,我们应当设法优化这一点。 如果我们将数对进行排序,那么最小的数对距离一定在 nums[i] - nums[i - 1] 中,其中 i 为从 1 到 n 的整数,究竟是哪个取决于谁更小。接下来就可以使用上面多路归并的思路来解决了。 如果 nums[i] - nums[i - 1] 的差是最小的,那么第 2 小的一定是剩下的 n - 1 种情况和 nums[i] - nums[i - 1] 分裂的新情况。关于如何分裂,和上面类似,我们只需要移动其中 i 的指针为 i + 1 即可。这里的指针数组长度固定为 2,而不是上面题目中的 m。这里我将两个指针分别命名为 fr 和 to,分别代表 from 和 to。 代码12345678910111213class Solution(object): def smallestDistancePair(self, nums, k): nums.sort() # n 种候选答案 h = [(nums[i+1] - nums[i], i, i+1) for i in range(len(nums) - 1)] heapq.heapify(h) for _ in range(k): diff, fr, to = heapq.heappop(h) if to + 1 < len(nums): heapq.heappush((nums[to + 1] - nums[fr], fr, to + 1)) return diff (代码 1.3.6) 由于时间复杂度和 k 有关,而 k 最多可能达到 $N^2$ 的量级,因此此方法实际上也会超时。不过这证明了这种思路的正确性,如果题目稍加改变说不定就能用上。 这道题可通过二分法来解决,由于和堆主题有偏差,因此这里简单讲一下。 求第 k 小的数比较容易想到的就是堆和二分法。二分的原因在于求第 k 小,本质就是求不大于其本身的有 k - 1 个的那个数。而这个问题很多时候满足单调性,因此就可使用二分来解决。 以这道题来说,最大的数对差就是数组的最大值 - 最小值,不妨记为 max_diff。我们可以这样发问: 数对差小于 max_diff 的有几个? 数对差小于 max_diff - 1 的有几个? 数对差小于 max_diff - 2 的有几个? 数对差小于 max_diff - 3 的有几个? 数对差小于 max_diff - 4 的有几个? 。。。 而我们知道,发问的答案也是不严格递减的,因此使用二分就应该被想到。我们不断发问直到问到小于 x 的有 k - 1 个即可。然而这样的发问也有问题。原因有两个: 小于 x 的有 k - 1 个的数可能不止一个 我们无法确定小于 x 的有 k - 1 个的数一定存在。 比如数对差分别为 [1,1,1,1,2],让你求第 3 大的,那么小于 x 有两个的数根本就不存在。 我们的思路可调整为求小于等于 x 有 k 个的,接下来我们使用二分法的最左模板即可解决。关于最左模板可参考我的二分查找专题 代码: 123456789101112131415161718192021class Solution: def smallestDistancePair(self, A: List[int], K: int) -> int: A.sort() l, r = 0, A[-1] - A[0] def count_ngt(mid): slow = 0 ans = 0 for fast in range(len(A)): while A[fast] - A[slow] > mid: slow += 1 ans += fast - slow return ans while l <= r: mid = (l + r) // 2 if count_ngt(mid) >= K: r = mid - 1 else: l = mid + 1 return l (代码 1.3.7) 632. 最小区间题目描述123456789101112131415161718192021222324252627282930313233343536373839你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。 示例 1:输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]输出:[20,24]解释:列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。示例 2:输入:nums = [[1,2,3],[1,2,3],[1,2,3]]输出:[1,1]示例 3:输入:nums = [[10,10],[11,11]]输出:[10,11]示例 4:输入:nums = [[10],[11]]输出:[10,11]示例 5:输入:nums = [[1],[2],[3],[4],[5],[6],[7]]输出:[1,7] 提示:nums.length == k1 <= k <= 35001 <= nums[i].length <= 50-105 <= nums[i][j] <= 105nums[i] 按非递减顺序排列 思路这道题本质上就是在 m 个一维数组中各取出一个数字,重新组成新的数组 A,使得新的数组 A 中最大值和最小值的差值(diff)最小。 这道题和上面的题目有点类似,又略有不同。这道题是一个矩阵,上面一道题是一维数组。不过我们可以将二维矩阵看出一维数组,这样我们就可以沿用上面的思路了。 上面的思路 diff 最小的一定产生于排序之后相邻的元素之间。而这道题我们无法直接对二维数组进行排序,而且即使进行排序,也不好确定排序的原则。 我们其实可以继续使用前面两道题的思路。具体来说就是使用小顶堆获取堆中最小值,进而通过一个变量记录堆中的最大值,这样就知道了 diff,每次更新指针都会产生一个新的 diff,不断重复这个过程并维护全局最小 diff 即可。 这种算法的成立的前提是 k 个列表都是升序排列的,这里需要数组升序原理和上面题目是一样的,有序之后就可以对每个列表维护一个指针,进而使用上面的思路解决。 以题目中的 nums = [[1,2,3],[1,2,3],[1,2,3]] 为例: [1,2,3] [1,2,3] [1,2,3] 我们先选取所有行的最小值,也就是 [1,1,1],这时的 diff 为 0,全局最大值为 1,最小值也为 1。接下来,继续寻找备胎,看有没有更好的备胎供我们选择。 接下来的备胎可能产生于情况 1: [1,2,3] [1,2,3] [1,2,3] 移动了这行的指针,将其从原来的 0 移动一个单位到达 1。 或者情况 2: [1,2,3] [1,2,3]移动了这行的指针,将其从原来的 0 移动一个单位到达 1。 [1,2,3] 。。。 这几种情况又继续分裂更多的情况,这个就和上面的题目一样了,不再赘述。 代码123456789101112131415161718class Solution: def smallestRange(self, martrix: List[List[int]]) -> List[int]: l, r = -10**9, 10**9 # 将每一行最小的都放到堆中,同时记录其所在的行号和列号,一共 n 个齐头并进 h = [(row[0], i, 0) for i, row in enumerate(martrix)] heapq.heapify(h) # 维护最大值 max_v = max(row[0] for row in martrix) while True: min_v, row, col = heapq.heappop(h) # max_v - min_v 是当前的最大最小差值, r - l 为全局的最大最小差值。因为如果当前的更小,我们就更新全局结果 if max_v - min_v < r - l: l, r = min_v, max_v if col == len(martrix[row]) - 1: return [l, r] # 更新指针,继续往后移动一位 heapq.heappush(h, (martrix[row][col + 1], row, col + 1)) max_v = max(max_v, martrix[row][col + 1]) (代码 1.3.8) 1675. 数组的最小偏移量题目描述1234567891011121314151617181920212223242526272829303132给你一个由 n 个正整数组成的数组 nums 。你可以对数组的任意元素执行任意次数的两类操作:如果元素是 偶数 ,除以 2例如,如果数组是 [1,2,3,4] ,那么你可以对最后一个元素执行此操作,使其变成 [1,2,3,2]如果元素是 奇数 ,乘上 2例如,如果数组是 [1,2,3,4] ,那么你可以对第一个元素执行此操作,使其变成 [2,2,3,4]数组的 偏移量 是数组中任意两个元素之间的 最大差值 。返回数组在执行某些操作之后可以拥有的 最小偏移量 。示例 1:输入:nums = [1,2,3,4]输出:1解释:你可以将数组转换为 [1,2,3,2],然后转换成 [2,2,3,2],偏移量是 3 - 2 = 1示例 2:输入:nums = [4,1,5,20,3]输出:3解释:两次操作后,你可以将数组转换为 [4,2,5,5,3],偏移量是 5 - 2 = 3示例 3:输入:nums = [2,10,8]输出:3提示:n == nums.length2 <= n <= 1051 <= nums[i] <= 109 思路题目说可对数组中每一项都执行任意次操作,但其实操作是有限的。 我们只能对奇数进行一次 2 倍操作,因为 2 倍之后其就变成了偶数了。 我们可以对偶数进行若干次除 2 操作,直到等于一个奇数,不难看出这也是一个有限次的操作。 以题目中的 [1,2,3,4] 来说。我们可以: 将 1 变成 2(也可以不变) 将 2 变成 1(也可以不变) 将 3 变成 6(也可以不变) 将 4 变成 2 或 1(也可以不变) 用图来表示就是下面这样的: 这不就相当于: 从 [[1,2], [1,2], [3,6], [1,2,4]] 这样的一个二维数组中的每一行分别选取一个数,并使得其差最小么?这难道不是和上面的题目一模一样么? 这里我直接将上面的题目解法封装成了一个 api 调用了,具体看代码。 代码1234567891011121314151617181920212223242526272829303132class Solution: def smallestRange(self, martrix: List[List[int]]) -> List[int]: l, r = -10**9, 10**9 # 将每一行最小的都放到堆中,同时记录其所在的行号和列号,一共 n 个齐头并进 h = [(row[0], i, 0) for i, row in enumerate(martrix)] heapq.heapify(h) # 维护最大值 max_v = max(row[0] for row in martrix) while True: min_v, row, col = heapq.heappop(h) # max_v - min_v 是当前的最大最小差值, r - l 为全局的最大最小差值。因为如果当前的更小,我们就更新全局结果 if max_v - min_v < r - l: l, r = min_v, max_v if col == len(martrix[row]) - 1: return [l, r] # 更新指针,继续往后移动一位 heapq.heappush(h, (martrix[row][col + 1], row, col + 1)) max_v = max(max_v, martrix[row][col + 1]) def minimumDeviation(self, nums: List[int]) -> int: matrix = [[] for _ in range(len(nums))] for i, num in enumerate(nums): if num & 1 == 1: matrix[i] += [num, num * 2] else: temp = [] while num and num & 1 == 0: temp += [num] num //= 2 temp += [num] matrix[i] += temp[::-1] a, b = self.smallestRange(matrix) return b - a (代码 1.3.9) 技巧三 - 事后小诸葛 这个技巧指的是:当从左到右遍历的时候,我们是不知道右边是什么的,需要等到你到了右边之后才知道。 如果想知道右边是什么,一种简单的方式是遍历两次,第一次遍历将数据记录下来,当第二次遍历的时候,用上次遍历记录的数据。这是我们使用最多的方式。不过有时候,我们也可以在遍历到指定元素后,往前回溯,这样就可以边遍历边存储,使用一次遍历即可。具体来说就是将从左到右的数据全部收集起来,等到需要用的时候,从里面挑一个用。如果我们都要取最大值或者最小值且极值会发生变动, 就可使用堆加速。直观上就是使用了时光机回到之前,达到了事后诸葛亮的目的。 这样说你肯定不明白啥意思。没关系,我们通过几个例子来讲一下。当你看完这些例子之后,再回头看这句话。 871. 最低加油次数题目描述1234567891011121314151617181920212223242526272829303132333435363738394041汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。沿途有加油站,每个 station[i] 代表一个加油站,它位于出发位置东面 station[i][0] 英里处,并且有 station[i][1] 升汽油。假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。 示例 1:输入:target = 1, startFuel = 1, stations = []输出:0解释:我们可以在不加油的情况下到达目的地。示例 2:输入:target = 100, startFuel = 1, stations = [[10,100]]输出:-1解释:我们无法抵达目的地,甚至无法到达第一个加油站。示例 3:输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]输出:2解释:我们出发时有 10 升燃料。我们开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。然后,我们从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料),并将汽油从 10 升加到 50 升。然后我们开车抵达目的地。我们沿途在1两个加油站停靠,所以返回 2 。 提示:1 <= target, startFuel, stations[i][1] <= 10^90 <= stations.length <= 5000 < stations[0][0] < stations[1][0] < ... < stations[stations.length-1][0] < target 思路为了能够获得最低加油次数,我们肯定希望能不加油就不加油。那什么时候必须加油呢?答案应该是如果你不加油,就无法到达下一个目的地的时候。 伪代码描述就是: 12345678cur = startFuel # 刚开始有 startFuel 升汽油last = 0 # 上一次的位置for i, fuel in stations: cur -= i - last # 走过两个 staton 的耗油为两个 station 的距离,也就是 i - last if cur < 0: # 我们必须在前面就加油,否则到不了这里 # 但是在前面的哪个 station 加油呢? # 直觉告诉我们应该贪心地选择可以加汽油最多的站 i,如果加上 i 的汽油还是 cur < 0,继续加次大的站 j,直到没有更多汽油可加或者 cur > 0 上面说了要选择可以加汽油最多的站 i,如果加了油还不行,继续选择第二多的站。这种动态求极值的场景非常适合使用 heap。 具体来说就是: 每经过一个站,就将其油量加到堆。 尽可能往前开,油只要不小于 0 就继续开。 如果油量小于 0 ,就从堆中取最大的加到油箱中去,如果油量还是小于 0 继续重复取堆中的最大油量。 如果加完油之后油量大于 0 ,继续开,重复上面的步骤。否则返回 -1,表示无法到达目的地。 那这个算法是如何体现事后小诸葛的呢?你可以把自己代入到题目中进行模拟。 把自己想象成正在开车,你的目标就是题目中的要求:最少加油次数。当你开到一个站的时候,你是不知道你的油量够不够支撑到下个站的,并且就算撑不到下个站,其实也许在上个站加油会更好。所以现实中你无论如何都无法知道在当前站,我是应该加油还是不加油的,因为信息太少了。 那我会怎么做呢?如果是我在开车的话,我只能每次都加油,这样都无法到达目的地,那肯定就无法到达目的地了。但如果这样可以到达目的地,我就可以说如果我们在那个站加油,这个站选择不加就可以最少加油次数到达目的地了。你怎么不早说呢? 这不就是事后诸葛亮么? 这个事后诸葛亮体现在我们是等到没油了才去想应该在之前的某个站加油。 所以这个事后诸葛亮本质上解决的是,基于当前信息无法获取最优解,我们必须掌握全部信息之后回溯。以这道题来说,我们可以先遍历一边 station,然后将每个 station 的油量记录到一个数组中,每次我们“预见“到无法到达下个站的时候,就从这个数组中取最大的。。。。 基于此,我们可以考虑使用堆优化取极值的过程,而不是使用数组的方式。 代码12345678910111213141516171819class Solution: def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int: stations += [(target, 0)] cur = startFuel ans = 0 h = [] last = 0 for i, fuel in stations: cur -= i - last while cur < 0 and h: cur -= heapq.heappop(h) ans += 1 if cur < 0: return -1 heappush(h, -fuel) last = i return ans (代码 1.3.10) 1488. 避免洪水泛滥题目描述1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨的时候,如果第 n 个湖泊是空的,那么它就会装满水,否则这个湖泊会发生洪水。你的目标是避免任意一个湖泊发生洪水。给你一个整数数组 rains ,其中:rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。请返回一个数组 ans ,满足:ans.length == rains.length如果 rains[i] > 0 ,那么ans[i] == -1 。如果 rains[i] == 0 ,ans[i] 是你第 i 天选择抽干的湖泊。如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组 。请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生(详情请看示例 4)。 示例 1:输入:rains = [1,2,3,4]输出:[-1,-1,-1,-1]解释:第一天后,装满水的湖泊包括 [1]第二天后,装满水的湖泊包括 [1,2]第三天后,装满水的湖泊包括 [1,2,3]第四天后,装满水的湖泊包括 [1,2,3,4]没有哪一天你可以抽干任何湖泊的水,也没有湖泊会发生洪水。示例 2:输入:rains = [1,2,0,0,2,1]输出:[-1,-1,2,1,-1,-1]解释:第一天后,装满水的湖泊包括 [1]第二天后,装满水的湖泊包括 [1,2]第三天后,我们抽干湖泊 2 。所以剩下装满水的湖泊包括 [1]第四天后,我们抽干湖泊 1 。所以暂时没有装满水的湖泊了。第五天后,装满水的湖泊包括 [2]。第六天后,装满水的湖泊包括 [1,2]。可以看出,这个方案下不会有洪水发生。同时, [-1,-1,1,2,-1,-1] 也是另一个可行的没有洪水的方案。示例 3:输入:rains = [1,2,0,1,2]输出:[]解释:第二天后,装满水的湖泊包括 [1,2]。我们可以在第三天抽干一个湖泊的水。但第三天后,湖泊 1 和 2 都会再次下雨,所以不管我们第三天抽干哪个湖泊的水,另一个湖泊都会发生洪水。示例 4:输入:rains = [69,0,0,0,69]输出:[-1,69,1,1,-1]解释:任何形如 [-1,69,x,y,-1], [-1,x,69,y,-1] 或者 [-1,x,y,69,-1] 都是可行的解,其中 1 <= x,y <= 10^9示例 5:输入:rains = [10,20,20]输出:[]解释:由于湖泊 20 会连续下 2 天的雨,所以没有没有办法阻止洪水。 提示:1 <= rains.length <= 10^50 <= rains[i] <= 10^9 思路如果上面的题用事后诸葛亮描述比较牵强的话,那后面这两个题可以说很适合了。 题目说明了我们可以在不下雨的时候抽干一个湖泊,如果有多个下满雨的湖泊,我们该抽干哪个湖呢?显然应该是抽干最近即将被洪水淹没的湖。但是现实中无论如何我们都不可能知道未来哪天哪个湖泊会下雨的,即使有天气预报也不行,因此它也不 100% 可靠。 但是代码可以啊。我们可以先遍历一遍 rain 数组就知道第几天哪个湖泊下雨了。有了这个信息,我们就可以事后诸葛亮了。 “今天天气很好,我开了天眼,明天湖泊 2 会被洪水淹没,我们今天就先抽干它,否则就洪水泛滥了。”。 和上面的题目一样,我们也可以不先遍历 rain 数组,再模拟每天的变化,而是直接模拟,即使当前是晴天我们也不抽干任何湖泊。接着在模拟的过程记录晴天的情况,等到洪水发生的时候,我们再考虑前面哪一个晴天应该抽干哪个湖泊。因此这个事后诸葛亮体现在我们是等到洪水泛滥了才去想应该在之前的某天采取什么手段。 算法: 遍历 rain, 模拟每天的变化 如果 rain 当前是 0 表示当前是晴天,我们不抽干任何湖泊。但是我们将当前天记录到 sunny 数组。 如果 rain 大于 0,说明有一个湖泊下雨了,我们去看下下雨的这个湖泊是否发生了洪水泛滥。其实就是看下下雨前是否已经有水了。这提示我们用一个数据结构 lakes 记录每个湖泊的情况,我们可以用 0 表示没有水,1 表示有水。这样当湖泊 i 下雨的时候且 lakes[i] = 1 就会发生洪水泛滥。 如果当前湖泊发生了洪水泛滥,那么就去 sunny 数组找一个晴天去抽干它,这样它就不会洪水泛滥,接下来只需要保持 lakes[i] = 1 即可。 这道题没有使用到堆,我是故意的。之所以这么做,是让大家明白事后诸葛亮这个技巧并不是堆特有的,实际上这就是一种普通的算法思想,就好像从后往前遍历一样。只不过,很多时候,我们事后诸葛亮的场景,需要动态取最大最小值, 这个时候就应该考虑使用堆了,这其实又回到文章开头的一个中心了,所以大家一定要灵活使用这些技巧,不可生搬硬套。 下一道题是一个不折不扣的事后诸葛亮 + 堆优化的题目。 代码1234567891011121314151617class Solution: def avoidFlood(self, rains: List[int]) -> List[int]: ans = [1] * len(rains) lakes = collections.defaultdict(int) sunny = [] for i, rain in enumerate(rains): if rain > 0: ans[i] = -1 if lakes[rain - 1] == 1: if 0 == len(sunny): return [] ans[sunny.pop()] = rain lakes[rain - 1] = 1 else: sunny.append(i) return ans (代码 1.3.11) 2021-04-06 fixed: 上面的代码有问题。错误的原因在于上述算法如果当前湖泊发生了洪水泛滥,那么就去 sunny 数组找一个晴天去抽干它,这样它就不会洪水泛滥部分的实现不对。sunny 数组找一个晴天去抽干它的根本前提是 出现晴天的时候湖泊里面要有水才能抽,如果晴天的时候,湖泊里面没有水也不行。这提示我们的 lakes 不存储 0 和 1 ,而是存储发生洪水是第几天。这样问题就变为在 sunny 中找一个日期大于 lakes[rain-1] 的项,并将其移除 sunny 数组。由于 sunny 数组是有序的,因此我们可以使用二分来进行查找。 由于我们需要删除 sunny 数组的项,因此时间复杂度不会因为使用了二分而降低。 正确的代码应该为: 123456789101112131415161718class Solution: def avoidFlood(self, rains: List[int]) -> List[int]: ans = [1] * len(rains) lakes = {} sunny = [] for i, rain in enumerate(rains): if rain > 0: ans[i] = -1 if rain - 1 in lakes: j = bisect.bisect_left(sunny, lakes[rain - 1]) if j == len(sunny): return [] ans[sunny.pop(j)] = rain lakes[rain - 1] = i else: sunny.append(i) return ans 1642. 可以到达的最远建筑题目描述123456789给你一个整数数组 heights ,表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。你从建筑物 0 开始旅程,不断向后面的建筑物移动,期间可能会用到砖块或梯子。当从建筑物 i 移动到建筑物 i+1(下标 从 0 开始 )时:如果当前建筑物的高度 大于或等于 下一建筑物的高度,则不需要梯子或砖块如果当前建筑的高度 小于 下一个建筑的高度,您可以使用 一架梯子 或 (h[i+1] - h[i]) 个砖块如果以最佳方式使用给定的梯子和砖块,返回你可以到达的最远建筑物的下标(下标 从 0 开始 )。 12345678910111213141516171819202122232425262728示例 1:输入:heights = [4,2,7,6,9,14,12], bricks = 5, ladders = 1输出:4解释:从建筑物 0 出发,你可以按此方案完成旅程:- 不使用砖块或梯子到达建筑物 1 ,因为 4 >= 2- 使用 5 个砖块到达建筑物 2 。你必须使用砖块或梯子,因为 2 < 7- 不使用砖块或梯子到达建筑物 3 ,因为 7 >= 6- 使用唯一的梯子到达建筑物 4 。你必须使用砖块或梯子,因为 6 < 9无法越过建筑物 4 ,因为没有更多砖块或梯子。示例 2:输入:heights = [4,12,2,7,3,18,20,3,19], bricks = 10, ladders = 2输出:7示例 3:输入:heights = [14,3,19,3], bricks = 17, ladders = 0输出:3 提示:1 <= heights.length <= 1051 <= heights[i] <= 1060 <= bricks <= 1090 <= ladders <= heights.length 思路我们可以将梯子看出是无限的砖块,只不过只能使用一次,我们当然希望能将好梯用在刀刃上。和上面一样,如果是现实生活,我们是无法知道啥时候用梯子好,啥时候用砖头好的。 没关系,我们继续使用事后诸葛亮法,一次遍历就可完成。和前面的思路类似,那就是我无脑用梯子,等梯子不够用了,我们就要开始事后诸葛亮了,要是前面用砖头就好了。那什么时候用砖头就好了呢?很明显就是当初用梯子的时候高度差,比现在的高度差小。 直白点就是当初我用梯子爬了个 5 米的墙,现在这里有个十米的墙,我没梯子了,只能用 10 个砖头了。要是之前用 5 个砖头,现在不就可以用一个梯子,从而省下 5 个砖头了吗? 这提示我们将用前面用梯子跨越的建筑物高度差存起来,等到后面梯子用完了,我们将前面被用的梯子“兑换”成砖头继续用。以上面的例子来说,我们就可以先兑换 10 个砖头,然后将 5 个砖头用掉,也就是相当于增加了 5 个砖头。 如果前面多次使用了梯子,我们优先“兑换”哪次呢?显然是优先兑换高度差大的,这样兑换的砖头才最多。这提示每次都从之前存储的高度差中选最大的,并在“兑换”之后将其移除。这种动态求极值的场景用什么数据结构合适?当然是堆啦。 代码123456789101112131415161718class Solution: def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int: h = [] for i in range(1, len(heights)): diff = heights[i] - heights[i - 1] if diff <= 0: continue if bricks < diff and ladders > 0: ladders -= 1 if h and -h[0] > diff: bricks -= heapq.heappop(h) else: continue bricks -= diff if bricks < 0: return i - 1 heapq.heappush(h, -diff) return len(heights) - 1 (代码 1.3.12) 四大应用接下来是本文的最后一个部分《四大应用》,目的是通过这几个例子来帮助大家巩固前面的知识。 1. topK求解 topK 是堆的一个很重要的功能。这个其实已经在前面的固定堆部分给大家介绍过了。 这里直接引用前面的话: “其实求第 k 小的数最简单的思路是建立小顶堆,将所有的数先全部入堆,然后逐个出堆,一共出堆 k 次。最后一次出堆的就是第 k 小的数。然而,我们也可不先全部入堆,而是建立大顶堆(注意不是上面的小顶堆),并维持堆的大小为 k 个。如果新的数入堆之后堆的大小大于 k,则需要将堆顶的数和新的数进行比较,并将较大的移除。这样可以保证堆中的数是全体数字中最小的 k 个,而这最小的 k 个中最大的(即堆顶)不就是第 k 小的么?这也就是选择建立大顶堆,而不是小顶堆的原因。” 其实除了第 k 小的数,我们也可以将中间的数全部收集起来,这就可以求出最小的 k 个数。和上面第 k 小的数唯一不同的点在于需要收集 popp 出来的所有的数。 需要注意的是,有时候权重并不是原本数组值本身的大小,也可以是距离,出现频率等。 相关题目: 面试题 17.14. 最小 K 个数 347. 前 K 个高频元素 973. 最接近原点的 K 个点 力扣中有关第 k 的题目很多都是堆。除了堆之外,第 k 的题目其实还会有一些找规律的题目,对于这种题目则可以通过分治+递归的方式来解决,具体就不再这里展开了,感兴趣的可以和我留言讨论。 2. 带权最短距离关于这点,其实我在前面部分也提到过了,只不过当时只是一带而过。原话是“不过 BFS 真的就没人用优先队列实现么?当然不是!比如带权图的最短路径问题,如果用队列做 BFS 那就需要优先队列才可以,因为路径之间是有权重的差异的,这不就是优先队列的设计初衷么。使用优先队列的 BFS 实现典型的就是 dijkstra 算法。” DIJKSTRA 算法主要解决的是图中任意两点的最短距离。 算法的基本思想是贪心,每次都遍历所有邻居,并从中找到距离最小的,本质上是一种广度优先遍历。这里我们借助堆这种数据结构,使得可以在 $logN$ 的时间内找到 cost 最小的点,其中 N 为 堆的大小。 代码模板: 1234567891011121314151617def dijkstra(graph, start, end): # 堆里的数据都是 (cost, i) 的二元祖,其含义是“从 start 走到 i 的距离是 cost”。 heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 (代码 1.4.1) 可以看出代码模板和 BFS 基本是类似的。如果你自己将堆的 key 设定为 steps 也可模拟实现 BFS,这个在前面已经讲过了,这里不再赘述。 比如一个图是这样的: 1234E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F \\ /\\ \\ || -------- 2 ---------> G ------- 1 ------ 我们使用邻接矩阵来构造: 1234567891011G = { \"B\": [[\"C\", 1]], \"C\": [[\"D\", 1]], \"D\": [[\"F\", 1]], \"E\": [[\"B\", 1], [\"G\", 2]], \"F\": [], \"G\": [[\"F\", 1]],}shortDistance = dijkstra(G, \"E\", \"C\")print(shortDistance) # E -- 3 --> F -- 3 --> C == 6 会了这个算法模板, 你就可以去 AC 743. 网络延迟时间 了。 完整代码: 12345678910111213141516171819202122232425262728class Solution: def dijkstra(self, graph, start, end): heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, w in times: graph[fr - 1].append((to - 1, w)) ans = -1 for to in range(N): # 调用封装好的 dijkstra 方法 dist = self.dijkstra(graph, K - 1, to) if dist == -1: return -1 ans = max(ans, dist) return ans (代码 1.4.2) 你学会了么? 上面的算法并不是最优解,我只是为了体现将 dijkstra 封装为 api 调用 的思想。一个更好的做法是一次遍历记录所有的距离信息,而不是每次都重复计算。时间复杂度会大大降低。这在计算一个点到图中所有点的距离时有很大的意义。 为了实现这个目的,我们的算法会有什么样的调整? 提示:你可以使用一个 dist 哈希表记录开始点到每个点的最短距离来完成。想出来的话,可以用力扣 882 题去验证一下哦~ 其实只需要做一个小的调整就可以了,由于调整很小,直接看代码会比较好。 代码: 12345678910111213141516171819202122class Solution: def dijkstra(self, graph, start, end): heap = [(0, start)] # cost from start node,end node dist = {} while heap: (cost, u) = heapq.heappop(heap) if u in dist: continue dist[u] = cost for v, c in graph[u]: if v in dist: continue next = cost + c heapq.heappush(heap, (next, v)) return dist def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, w in times: graph[fr - 1].append((to - 1, w)) ans = -1 dist = self.dijkstra(graph, K - 1, to) return -1 if len(dist) != N else max(dist.values()) (代码 1.4.3) 可以看出我们只是将 visitd 替换成了 dist,其他不变。另外 dist 其实只是带了 key 的 visited,它这里也起到了 visitd 的作用。 如果你需要计算一个节点到其他所有节点的最短路径,可以使用一个 dist (一个 hashmap)来记录出发点到所有点的最短路径信息,而不是使用 visited (一个 hashset)。 类似的题目也不少, 我再举一个给大家 787. K 站中转内最便宜的航班。题目描述: 1234567891011121314有 n 个城市通过 m 个航班连接。每个航班都从城市 u 开始,以价格 w 抵达 v。现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到从 src 到 dst 最多经过 k 站中转的最便宜的价格。 如果没有这样的路线,则输出 -1。 示例 1:输入:n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]src = 0, dst = 2, k = 1输出: 200解释:城市航班图如下 1234567891011从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。示例 2:输入:n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]src = 0, dst = 2, k = 0输出: 500解释:城市航班图如下 123456789101112从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示。 提示:n 范围是 [1, 100],城市标签从 0 到 n - 1航班数量范围是 [0, n * (n - 1) / 2]每个航班的格式 (src, dst, price)每个航班的价格范围是 [1, 10000]k 范围是 [0, n - 1]航班没有重复,且不存在自环 这道题和上面的没有本质不同, 我仍然将其封装成 API 来使用,具体看代码就行。 这道题唯一特别的点在于如果中转次数大于 k,也认为无法到达。这个其实很容易,我们只需要在堆中用元组来多携带一个 steps即可,这个 steps 就是 不带权 BFS 中的距离。如果 pop 出来 steps 大于 K,则认为非法,我们跳过继续处理即可。 12345678910111213141516171819202122232425class Solution: # 改造一下,增加参数 K,堆多携带一个 steps 即可 def dijkstra(self, graph, start, end, K): heap = [(0, start, 0)] visited = set() while heap: (cost, u, steps) = heapq.heappop(heap) if u in visited: continue visited.add((u, steps)) if steps > K: continue if u == end: return cost for v, c in graph[u]: if (v, steps) in visited: continue next = cost + c heapq.heappush(heap, (next, v, steps + 1)) return -1 def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, price in flights: graph[fr].append((to, price)) # 调用封装好的 dijkstra 方法 return self.dijkstra(graph, src, dst, K + 1) (代码 1.4.4) 3. 因子分解和上面两个应用一下,这个我在前面 《313. 超级丑数》部分也提到了。 回顾一下丑数的定义: 丑数就是质因数只包含 2, 3, 5 的正整数。 因此丑数本质就是一个数经过因子分解之后只剩下 2,3,5 的整数,而不携带别的因子了。 关于丑数的题目有很多,大多数也可以从堆的角度考虑来解。只不过有时候因子个数有限,不使用堆也容易解决。比如:264. 丑数 II 就可以使用三个指针来记录即可,这个技巧在前面也讲过了,不再赘述。 一些题目并不是丑数,但是却明确提到了类似因子的信息,并让你求第 k 大的 xx,这个时候优先考虑使用堆来解决。如果题目中夹杂一些其他信息,比如有序,则也可考虑二分法。具体使用哪种方法,要具体问题具体分析,不过在此之前大家要对这两种方法都足够熟悉才行。 4. 堆排序前面的三种应用或多或少在前面都提到过。而堆排序却未曾在前面提到。 直接考察堆排序的题目几乎没有。但是面试却有可能会考察,另外学习堆排序对你理解分治等重要算法思维都有重要意义。个人感觉,堆排序,构造二叉树,构造线段树等算法都有很大的相似性,掌握一种,其他都可以触类旁通。 实际上,经过前面的堆的学习,我们可以封装一个堆排序,方法非常简单。 这里我放一个使用堆的 api 实现堆排序的简单的示例代码: 1234567h = [9,5,2,7]heapq.heapify(h)ans = []while h: ans.append(heapq.heappop(h))print(ans) # 2,5,7,9 明白了示例, 那封装成通用堆排序就不难了。 123456def heap_sort(h): heapq.heapify(h) ans = [] while h: ans.append(heapq.heappop(h)) return ans 这个方法足够简单,如果你明白了前面堆的原理,让你手撸一个堆排序也不难。可是这种方法有个弊端,它不是原位算法,也就是说你必须使用额外的空间承接结果,空间复杂度为 $O(N)$。但是其实调用完堆排序的方法后,原有的数组内存可以被释放了,因此理论上来说空间也没浪费,只不过我们计算空间复杂度的时候取的是使用内存最多的时刻,因此使用原地算法毫无疑问更优秀。如果你实在觉得不爽这个实现,也可以采用原地的修改的方式。这倒也不难,只不过稍微改造一下前面的堆的实现即可,由于篇幅的限制,这里不多讲了。 总结堆和队列有千丝万缕的联系。 很多题目我都是先思考使用堆来完成。然后发现每次入堆都是 + 1,而不会跳着更新,比如下一个是 + 2,+3 等等,因此使用队列来完成性能更好。 比如 649. Dota2 参议院 和 1654. 到家的最少跳跃次数 等。 堆的中心就一个,那就是动态求极值。 而求极值无非就是最大值或者最小值,这不难看出。如果求最大值,我们可以使用大顶堆,如果求最小值,可以用最小堆。而实际上,如果没有动态两个字,很多情况下没有必要使用堆。比如可以直接一次遍历找出最大的即可。而动态这个点不容易看出来,这正是题目的难点。这需要你先对问题进行分析, 分析出这道题其实就是动态求极值,那么使用堆来优化就应该被想到。 堆的实现有很多。比如基于链表的跳表,基于数组的二叉堆和基于红黑树的实现等。这里我们介绍了两种主要实现 并详细地讲述了二叉堆的实现,不仅是其实现简单,而且其在很多情况下表现都不错,推荐大家重点掌握二叉堆实现。 对于二叉堆的实现,核心点就一点,那就是始终维护堆的性质不变,具体是什么性质呢?那就是 父节点的权值不大于儿子的权值(小顶堆)。为了达到这个目的,我们需要在入堆和出堆的时候,使用上浮和下沉操作,并恰当地完成元素交换。具体来说就是上浮过程和比它大的父节点进行交换,下沉过程和两个子节点中较小的进行交换,当然前提是它有子节点且子节点比它小。 关于堆化我们并没有做详细分析。不过如果你理解了本文的入堆操作,这其实很容易。因此堆化本身就是一个不断入堆的过程,只不过将时间上的离散的操作变成了一次性操作而已。 另外我给大家介绍了三个堆的做题技巧,分别是: 固定堆,不仅可以解决第 k 问题,还可有效利用已经计算的结果,避免重复计算。 多路归并,本质就是一个暴力解法,和暴力递归没有本质区别。如果你将其转化为递归,也是一种不能记忆化的递归。因此更像是回溯算法。 事后小诸葛。有些信息,我们在当前没有办法获取,就可用一种数据结构存起来,方便之后”东窗事发“的时候查。这种数据解决可以是很多,常见的有哈希表和堆。你也可以将这个技巧看成是事后后悔,有的人比较能接受这种叫法,不过不管叫法如何,指的都是这个含义。 最后给大家介绍了四种应用,这四种应用除了堆排序,其他在前面或多或少都讲过,它们分别是: topK 带权最短路径 因子分解 堆排序 这四种应用实际上还是围绕了堆的一个中心动态取极值,这四种应用只不过是灵活使用了这个特点罢了。因此大家在做题的时候只要死记动态求极值即可。如果你能够分析出这道题和动态取极值有关,那么请务必考虑堆。接下来我们就要在脑子中过一下复杂度,对照一下题目数据范围就大概可以估算出是否可行啦。 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。目前已经 39K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/categories/堆/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/tags/堆/"}]},{"title":"蓄水池抽样","slug":"reservoid-sampling","date":"2021-01-11T16:00:00.000Z","updated":"2023-01-05T12:24:50.176Z","comments":true,"path":"2021/01/12/reservoid-sampling/","link":"","permalink":"https://lucifer.ren/blog/2021/01/12/reservoid-sampling/","excerpt":"力扣中关于蓄水池抽样问题官方标签是 2 道,根据我的做题情况来看,可能有三四道。比重算是比较低的,大家可以根据自己的实际情况选择性掌握。 蓄水池抽样的算法思维很巧妙,代码简单且容易理解,就算不掌握它,作为了解也是很不错的。","text":"力扣中关于蓄水池抽样问题官方标签是 2 道,根据我的做题情况来看,可能有三四道。比重算是比较低的,大家可以根据自己的实际情况选择性掌握。 蓄水池抽样的算法思维很巧妙,代码简单且容易理解,就算不掌握它,作为了解也是很不错的。 问题描述给出一个数据流,我们需要在此数据流中随机选取 k 个数。由于这个数据流的长度很大,因此需要边遍历边处理,而不能将其一次性全部加载到内存。 请写出一个随机选择算法,使得数据流中所有数据被等概率选中。 这种问题的表达形式有很多。比如让你随机从一个矩形中抽取 k 个点,随机从一个单词列表中抽取 k 个单词等等,要求你等概率随机抽取。不管描述怎么变,其本质上都是一样的。今天我们就来看看如何做这种题。 算法描述这个算法叫蓄水池抽样算法(reservoid sampling)。 其基本思路是: 构建一个大小为 k 的数组,将数据流的前 k 个元素放入数组中。 对数据流的前 k 个数先不进行任何处理。 从数据流的第 k + 1 个数开始,在 [1, i] 之间选一个数 rand,其中 i 表示当前是第几个数。 如果 rand 大于等于 k 什么都不做 如果 rand 小于 k, 将 rand 和 i 交换,也就是说选择当前的数代替已经被选中的数(备胎)。 最终返回幸存的备胎即可 这种算法的核心在于先以某一种概率选取数,并在后续过程以另一种概率换掉之前已经被选中的数。因此实际上每个数被最终选中的概率都是被选中的概率 * 不被替换的概率。 伪代码: 伪代码参考的某一本算法书,并略有修改。 12345Init : a reservoir with the size: kfor i= k+1 to N if(random(1, i) < k) { SWAP the Mth value and ith value } 这样可以保证被选择的数是等概率的吗?答案是肯定的。 当 i <= k ,i 被选中的概率是 1。 到第 k + 1 个数时,第 k + 1 个数被选中的概率(走进上面的 if 分支的概率)是 $\\frac{k}{k+1}$,到第 k + 2 个数时,第 k + 2 个数被选中的概率(走进上面的 if 分支的概率)是 $\\frac{k}{k+2}$,以此类推。那么第 n 个数被选中的概率就是 $\\frac{k}{n}$ 上面分析了被选中的概率,接下来分析不被替换的概率。到第 k + 1 个数时,前 k 个数被替换的概率是 $\\frac{1}{k}$。到前 k + 2 个数时,第 k + 2 个数被替换的概率是 $\\frac{1}{k}$,以此类推。也就是说所有的被替换的概率都是 $\\frac{1}{k}$。知道了被替换的概率,那么不被替换的概率其实就是 1 - 被替换的概率。 因此对于前 k 个数,最终被选择的概率都是 1 * 不被 k + 1 替换的概率 * 不被 k + 2 替换的概率 * … 不被 n 替换的概率,即 1 * (1 - 被 k + 1 替换的概率) * (1 - 被 k + 2 替换的概率) * … (1 - 被 n 替换的概率),即 $1 \\times (1 - \\frac{k}{k+1} \\times \\frac{1}{k}) \\times (1 - \\frac{k}{k+2} \\times \\frac{1}{k}) \\times … \\times (1 - \\frac{k}{n} \\times \\frac{1}{k}) = \\frac{k}{n} $。 对于 第 i (i > k) 个数,最终被选择的概率是 第 i 步被选中的概率 * 不被第 i + 1 步替换的概率 * … * 不被第 n 步被替换的概率, 即 $\\frac{k}{k+1} \\times (1 - \\frac{k}{k+2} \\times \\frac{1}{k}) \\times … \\times (1 - \\frac{k}{n} \\times \\frac{1}{k}) = \\frac{k}{n} $。 总之,不管是哪个数,被选中的概率都是 $\\frac{k}{n}$,满足概率相等的需求。 相关题目 382. 链表随机节点 398. 随机数索引 497. 非重叠矩形中的随机点 总结蓄水池抽样算法核心代码非常简单。但是却不容易想到,尤其是之前没见过的情况下。其核心点在于每个数被最终选中的概率都是被选中的概率 * 不被替换的概率。于是我们可以采取某一种动态手段,使得每一轮都有概率选中和替换一些数字。 上面我们有给出了概率相等的证明过程,大家不妨自己尝试证明一下。之后结合文末的相关题目练习一下,效果会更好。","categories":[{"name":"蓄水池抽样","slug":"蓄水池抽样","permalink":"https://lucifer.ren/blog/categories/蓄水池抽样/"}],"tags":[{"name":"概率","slug":"概率","permalink":"https://lucifer.ren/blog/tags/概率/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"蓄水池抽样","slug":"蓄水池抽样","permalink":"https://lucifer.ren/blog/tags/蓄水池抽样/"}]},{"title":"几乎刷完了力扣所有的堆题,我发现了这些东西。。。","slug":"heap","date":"2020-12-25T16:00:00.000Z","updated":"2023-01-05T12:24:49.886Z","comments":true,"path":"2020/12/26/heap/","link":"","permalink":"https://lucifer.ren/blog/2020/12/26/heap/","excerpt":"大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(就是本文)","text":"大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(就是本文) 一点絮叨堆标签在 leetcode 一共有 42 道题。 为了准备这个专题,我将 leetcode 几乎所有的堆题目都刷了一遍。 可以看出,除了 3 个上锁的,其他我都刷了一遍。通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。 需要注意的是,本文不对堆和优先队列进行区分。因此本文提到的堆和优先队列大家可以认为是同一个东西。如果大家对两者的学术区别感兴趣,可以去查阅相关资料。 如果不做特殊说明,本文的堆均指的是小顶堆。 堆的题难度几何?堆确实是一个难度不低的专题。从官方的难度标签来看,堆的题目一共才 42 道,困难度将近 50%。没有对比就没有伤害,树专题困难度只有不到 10%。 从通过率来看,一半以上的题目平均通过率在 50% 以下。作为对比, 树的题目通过率在 50% 以下的只有不到三分之一。 不过大家不要太有压力。lucifer 给大家带来了一个口诀一个中心,两种实现,三个技巧,四大应用,我们不仅讲实现和原理,更讲问题的背景以及套路和模板。 文章里涉及的模板大家随时都可以从我的力扣刷题插件 leetcode-cheatsheet 中获取。 堆的使用场景分析堆其实就是一种数据结构,数据结构是为了算法服务的,那堆这种数据结构是为哪种算法服务的?它的适用场景是什么? 这是每一个学习堆的人第一个需要解决的问题。在什么情况下我们会使用堆呢?堆的原理是什么?如何实现一个堆?别急,本文将一一为你揭秘。 在进入正文之前,给大家一个学习建议 - 先不要纠结堆怎么实现的,咱先了解堆解决了什么问题。当你了解了使用背景和解决的问题之后,然后当一个调包侠,直接用现成的堆的 api 解决问题。等你理解得差不多了,再去看堆的原理和实现。我就是这样学习堆的,因此这里就将这个学习经验分享给你。 为了对堆的使用场景进行说明,这里我虚拟了一个场景。 下面这个例子很重要, 后面会反复和这个例子进行对比。 一个挂号系统问题描述假如你是一个排队挂号系统的技术负责人。该系统需要给每一个前来排队的人发放一个排队码(入队),并根据先来后到的原则进行叫号(出队)。 除此之外,我们还可以区分了几种客户类型, 分别是普通客户, VIP 客户 和 至尊 VIP 客户。 如果不同的客户使用不同的窗口的话,我该如何设计实现我的系统?(大家获得的服务不一样,比如 VIP 客户是专家级医生,普通客户是普通医生) 如果不同的客户都使用一个窗口的话,我该如何设计实现我的系统?(大家获得的服务都一样,但是优先级不一样。比如其他条件相同情况下(比如他们都是同时来挂号的),VIP 客户 优先级高于普通客户) 我该如何设计我的系统才能满足需求,并获得较好的扩展性? 初步的解决方案如果不同的客户使用不同的窗口。那么我们可以设计三个队列,分别存放正在排队的三种人。这种设计满足了题目要求,也足够简单。 如果我们只有一个窗口,所有的病人需要使用同一个队列,并且同样的客户类型按照上面讲的先到先服务原则,但是不同客户类型之间可能会插队。 简单起见,我引入了虚拟时间这个概念。具体来说: 普通客户的虚拟时间就是真实时间。 VIP 客户的虚拟时间按照实际到来时间减去一个小时。比如一个 VIP 客户是 14:00 到达的,我认为他是 13:00 到的。 至尊 VIP 客户的虚拟时间按照实际到来时间减去两个小时。比如一个 至尊 VIP 客户是 14:00 到达的,我认为他是 12:00 到的。 这样,我们只需要按照上面的”虚拟到达时间“进行先到先服务即可。 因此我们就可以继续使用刚才的三个队列的方式,只不过队列存储的不是真实时间,而是虚拟时间。每次开始叫号的时候,我们使用虚拟时间比较,虚拟时间较小的先服务即可。 不难看出,队列内部的时间都是有序。 而这里的虚拟时间,其实就是优先队列中的优先权重,虚拟时间越小,权重越大。 可以插队怎么办?这种算法很好地完成了我们的需求,复杂度相当不错。不过事情还没有完结,这一次我们又碰到新的产品需求: 如果有别的门诊的病人转院到我们的诊所,则按照他之前的排队信息算,比如 ta 是 12:00 在别的院挂的号,那么转到本院仍然是按照 12:00 挂号算。 如果被叫到号三分钟没有应答,将其作废。但是如果后面病人重新来了,则认为他是当前时间减去一个小时的虚拟时间再次排队。比如 ta 是 13:00 被叫号,没有应答,13:30 又回来,则认为他是 12:30 排队的,重新进队列。 这样就有了”插队“的情况了。该怎么办呢?一个简单的做法是,将其插入到正确位置,并重新调整后面所有人的排队位置。 如下图是插入一个 1:30 开始排队的普通客户的情况。 (查找插入位置) (将其插入) 如果队列使用数组实现, 上面插队过程的时间复杂度为 $O(N)$,其中 $N$ 为被插队的队伍长度。如果队伍很长,那么调整的次数明显增加。 不过我们发现,本质上我们就是在维护一个有序列表,而使用数组方式去维护有序列表的好处是可以随机访问,但是很明显这个需求并不需要这个特性。如果使用链表去实现,那么时间复杂度理论上是 $O(1)$,但是如何定位到需要插入的位置呢?朴素的思维是遍历查找,但是这样的时间复杂度又退化到了 $O(N)$。有没有时间复杂度更好的做法呢?答案就是本文的主角优先队列。 上面说了链表的实现核心在于查找也需要 $O(N)$,我们可以优化这个过程吗?实际上这就是优先级队列的链表实现,由于是有序的,我们可以用跳表加速查找,时间复杂度可以优化到 $O(logN)$。 其实算法界有很多类似的问题。比如建立数据库索引的算法,如果给某一个有序的列添加索引,不能每次插入一条数据都去调整所有的数据吧(上面的数组实现)?因此我们可以用平衡树来实现,这样每次插入可以最多调整 $(O(logN))$。优先队列的另外一种实现 - 二叉堆就是这个思想,时间复杂度也可以优化到 $O(logN)$ 本文只讲解常见的二叉堆实现,对于跳表和红黑树不再这里讲。 关于优先队列的二叉堆实现,我们会在后面给大家详细介绍。这里大家只有明白优先队列解决的问题是什么就可以了。 使用堆解决问题堆的两个核心 API 是 push 和 pop。 大家先不考虑它怎么实现的,你可以暂时把 ta 想象成一个黑盒,提供了两个 api: push: 推入一个数据,内部怎么组织我不管。对应我上面场景里面的排队和插队。 pop: 弹出一个数据,该数据一定是最小的,内部怎么实现我不管。对应我上面场景里面的叫号。 这里的例子其实是小顶堆。而如果弹出的数据一定是最大的,那么对应的实现为大顶堆。 借助这两个 api 就可以实现上面的需求。 12345678910# 12:00 来了一个普通的顾客(push)heapq.heappush(normal_pq, '12:00')# 12:30 来了一个普通顾客(push)heapq.heappush(normal_pq, '12:30')# 13:00 来了一个普通顾客(push)heapq.heappush(normal_pq, '13:00')# 插队(push)。时间复杂度可以达到 O(logN)。如何做到先不管,我们先会用就行,具体实现细节后面再讲。heapq.heappush(normal_pq, '12: 20')# 叫号(pop)。12:00 来的先被叫到。需要注意的是这里的弹出时间复杂度也变成了 O(logN),这或许就是幸福的代价吧。heapq.heappop(normal_pq) 小结上面这个场景单纯使用数组和链表都可以满足需求,但是使用其他数据结构在应对”插队“的情况表现地会更好。 具体来说: 如果永远都维护一个有序数组的方式取极值很容易,但是插队麻烦。 如果永远都维护一个有序链表的方式取极值也容易。 不过要想查找足够快,而不是线性扫描,就需要借助索引,这种实现对应的就是优先级队列的跳表实现。 如果永远都维护一个树的方式取极值也可以实现,比如根节点就是极值,这样 O(1) 也可以取到极值,但是调整过程需要 $O(logN)$。这种实现对应的就是优先级队列的二叉堆实现。 简单总结下就是,堆就是动态帮你求极值的。当你需要动态求最大或最小值就就用它。而具体怎么实现,复杂度的分析我们之后讲,现在你只要记住使用场景,堆是如何解决这些问题的以及堆的 api 就够了。 队列 VS 优先队列上面通过一个例子带大家了解了一下优先队列。那么在接下来讲具体实现之前,我觉得有必要回答下一个大家普遍关心的问题,那就是优先队列是队列么? 很多人觉得队列和优先队列是完全不同的东西,就好像 Java 和 JavaScript 一样,我看了很多文章都是这么说的。 而我不这么认为。实际上,普通的队列也可以看成是一个特殊的优先级队列, 这和网上大多数的说法优先级队列和队列没什么关系有所不同。我认为队列无非就是以时间这一变量作为优先级的优先队列,时间越早,优先级越高,优先级越高越先出队。 大家平时写 BFS 的时候都会用到队列来帮你处理节点的访问顺序。那使用优先队列行不行?当然可以了!我举个例子: 例题 - 513. 找树左下角的值题目描述12345678910111213141516171819202122232425262728293031定一个二叉树,在树的最后一行找到最左边的值。示例 1:输入: 2 / \\ 1 3输出:1 示例 2:输入: 1 / \\ 2 3 / / \\ 4 5 6 / 7输出:7 注意: 您可以假设树(即给定的根节点)不为 NULL。 思路我们可以使用 BFS 来做一次层次遍历,并且每一层我们都从右向左遍历,这样层次遍历的最后一个节点就是树左下角的节点。 常规的做法是使用双端队列(就是队列)来实现,由于队列的先进先出原则很方便地就能实现层次遍历的效果。 代码对于代码看不懂的同学,可以先不要着急。等完整读完本文之后再回过头看会容易很多。下同,不再赘述。 Python Code: 123456789101112131415class Solution: def findBottomLeftValue(self, root: TreeNode) -> int: if root is None: return None queue = collections.deque([root]) ans = None while queue: size = len(queue) for _ in range(size): ans = node = queue.popleft() if node.right: queue.append(node.right) if node.left: queue.append(node.left) return ans.val 实际上, 我们也可以使用优先队列的方式,思路和代码也几乎和上面完全一样。 123456789101112131415161718class Solution: def findBottomLeftValue(self, root: TreeNode) -> int: if root is None: return None queue = [] # 堆存储三元组(a,b,c),a 表示层级,b 表示节点编号(以完全二叉树的形式编号,空节点也编号),c 是节点本身 heapq.heappush(queue, (1, 1, root)) ans = None while queue: size = len(queue) for _ in range(size): level, i, node = heapq.heappop(queue) ans = node if node.right: heapq.heappush(queue, (level + 1, 2 * i + 1, node.right)) if node.left: heapq.heappush(queue, (level + 1, 2 * i + 2, node.left)) return ans.val 小结所有使用队列的地方,都可以使用优先队列来完成,反之却不一定。 既然优先队列这么厉害,那平时都用优先队列不就行了?为啥使用队列的地方没见过别人用堆呢?最核心的原因是时间复杂度更差了。 比如上面的例子,本来入队和出队都可是很容易地在 $O(1)$ 的时间完成。而现在呢?入队和出队的复杂度都是 $O(logN)$,其中 N 为当前队列的大小。因此在没有必要的地方使用堆,会大大提高算法的时间复杂度,这当然不合适。说的粗俗一点就是脱了裤子放屁。 不过 BFS 真的就没人用优先队列实现么?当然不是!比如带权图的最短路径问题,如果用队列做 BFS 那就需要优先队列才可以,因为路径之间是有权重的差异的,这不就是优先队列的设计初衷么。使用优先队列的 BFS 实现典型的就是 dijkstra 算法。 这再一次应征了我的那句话队列就是一种特殊的优先队列而已。特殊到大家的权重就是按照到来的顺序定,谁先来谁的优先级越高。在这种特殊情况下,我们没必须去维护堆来完成,进而获得更好的时间复杂度。 一个中心堆的问题核心点就一个,那就是动态求极值。动态和极值二者缺一不可。 求极值比较好理解,无非就是求最大值或者最小值,而动态却不然。比如要你求一个数组的第 k 小的数,这是动态么?这其实完全看你怎么理解。而在我们这里,这种情况就是动态的。 如何理解上面的例子是动态呢? 你可以这么想。由于堆只能求极值。比如能求最小值,但不能直接求第 k 小的值。 那我们是不是先求最小的值,然后将其出队(对应上面例子的叫号)。然后继续求最小的值,这个时候求的就是第 2 小了。如果要求第 k 小,那就如此反复 k 次即可。 这个过程,你会发现数据是在动态变化的,对应的就是堆的大小在变化。 接下来,我们通过几个例子来进行说明。 例一 - 1046. 最后一块石头的重量题目描述12345678910111213141516171819202122232425有一堆石头,每块石头的重量都是正整数。每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:如果 x == y,那么两块石头都会被完全粉碎;如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。 示例:输入:[2,7,4,1,8,1]输出:1解释:先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。 提示:1 <= stones.length <= 301 <= stones[i] <= 1000 思路题目比较简单,直接模拟即可。需要注意的是,每次选择两个最重的两个石头进行粉碎之后,最重的石头的重量便发生了变化。这会影响到下次取最重的石头。简单来说就是最重的石头在模拟过程中是动态变化的。 这种动态取极值的场景使用堆就非常适合。 当然看下这个数据范围1 <= stones.length <= 30 且 1 <= stones[i] <= 1000,使用计数的方式应该也是可以的。 代码Java Code: 1234567891011121314151617181920212223242526import java.util.PriorityQueue;public class Solution { public int lastStoneWeight(int[] stones) { int n = stones.length; PriorityQueue<Integer> maxHeap = new PriorityQueue<>(n, (a, b) -> b - a); for (int stone : stones) { maxHeap.add(stone); } while (maxHeap.size() >= 2) { Integer head1 = maxHeap.poll(); Integer head2 = maxHeap.poll(); if (head1.equals(head2)) { continue; } maxHeap.offer(head1 - head2); } if (maxHeap.isEmpty()) { return 0; } return maxHeap.poll(); }} 例二 - 313. 超级丑数题目描述123456789101112131415编写一段程序来查找第 n 个超级丑数。超级丑数是指其所有质因数都是长度为 k 的质数列表 primes 中的正整数。示例:输入: n = 12, primes = [2,7,13,19]输出: 32解释: 给定长度为 4 的质数列表 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。说明:1 是任何给定 primes 的超级丑数。 给定 primes 中的数字以升序排列。0 < k ≤ 100, 0 < n ≤ 10^6, 0 < primes[i] < 1000 。第 n 个超级丑数确保在 32 位有符整数范围内。 思路这道题看似和动态求极值没关系。其实不然,让我们来分析一下这个题目。 我们可以实现生成超级多的丑数,比如先从小到大生成 N 个丑数,然后直接取第 N 个么? 拿这道题来说, 题目有一个数据范围限制 0 < n ≤ 10^6,那我们是不是预先生成一个大小为 $10^6$ 的超级丑数数组,这样我们就可通过 $O(1)$ 的时间获取到第 N 个超级丑数了。 首先第一个问题就是时间和空间浪费。我们其实没有必要每次都计算所有的超级丑数,这样的预处理空间和时间都很差。 第二个问题是,我们如何生成 $10^6$ 以为的超级丑数呢? 通过丑数的定义,我们能知道超级丑数一定可以写出如下形式。 123if primes = [a,b,c,....]then f(ugly) = a * x1 * b * x2 * c * x3 ...其中 x1,x2,x3 均为正整数。 不妨将问题先做一下简化处理。考虑题目给的例子:[2,7,13,19]。 我们可以使用四个指针来处理。直接看下代码吧: 123456789101112131415public class Solution { public int solve(int n) { int ans[]=new int[n+5]; ans[0]=1; int p1=0,p2=0,p3=0,p4=0; for(int i=1;i<n;i++){ ans[i]=Math.min(ans[p1]*2,Math.min(ans[p2]*7,Math.min(ans[p3]*13,ans[p4]*19))); if(ans[i]==ans[p1]*2) p1++; if(ans[i]==ans[p2]*7) p2++; if(ans[i]==ans[p3]*13) p3++; if(ans[i]==ans[p3]*19) p4++; } return ans[n-1]; }} 这个技巧我自己称之为多路归并(实现想不到什么好的名字),我也会在后面的三个技巧也会对此方法使用堆来优化。 由于这里的指针是动态的,指针的数量其实和题目给的 primes 数组长度一致。因此实际上,我们可以使用记忆化递归的形式来完成,递归体和递归栈分别维护一个迭代变量即可。而这道题其实可以看出是一个状态机,因此使用动态规划来解决是符合直觉的。而这里,介绍一种堆的解法,相比于动态规划,个人认为更简单和符合直觉。 关于状态机,我这里有一篇文章原来状态机也可以用来刷 LeetCode?,大家可以参考一下哦。 实际上,我们可以动态维护一个当前最小的超级丑数。找到第一个, 我们将其移除,再找下一个当前最小的超级丑数(也就是全局第二小的超级丑数)。这样经过 n 轮,我们就得到了第 n 小的超级丑数。这种动态维护极值的场景正是堆的用武之地。 有没有觉得和上面石头的题目很像? 以题目给的例子 [2,7,13,19] 来说。 将 [2,7,13,19] 依次入堆。 出堆一个数字,也就是 2。这时取到了第一个超级丑数。 接着将 2 和 [2,7,13,19] 的乘积,也就是 [4,14,26,38] 依次入堆。 如此反复直到取到第 n 个超级丑数。 上面的正确性是毋庸置疑的,由于每次堆都可以取到最小的,每次我们也会将最小的从堆中移除。因此取 n 次自然就是第 n 大的超级丑数了。 堆的解法没有太大难度,唯一需要注意的是去重。比如 2 * 13 = 26,而 13 * 2 也是 26。我们不能将 26 入两次堆。解决的方法也很简单: 要么使用哈希表记录全部已经取出的数,对于已经取出的数字不再取即可。 另一种方法是记录上一次取出的数,由于取出的数字是按照数字大小不严格递增的,这样只需要拿上次取出的数和本次取出的数比较一下就知道了。 用哪种方法不用多说了吧? 代码Java Code: 12345678910111213141516171819class Solution { public int nthSuperUglyNumber(int n, int[] primes) { PriorityQueue<Long> queue=new PriorityQueue<>(); int count = 0; long ans = 1; queue.add(ans); while (count < n) { ans=queue.poll(); while (!queue.isEmpty() && ans == queue.peek()) { queue.poll(); } count++; for (int i = 0; i < primes.length ; i++) { queue.offer(ans * primes[i]); } } return (int)ans; }} ans 初始化为 1 的作用相当于虚拟头,仅仅起到了简化操作的作用 小结堆的中心就一个,那就是动态求极值。 而求极值无非就是最大值或者最小值,这不难看出。如果求最大值,我们可以使用大顶堆,如果求最小值,可以用最小堆。 而实际上,如果没有动态两个字,很多情况下没有必要使用堆。比如可以直接一次遍历找出最大的即可。而动态这个点不容易看出来,这正是题目的难点。这需要你先对问题进行分析, 分析出这道题其实就是动态求极值,那么使用堆来优化就应该被想到。类似的例子有很多,我也会在后面的小节给大家做更多的讲解。 两种实现上面简单提到了堆的几种实现。这里介绍两种常见的实现,一种是基于链表的实现- 跳表,另一种是基于数组的实现 - 二叉堆。 使用跳表的实现,如果你的算法没有经过精雕细琢,性能会比较不稳定,且在数据量大的情况下内存占用会明显增加。 因此我们仅详细讲述二叉堆的实现,而对于跳表的实现,仅讲述它的基本原理,对于代码实现等更详细的内容由于比较偏就不在这里讲了。 跳表跳表也是一种数据结构,因此 ta 其实也是服务于某种算法的。 跳表虽然在面试中出现的频率不大,但是在工业中,跳表会经常被用到。力扣中关于跳表的题目只有一个。但是跳表的设计思路值得我们去学习和思考。 其中有很多算法和数据结构技巧值得我们学习。比如空间换时间的思想,比如效率的取舍问题等。 上面提到了应付插队问题是设计堆应该考虑的首要问题。堆的跳表实现是如何解决这个问题的呢? 我们知道,不借助额外空间的情况下,在链表中查找一个值,需要按照顺序一个个查找,时间复杂度为 $O(N)$,其中 N 为链表长度。 (单链表) 当链表长度很大的时候, 这种时间是很难接受的。 一种常见的的优化方式是建立哈希表,将所有节点都放到哈希表中,以空间换时间的方式减少时间复杂度,这种做法时间复杂度为 $O(1)$,但是空间复杂度为 $O(N)$。 (单链表 + 哈希表) 为了防止链表中出现重复节点带来的问题,我们需要序列化节点,再建立哈希表,这种空间占用会更高,虽然只是系数级别的增加,但是这种开销也是不小的 。更重要的是,哈希表不能解决查找极值的问题,其仅适合根据 key 来获取内容。 为了解决上面的问题,跳表应运而生。 如下图所示,我们从链表中每两个元素抽出来,加一级索引,一级索引指向了原始链表,即:通过一级索引 7 的 down 指针可以找到原始链表的 7 。那怎么查找 10 呢? 注意这个算法要求链表是有序的。 (建立一级索引) 我们可以: 通过现在一级跳表中搜索到 7,发现下一个 18 大于 10 ,也就是说我们要找的 10 在这两者之间。 通过 down 指针回到原始链表,通过原始链表的 next 指针我们找到了 10。 这个例子看不出性能提升。但是如果元素继续增大, 继续增加索引的层数,建立二级,三级。。。索引,使得链表能够实现二分查找,从而获得更好的效率。但是相应地,我们需要付出额外空间的代价。 (增加索引层数) 理解了上面的点,你可以形象地将跳表想象为玩游戏的存档。 一个游戏有 10 关。如果我想要玩第 5 关的某一个地方,那么我可以直接从第五关开始,这样要比从第一关开始快。我们甚至可以在每一关同时设置很多的存档。这样我如果想玩第 5 关的某一个地方,也可以不用从第 5 关的开头开始,而是直接选择离你想玩的地方更近的存档,这就相当于跳表的二级索引。 跳表的时间复杂度和空间复杂度不是很好分析。由于时间复杂度 = 索引的高度 * 平均每层索引遍历元素的个数,而高度大概为 $logn$,并且每层遍历的元素是常数,因此时间复杂度为 $logn$,和二分查找的空间复杂度是一样的。 空间复杂度就等同于索引节点的个数,以每两个节点建立一个索引为例,大概是 n/2 + n/4 + n/8 + … + 8 + 4 + 2 ,因此空间复杂度是 $O(n)$。当然你如果每三个建立一个索引节点的话,空间会更省,但是复杂度不变。 理解了上面的内容,使用跳表实现堆就不难了。 入堆操作,只需要根据索引插到链表中,并更新索引(可选)。 出堆操作,只需要删除头部(或者尾部),并更新索引(可选)。 大家如果想检测自己的实现是否有问题,可以去力扣的1206. 设计跳表 检测。 接下来,我们看下一种更加常见的实现 - 二叉堆。 二叉堆二叉堆的实现,我们仅讲解最核心的两个操作: heappop(出堆) 和 heappush(入堆)。对于其他操作不再讲解,不过我相信你会了这两个核心操作,其他的应该不是难事。 实现之后的使用效果大概是这样的: 123456789h = min_heap()h.build_heap([5, 6, 2, 3])h.heappush(1)h.heappop() # 1h.heappop() # 2h.heappush(1)h.heappop() # 1h.heappop() # 3 基本原理本质上来说,二叉堆就是一颗特殊的完全二叉树。它的特殊性只体现在一点,那就是父节点的权值不大于儿子的权值(小顶堆)。 (一个小顶堆) 上面这句话需要大家记住,一切的一切都源于上面这句话。 由于父节点的权值不大于儿子的权值(小顶堆),那么很自然能推导出树的根节点就是最小值。这就起到了堆的取极值的作用了。 那动态性呢?二叉堆是怎么做到的呢? 出堆假如,我将树的根节点出堆,那么根节点不就空缺了么?我应该将第二小的顶替上去。怎么顶替上去呢?一切的一切还是那句话父节点的权值不大于儿子的权值(小顶堆)。 如果仅仅是删除,那么一个堆就会变成两个堆了,问题变复杂了。 (上图出堆之后会生成两个新的堆) 一个常见的操作是,把根结点和最后一个结点交换。但是新的根结点可能不满足 父节点的权值不大于儿子的权值(小顶堆)。 如下图,我们将根节点的 2 和尾部的数字进行交换后,这个时候是不满足堆性质的。 这个时候,其实只需要将新的根节点下沉到正确位置即可。这里的正确位置,指的还是那句话父节点的权值不大于儿子的权值(小顶堆)。如果不满足这一点,我们就继续下沉,直到满足。 我们知道根节点往下下沉的过程,其实有两个方向可供选择,是下沉到左子节点?还是下沉到右子节点?以小顶堆来说,答案应该是下沉到较小的子节点处,否则会错失正确答案。以上面的堆为例,如果下沉到右子节点 4,那么就无法得到正确的堆顶 3。因此我们需要下沉到左子节点。 下沉到如图位置,还是不满足 父节点的权值不大于儿子的权值(小顶堆),于是我们继续执行同样的操作。 有的同学可能有疑问。弹出根节点前堆满足堆的性质,但是弹出之后经过你上面讲的下沉操作,一定还满足么? 答案是肯定的。这个也不难理解。由于最后的叶子节点被提到了根节点,它其实最终在哪是不确定的,但是经过上面的操作,我们可以看出: 其下沉路径上的节点一定都满足堆的性质。 不在下沉路径上的节点都保持了堆之前的相对关系,因此也满足堆的性质。 因此弹出根节点后,经过上面的下沉操作一定仍然满足堆的性质。 时间复杂度方面可以证明,下沉和树的高度成正相关,因此时间复杂度为 $O(h)$,其中 h 为树高。而由于二叉堆是一颗完全二叉树,因此树高大约是 $logN$,其中 N 为树中的节点个数。 入堆入堆和出堆类似。我们可以直接往树的最后插入一个节点。和上面类似,这样的操作同样可能会破坏堆的性质。 之所以这么做的其中一个原因是时间复杂度更低,因为我们是用数组进行模拟的,而在数组尾部添加元素的时间复杂度为 $O(1)$。 这次我们发现,不满足堆的节点目前是刚刚被插入节点的尾部节点,因此不能进行下沉操作了。这一次我们需要执行上浮操作。 叶子节点是只能上浮的(根节点只能下沉,其他节点既可以下沉,又可以上浮) 和上面基本类似,如果不满足堆的性质,我们将其和父节点交换(上浮),继续这个过程,直到满足堆的性质。 (第一次上浮,仍然不满足堆特性,继续上浮) (满足了堆特性,上浮过程完毕) 经过这样的操作,其还是一个满足堆性质的堆。证明过程和上面类似,不再赘述。 需要注意的是,由于上浮只需要拿当前节点和父节点进行比对就可以了, 由于省去了判断左右子节点哪个更小的过程,因此更加简单。 实现对于完全二叉树来说使用数组实现非常方便。因为: 如果节点在数组中的下标为 i,那么其左子节点下标为 $2 \\times i$,右节点为 $2 \\times i$+1。 如果节点在数组中的下标为 i,那么父节点下标为 i//2(地板除)。 当然这要求你的数组从 1 开始存储数据。如果不是,上面的公式其实微调一下也可以达到同样的效果。不过这是一种业界习惯,我们还是和业界保持一致比较好。从 1 开始存储的另外一个好处是,我们可以将索引为 0 的位置空出来存储诸如堆大小的信息,这是一些大学教材里的做法,大家作为了解即可。 如图所示是一个完全二叉树和树的数组表示法。 (注意数组索引的对应关系) 形象点来看,我们可以可以画出如下的对应关系图: 这样一来,是不是和上面的树差不多一致了?有没有容易理解一点呢? 上面已经讲了上浮和下沉的过程。刚才也讲了父子节点坐标的关系。那么代码就呼之欲出了。我们来下最核心的上浮和下沉的代码实现吧。 伪代码: 123456789101112131415161718// x 是要上浮的元素,从树的底部开始上浮private void shift_up(int x) { while (x > 1 && h[x] > h[x / 2]) { // swqp 就是交换数组两个位置的值 swap(h[x], h[x / 2]); x /= 2; }}// x 是要下沉的元素,从树的顶部开始下沉private void shift_down(int x) { while (x * 2 <= n) { // minChild 是获取更小的子节点的索引并返回 mc = minChild(x); if (h[mc] <= h[x]) break; swap(h[x], h[mc]); x = mc; }} 这里 Java 语言为例,讲述一下代码的编写。其他语言的二叉堆实现可以去我的刷题插件 leetcode-cheatsheet 中获取。插件的获取方式在公众号力扣加加里,回复插件即可。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110import java.util.Arrays;import java.util.Comparator;/** * 用完全二叉树来构建 堆 * 前置条件 起点为 1 * 那么 子节点为 i <<1 和 i<<1 + 1 * 核心方法为 * shiftdown 交换下沉 * shiftup 交换上浮 * <p> * build 构建堆 */public class Heap { int size = 0; int queue[]; public Heap(int initialCapacity) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new int[initialCapacity]; } public Heap(int[] arr) { size = arr.length; queue = new int[arr.length + 1]; int i = 1; for (int val : arr) { queue[i++] = val; } } public void shiftDown(int i) { int temp = queue[i]; while ((i << 1) <= size) { int child = i << 1; // child!=size 判断当前元素是否包含右节点 if (child != size && queue[child + 1] < queue[child]) { child++; } if (temp > queue[child]) { queue[i] = queue[child]; i = child; } else { break; } } queue[i] = temp; } public void shiftUp(int i) { int temp = queue[i]; while ((i >> 1) > 0) { if (temp < queue[i >> 1]) { queue[i] = queue[i >> 1]; i >>= 1; } else { break; } } queue[i] = temp; } public int peek() { int res = queue[1]; return res; } public int pop() { int res = queue[1]; queue[1] = queue[size--]; shiftDown(1); return res; } public void push(int val) { if (size == queue.length - 1) { queue = Arrays.copyOf(queue, size << 1+1); } queue[++size] = val; shiftUp(size); } public void buildHeap() { for (int i = size >> 1; i > 0; i--) { shiftDown(i); } } public static void main(String[] args) { int arr[] = new int[]{2,7,4,1,8,1}; Heap heap = new Heap(arr); heap.buildHeap(); System.out.println(heap.peek()); heap.push(5); while (heap.size > 0) { int num = heap.pop(); System.out.printf(num + \"\"); } }} 小结堆的实现有很多。比如基于链表的跳表,基于数组的二叉堆和基于红黑树的实现等。这里我们详细地讲述了二叉堆的实现,不仅是其实现简单,而且其在很多情况下表现都不错,推荐大家重点掌握二叉堆实现。 对于二叉堆的实现,核心点就一点,那就是始终维护堆的性质不变,具体是什么性质呢?那就是 父节点的权值不大于儿子的权值(小顶堆)。为了达到这个目的,我们需要在入堆和出堆的时候,使用上浮和下沉操作,并恰当地完成元素交换。具体来说就是上浮过程和比它大的父节点进行交换,下沉过程和两个子节点中较小的进行交换,当然前提是它有子节点且子节点比它小。 关于堆化我们并没有做详细分析。不过如果你理解了本文的入堆操作,这其实很容易。因此堆化本身就是一个不断入堆的过程,只不过将时间上的离散的操作变成了一次性操作而已。 预告本文预计分两个部分发布。这是第一部分,后面的内容更加干货,分别是三个技巧和四大应用。 三个技巧 多路归并 固定堆 事后小诸葛 四大应用 topK 带权最短距离 因子分解 堆排序 这两个主题是专门教你怎么解题的。掌握了它,力扣中的大多数堆的题目都不在话下(当然我指的仅仅是题目中涉及到堆的部分)。 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。目前已经 37K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/categories/堆/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/tags/堆/"}]},{"title":"来和大家聊聊我是如何刷题的(第三弹)","slug":"shuati-silu3","date":"2020-12-20T16:00:00.000Z","updated":"2023-01-05T12:24:49.999Z","comments":true,"path":"2020/12/21/shuati-silu3/","link":"","permalink":"https://lucifer.ren/blog/2020/12/21/shuati-silu3/","excerpt":"前两篇的地址在这里,没有看过的同学建议先看下。 来和大家聊聊我是如何刷题的(第一弹) 来和大家聊聊我是如何刷题的(第二弹) 本章或许是这个系列的最终章。这次给大家聊一点硬核的,聊一些几乎所有算法题都能用得上的超实用思想。 上一节给大家抛出了两个问题,分别是: 如何锁定使用哪种算法?比如我看到了这道题,我怎么知道该用什么解法呢?二分?动态规划? 一看就会,一写就废, 如何克服? 今天,我就来圆一个当初吹下的牛逼。话不多说,直接上干货。如果你觉得有用,请三连支持我一下,让我能够坚持下去,给大家带来更多的干货。","text":"前两篇的地址在这里,没有看过的同学建议先看下。 来和大家聊聊我是如何刷题的(第一弹) 来和大家聊聊我是如何刷题的(第二弹) 本章或许是这个系列的最终章。这次给大家聊一点硬核的,聊一些几乎所有算法题都能用得上的超实用思想。 上一节给大家抛出了两个问题,分别是: 如何锁定使用哪种算法?比如我看到了这道题,我怎么知道该用什么解法呢?二分?动态规划? 一看就会,一写就废, 如何克服? 今天,我就来圆一个当初吹下的牛逼。话不多说,直接上干货。如果你觉得有用,请三连支持我一下,让我能够坚持下去,给大家带来更多的干货。 如何锁定使用哪种算法?为什么很多人刚看了一眼题目就知道怎么解? 一种可能是 ta 之前做过同样或者类似的题目,形成了内在的记忆,直接提取了之前的记忆。 另一种可能是题目给出了明确的提示信息,他们根据这些信息”蒙“的,这种蒙就是题感。 最后一种是刚开始也没思路,尝试暴力解,发现某些步骤可以优化,慢慢剥茧抽丝,推导出最终答案。 接下来,我们来聊下第二种和第三种。至于第一种则不是一篇文章能解决的,这需要大家多做题,并且做题的时候要多总结多交流。 关键字关键字可以对解题起到提示作用。这很好理解,假设题目没有限制信息等关键字,那就是耍流氓,毫无算法可言了。 比如”在一个数组中找 target“,这道题就很无聊,正常不会有这种算法题。 可能的出题形式是加上有序两个字,变成有序数组。那有序就是关键字了。 其他的例子有很多,接下来我们来看下常见的关键字以及对应的可能解法有哪些。 如果题目是求极值,计数,很有可能是动态规划,堆等。 如果题目是有序的,则可能是双指针。比如二分法。 如果题目要求连续,则可能是滑动窗口。 如果题目要求所有可能,需要路径信息,则可能是回溯。 如上的这些只是看到关键词你应该第一时间想到的可能解法,究竟正确与否,以及复杂度是否达标需要在脑子里二次加工。 关于复杂度是否达标这一点,后面给大家介绍。 限制条件很多题目都会给一些数据范围的提示,大家一定要注意看。 比如 1681. 最小不兼容性,题目描述就不看了,我们不打算在这里讲具体怎么解。这道题的函数签名如下: 1def minimumIncompatibility(self, nums: List[int], k: int) -> int: 这道题的提示是这样的: 1231 <= k <= nums.length <= 16nums.length 能被 k 整除。1 <= nums[i] <= nums.length 看到了这个你有什么想法么? 注意到 nums 的长度和值都很小,这道题很可能是暴力回溯 + 状态压缩。关于回溯和状态压缩技巧可以翻翻我的历史文章。 这里再给大家一个超实用小技巧。 如果 n 是 10 左右,那么算法通常是 n! 的时间复杂度。 如果 n 是 20 左右,那么算法通常是 2^n 的时间复杂度 因此 1681. 最小不兼容性 这道题的复杂度很可能就是指数级别。 那为什么 10 左右就是 n!,20 是 2^n? 这里给大家介绍一个你可能不知道的技巧。请大家记住一个数字 1000 万。 上面之所以是 10 左右, 20 左右就是因为你把 n 带进去差不多都是 1000 万。 再比如一道题是 n 是 $10^7$,那很可能是$O(N)$复杂度,因为 $10 ^7$ 就是 1000 万。 再比如,我之前写的一篇文章《穿上衣服我就不认识你了?来聊聊最长上升子序列》,上面所有的题时间复杂度都是 $N^2$,基本都可以通过所有的测试用例。为什么?因为题目数据范围差不多是 2500,那 2500 的平方是多少?是 600 多万,因此数据范围是 3000 以内, 平方差不多都可解,当然我说的只是大多数情况,并且需要注意越接近临界值越可能超时。 再比如1631. 最小体力消耗路径。题目描述: 12345你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。请你返回从左上角走到右下角的最小 体力消耗值 。。 示例 1: 1234输入:heights = [[1,2,2],[3,8,2],[5,3,5]]输出:2解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。 这道题的函数签名如下: 12class Solution: def minimumEffortPath(self, heights: List[List[int]]) -> int: 这道题的提示是这样的: 1234rows == heights.lengthcolumns == heights[i].length1 <= rows, columns <= 1001 <= heights[i][j] <= 10^6 首先,我们至少需要从左上走到右下,那么时间复杂度就已经是 $O(rows * columns)$ 了。题目说了这两个数字都不大于 100,因此最大就是 $10^4$。而对于路线上的高度差绝对值的数据范围也不超过 $10^6$。 暴力法就是一个个试, 复杂度是二者直接相乘,也就是 $10^10$,大于前面给大家讲的 $10^7$,因此这个复杂度通常是不能 AC 的。 而上面说了 $O(rows * columns)$ 是不可能省的,因为你至少要走一次。但如果 $10^6$ 不是线性去试,而是指数的话呢?而指数复杂度首先想到二分。 此题的伪代码: 1234567891011121314class Solution: def minimumEffortPath(self, heights: List[List[int]]) -> int: def test(mid, x, y): # dosomething l, r = 0, 10**6 # 最左满足条件的值的二分模板。大家可以去 leetcode-cheatsheet 插件获取更多算法模板 while l <= r: mid = (l + r) // 2 # 测试有没有一条路径从(0,0)出发到达(rows-1,cols-1),且路径上的高度绝对值差不大于 mid if test(mid, 0, 0): r = mid - 1 else: l = mid + 1 return l 你说 1000 万这个数字重要不重要?1000 万不仅是我的人生目标,更是做题时刻铭记的一个数字!^_^ 暴力优化最后给大家介绍的”识别题目可能的解法“的技巧是暴力优化。 一句话概括就是先暴力解,然后思考性能瓶颈,再尝试使用数据结构和算法对瓶颈进行优化。 比如 316. 去除重复字母,我就是先暴力求出来。发现每次都直接判断是否在是否在栈上需要 $O(N)$ 的时间,太慢了。由于我就用了哈希表进行优化。而使用哈希表这点,绝对不是我一开始就想到的,而是先暴力求解,求解的过程发现算法的性能瓶颈才意识到该用哈希表的。关于这道题的详细的解法就不再这里讲了,大家点进去看我的题解就行。或者直接去力扣搜题,排名第一的非官方题解应该就是我。 总结一下就是,大家一定不要小看暴力法。暴力法解出来剪剪枝说不定就过了。如果不过,思考下瓶颈在哪,用合适的数据结构和算法优化一下说不定也就过了。这可不是随便说说。比如下面要讲的硬币找零问题,就是暴力解发现瓶颈,加个记忆化去除重复子问题就是动态规划了 一看就会,一写就废, 如何克服?针对这个问题,之前我给大家的建议是多复习, 多动手写。 后来我和几个朋友聊了一下,发现自己有点幸存者偏差。我发现很多人在没有算法思维的情况下就开始学习算法了,这很不可取。 不过算法思维这东西你让我在这一篇文章给你整的明明白白的,这也不现实。今天我给大家分享一个我认为最最重要的一个算法思想 - 分治。 分治思维“一看就会,一写就废, 如何克服?”,有一个可能是你没有分治思维。 我们的大脑天生适合处理一些简单的东西,而不适合处理看起来就很复杂的东西。因此面对一个很复杂的东西,第一件事情应该是思考是否可以将其分解 ,然后逐个击破。 举个例子给大家,如下是一道力扣的 hard 题 《2 出现的次数》,题目描述如下: 12345678910编写一个方法,计算从 0 到 n (含 n) 中数字 2 出现的次数。示例:输入: 25输出: 9解释: (2, 12, 20, 21, 22, 23, 24, 25)(注意 22 应该算作两次)提示:n <= 10^9 很多人一看到题就蒙了,这要多少种情况啊? 总不可能一个数字一个数字试过去吧? 其实看一眼数据范围中 n 上限是 10^9,大于 1000 万,就知道不能这样暴力。 于是悄悄打开题解,不仅感叹“原来是这样啊!”,“这怎么想到的?这什么脑子啊!” 来让我告诉你,你缺啥。 你缺的不是一个好使的脑子,而是一个懂得将复杂问题变成若干个简单问题的意识和能力。 以这道题来说, 我可以将其分解为几个子问题。 从 0 到 n (含 n) 中 个位 数字 2 出现的次数 从 0 到 n (含 n) 中 十位 数字 2 出现的次数 。。。 最终的答案就是以上几个子问题的和。 经过这样的思路,大家一下子就能打开思路。剩下的任务就简单了。因为每次固定一位之后,就将数字分为了左右两部分,那么该位是 2 的次数就是左右所有可能的笛卡尔积,即 a * b。 比如 n 是 135。 百位上不可能是 2,因为 2xx 一定超过 135 了。 那十位有多少个 2 呢?按照上面的思路: 左边就是百位,百位可能是 0 或者 1,共 2 种可能。 右边就是个位,个位可能是 [0 - 9] 共 10 种可能。 那么十位是 2 的次数就是 2 * 10 = 20。 那个位有多少个 2 呢?按照上面的思路: 左边就是十位和百位,其可能是 [0-13],共 14 种可能。 右边啥都没有,1 种可能。 那么个位是 2 的次数就是 14 种。 因此不超过 135 的数字中 2 的出现次数就是 20 + 14 = 34 种。 当然,这里面还有一些细节,比如如果某一位比 2 小或者正好是 2 怎么办?我就不在这里讲了。这里直接贴下代码,大家自己继续完成好了。 123456789101112class Solution: def numberOf2sInRange(self, n: int) -> int: ans = 0 m = 1 while m <= n: cur = n // m % 10 if cur > 2: ans += (n // m // 10 + 1) * m else: if cur == 2: ans += n % m + 1 ans += (n // m // 10) * m m *= 10 return ans 把 2 换成其他数字 x,那就可以计算不超过 n 的 x 的出现次数。 举这个例子就想告诉大家为啥一些题目你压根就没有思路的原因: 要么就是这种题没见过,那没办法,多做题呗。 要么就是你算法思维还不够。比如我上面讲的分治的算法思维。 一看就会又说明这种题你是回答过的,因此一看就会,一写就废,一般都是没有养成良好的算法思维,而分治就是一种非常重要的算法思维。当算法思维有了,剩下的细节就慢慢练习就好了,这没有捷径。但是算法思维是有捷径的,大家在刷题之前要特别注重算法思维的学习。 我再举几个例子给大家,帮助大家加深理解。 三个题目带你理解分治思想 在一个数组 nums 中找值为 target 的元素,并返回数组下标,题目保证 nums 中有且仅有一个数等于 target。 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。(322. 零钱兑换) n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。(51. N 皇后) 这几道题覆盖了简单中等和困难三种难度。接下来,我们来看下这几个题。 第一题对于第一题, 答案无非就是 [0, n - 1]。因此我们可以将问题分解为以下几个子问题: 是 0 么?no 是 1 么?no 是 2 么?no 。。。 最终的答案就是子问题中回答为 “yes” 的索引。严格意义上来说,这里只有分,没有治,而且这个分和前面的分有微妙的差异。前面的分完之后后面还要用,这个分是直接给扔掉了。类似的有二分法,二分法就是一种只有分没有治的“分治法”。 第二题coins 是个变量, amount 也是变量,它们关系感觉好多的样子?我该怎么理清呢? 我们从特殊入手,比如 coins = [1, 2, 5], amount = 11。为了方便描述,原问题我用 f([1,2,5], 11) 表示 coins 为 [1,2,5],amount 为 11 的最少需要多少硬币凑齐。 我也不知道最终的最少硬币方案是怎么选的。那我就所有情况都走一遍呗,比较一下哪种方案用硬币最少就用哪个不就行了么? 最终的算法还真就是基于这个朴实的想法来的。 选第一枚硬币的时候,一共只有三种情况:选择 1,选择 2,选择 5。 如果我们先选了 1,那么再凑出 10 就行了。那怎么凑出 10 呢?不就是 f([1, 2, 5], 10) 么? 如果我们先选了 2,那么再凑出 9 就行了。那怎么凑出 9 呢?不就是 f([1, 2, 5], 9) 么? 如果我们先选了 5,那么再凑出 6 就行了。那怎么凑出 6 呢?不就是 f([1, 2, 5], 6) 么? 上面是选取一个硬币的情况,由于没有凑到 amount,我们继续重复,直到凑到 amount。 于是你可以画出类似如下的逻辑树结构,由于节点太多我没有画全。 有没有发现你的大脑直接处理大问题没有思路,但将其分解为小问题就简单了许多?分完了,我们还要治。 这就好像你是主管,向下面布置了作业,布置完了你还要收作业将他们汇总起来搞个 ppt 啥的。 不过这也不难。由于问题是最少硬币,那么治就取最少呗。 11 + min(f([1,2,5], 10),f([1,2,5], 9),f([1,2,5], 6)) 总结一下: 这道题的分我们可以从几个特例入手就可以打开思维。上面的分的手段用伪代码描述就是: 123for (int coin : coins) { f(coins, amount - coin)} 分完了就是处理边界和治了。 完整的分治代码就是: 12345678910111213141516public int f(coins, amount) { if (amount < 0) { // 非法解,用正无穷表示 return Float.POSITIVE_INFINITY } // 叶子节点 if (amount == 0) { // 找到一个解,是不是最小的”治“阶段处理。 return 0 } int ans = Float.POSITIVE_INFINITY for (int coin : coins) { ans = Math.min(ans, 1 + f(coins, amount - coins)) } return ans} 为了突出我的算法主框架,略去了一些细节。 比如原题在无解的时候需要返回 - 1,而我返回的是正无穷。 如果之前做过这道题的朋友应该知道这是一个典型的背包问题。如果现在让我做,我可能也直接自底向上 dp table 解决了(不过 dp table 和记忆化递归没有本质的思维差别)。但是算法是如何想出来的这一点,是如何一步一步优化的,大家一定钻到底,这样刷题效率才高。 第三题不懂题目意思的可以去看下力扣原题 51. N 皇后。这道题就是典型的回溯题目,什么是回溯?一言以蔽之,那就是一个一个试,不行了就返回上一步继续试。 这么多格子我该放哪呢?每个格子还有制约关系!好乱,没有思路。 别急,继续使用分治的思维。这道题是让我们将 N 个皇后放到 N X N 的棋盘上。那不就是: 第一行的皇后应该放到第几列? 第二行的皇后应该放到第几列? 第三行的皇后应该放到第几列? 。。。 改成”第 x 列的皇后应该放到第几行?”这种子问题划分模式也是可以的。 伪代码: 1234567public int placeRow(i) { // 决定应该放到第几列}for (int i=0;i<n;i++) { placeRow(i)} 如果上面的子问题都解决了,那整个问题不就解决了么? 但是上面的子问题,还是无法直接解决。比如“第一行的皇后应该放到第几列?”我也不知道啊。没关系,我们继续对“第一行的皇后应该放到第几列?” 这个问题进行分解。 第一行的皇后放到第 1 列么? 第一行的皇后放到第 2 列么? 第一行的皇后放到第 3 列么? 。。。 继续完善上面的 placeRow 代码即可。这里给出伪代码: 1234567891011public boolean canPlaceQueue(i, j) { // 根据目前的棋局(放了是否能不相互攻击),分析 i 和 j 这个位置能否放女王。}public int placeRow(i) { for (int j=0;j<n;j++) { if (canPlaceQueue(i, j)) { // 将女王放到 (i,j),更新当前棋局 placeQueue(i, j) } }} 现在的问题就只剩下实现canPlaceQueue(i, j) 和 placeQueue(i, j)了,这两个函数根据题目要求模拟实现即可。 需要注意的是我们做了一个placeQueue(i, j) 的操作,这可能是一个 mutable 的操作。因此如果一条路行不通需要回溯,那么 mutable 的数据需要撤销修改。当然如果你的数据是 immutable 就无所谓了。不过 immutable 则有可能内存移除或者超时的风险。 由于这里只是讲思维的,不是讲题目本身的,因此还是点到为止,后面的算法细节我就不讲了,希望读者能自己将代码完善一下。 更多类似的例子实在太多了,根本举不过来,我随口给大家说几个。 如果让你求一个数组的连续子数组总个数,你会如何求?其中连续指的是数组的索引连续。 比如 [1,3,4],其连续子数组有:[1], [3], [4], [1,3], [3,4] , [1,3,4],你需要返回 6。分治就好了,连续子数组个数等于:以索引为 0 结尾的子数组个数 + 以索引为 1 结尾的子数组个数 + … + 以索引为 n - 1 结尾的子数组个数 70. 爬楼梯 让你求爬到最后一级台阶有多少种方法。这太多了,我数不过来。但是我可以将其分解成两个子问题。如果我用 f(n) 表示爬到第 n 级的方法数,那么 f(n) = f(n - 1) + f(n - 2)。但是 n - 1 我也不会啊,没关系,我们继续分解。这和上面的硬币问题有多大差别么? 对于这道题,分就是拆成两个子问题,治就是求和。 这就是最简单的无选择的递推动态规划 746. 使用最小花费爬楼梯 换了个皮又来了? 220 场周赛 - 跳跃游戏 VI 这不还是上面爬楼梯换皮么?这次变成了一次能爬 k 级台阶罢了。 这道题数组长度是 $10^5$,如果不做优化复杂度会是 $N^2$,算起来就是 $10^10$ 过不了,大于上面给大家讲的 1000 万。如何优化有点跑题了,就不在这里讲了。 62. 不同路径 穿个二维的衣服就看不出你是爬楼梯了? 相关换皮题目太多,大家可以去我的插件里看。 总结本次给大家分享了一个很重要的算法思想分治,很多题都可以用到这个思想。能运用分治思想的专题有“动态规划”,”分治“,“回溯” 等,大家在平时做题的时候可以参考我的这种思考方式。 如果你碰到一个复杂的问题,可以尝试以下几个方法。 不妨先尝试将其拆解,看能否将其拆解成几个小问题。 在草稿上画画图,从特殊情况入手,看能否发现什么蛛丝马迹 暴力模拟。看能否通过剪枝和添加恰当的数据结构来优化算法,使之通过。 如果你有更好的干货技巧,非常希望你能和我交流,万分期待! 除了算法思想,我还和大家分享两个超实用的技巧,分别是: 看关键字。关键字很多时候起到了提示作用,甭管对不对,咱要想到。想到之后迅速脑子中过一下能不能 AC。 看限制条件。 记住一个数字就行了,1000 万。 最后和大家说了一个小心得 - ”不要小看暴力法“。暴力法不仅能帮助你打开思路,有时候甚至暴力 + 剪枝(或数据结构优化)就过了。大力出奇迹,欧耶!(^o^)/ 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/categories/刷题方法/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/tags/刷题方法/"}]},{"title":"力扣 APP 全新改版,史诗级增强!","slug":"leetcode-app-1","date":"2020-12-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.468Z","comments":true,"path":"2020/12/16/leetcode-app-1/","link":"","permalink":"https://lucifer.ren/blog/2020/12/16/leetcode-app-1/","excerpt":"这次的改版真的是判若两人,PC 端的几乎所有功能都可以在新版 APP 中看到,并且体验更好。 不仅之前令我不爽的地方全部不见了,而且多了一些我想都没想到的好用功能。 比如摇一摇功能。 我拿到了体验版第一时间就给大家写了这篇体验报告。下面,西法就带你看看全新版本都有啥。","text":"这次的改版真的是判若两人,PC 端的几乎所有功能都可以在新版 APP 中看到,并且体验更好。 不仅之前令我不爽的地方全部不见了,而且多了一些我想都没想到的好用功能。 比如摇一摇功能。 我拿到了体验版第一时间就给大家写了这篇体验报告。下面,西法就带你看看全新版本都有啥。 每日一题 & 推荐题库这可能是大家最关注的一个功能了。我想看一个每日一题还必须去 PC 端才能看到,这就不方便了。 新榜的 APP 直接就可以在题库标签的顶部看到。 除了每日一题, 新榜的 APP 还把其他题库一起装进来了。比如大家熟悉的《剑指 Offer》 和 《程序员面试金典》。 代码展示之前 APP 代码真的不是给人看的,还会换行,所以我一般都只能是复制出来到别的地方看。 后面力扣对代码进行了优化, 使得代码不换行,而是通过左右滑动的方式查看,相比之前体验好了很多。而现在新版 APP 对代码的展示又进行了优化,真的是丝滑般柔顺了,大家看下对比效果。 (旧版效果) (新版效果) 这对比应该很明显了。手动点赞 o( ̄ ▽  ̄)d 消息功能增强相比之前手机 APP 只能是关注的人参与了社区讨论才会收到通知,写个题解什么的根本就不会接受到通知。 现在大家可以在今天标签页下直接能看到你关注的所有人的动态。 这才是关注应该有的样子嘛。 小提示:力扣的小伙伴的点下我头像的关注按钮,这样就会第一时间收到我的动态啦~ 除此之外,通知功能也对齐 PC 端,对消息内容进行了分组展示。 其他功能摇一摇切换中英文描述,摇一摇随机一题 学习分析 下载现在还在灰度内测阶段,内测的话需要填写一份问卷 https://shimo.im/forms/8CDdY3VcyRRgwhPq/fill 。不想填问卷也没关系,正式版也快和大家见面了,等到正式版发布大家就可以去各大 APP Store 中进行下载了。","categories":[{"name":"软件工具","slug":"软件工具","permalink":"https://lucifer.ren/blog/categories/软件工具/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"《西法的刷题秘籍》电子书开放下载啦~","slug":"leetcode-ebook-1","date":"2020-12-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.819Z","comments":true,"path":"2020/12/13/leetcode-ebook-1/","link":"","permalink":"https://lucifer.ren/blog/2020/12/13/leetcode-ebook-1/","excerpt":"2019-07-10 :纪念项目 Star 突破 1W 的一个短文, 记录了项目的”兴起”之路,大家有兴趣可以看一下,如果对这个项目感兴趣,请点击一下 Star, 项目会持续更新,感谢大家的支持。 2019-10-08: 纪念 LeetCode 项目 Star 突破 2W,并且 Github 搜索“LeetCode”,排名第一。 2020-04-12: 项目突破三万 Star。 2020-04-14: 官网力扣加加上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/","text":"2019-07-10 :纪念项目 Star 突破 1W 的一个短文, 记录了项目的”兴起”之路,大家有兴趣可以看一下,如果对这个项目感兴趣,请点击一下 Star, 项目会持续更新,感谢大家的支持。 2019-10-08: 纪念 LeetCode 项目 Star 突破 2W,并且 Github 搜索“LeetCode”,排名第一。 2020-04-12: 项目突破三万 Star。 2020-04-14: 官网力扣加加上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/ 前言这是我将我的所有公开的算法资料整理的一个电子书,全部题目信息中文化,以前会有一些英文描述,感谢 @CYL 的中文整理。 我写这本电子书花费了大量的时间和精力,除了内容上的创作,还要做一些电子书的排版,以让大家获得更好的阅读体验。光数学公式的展示,我就研究了多个插件的要源码,并魔改了一下才使得导出的电子书支持 latex。 不过有些动图,在做成电子书的时候自然就变没了,如果需要看动图的, 可以去我的公众号《力扣加加》或者我的 leetcode 题解仓库看。 由于是电子书,因此阅读体验可能会更好, 但是相应地就不能获得及时的更新,因此你可以收藏一下我的同步电子书的网站 西法的刷题秘籍 - 在线版。后期可能将每日一题, 91 天学算法其他章节的讲义等也整理进来。 电子书有更新我也会在公众号《力扣加加》进行通知, 感兴趣的同学可以关注一下。 目前导出了四种格式,可惜的是这几种格式都有自己的不足: 在线版。 实时更新,想要及时获取最新信息的可以用在线版。 html。 方便大家在线观看,由于是 html ,实际上大家也可以保存起来离线观看。 pdf。可使用 pdf 阅读器和浏览器(比如谷歌)直接观看,阅读体验一般,生成的目录不能导航。 mobi。 下载一个 Kindle 客户端就可以看,不需要购买 Kindle。 epub。 数学公式和主题都比较不错, 但是代码没有高亮。 大家选择适合自己的格式下载即可。 在线版 html, pdf,mobi 和 epub 格式,关注我的公众号《力扣加加》回复电子书即可。 介绍leetcode 题解,记录自己的 leetcode 解题之路。 本仓库目前分为五个部分: 第一个部分是 leetcode 经典题目的解析,包括思路,关键点和具体的代码实现。 第二部分是对于数据结构与算法的总结 第三部分是 anki 卡片, 将 leetcode 题目按照一定的方式记录在 anki 中,方便大家记忆。 第四部分是每日一题,每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块。 第五部分是计划, 这里会记录将来要加入到以上三个部分内容 只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 非科学人士看过来如果是国内的非科学用户,可以使用 https://lucifer.ren/leetcode ,整站做了静态化,速度贼快!但是阅读体验可能一般,大家也可以访问力扣加加(暂时没有静态化)获得更好的阅读体验。 另外需要科学的,我推荐一个工具, 用户体验真的是好,用起来超简单, 提供一站式工具,包括网络检测工具,浏览器插件等,支持多种客户端(还有我最喜欢的 Switch 加速器),价格也不贵,基础套餐折算到月大约 11.2 块/月。它还支持签到送天数,也就是说你可以每天签到无限续期。地址:https://glados.space/landing/M9OHH-Q88JQ-DX72D-R04RN 怎么刷 LeetCode? 我是如何刷 LeetCode 的 算法小白如何高效、快速刷 leetcode? 刷题插件 刷题效率低?或许你就差这么一个插件 力扣刷题插件 91 天学算法 91 天,遇见不一样的自己 食用指南 我对大部分题目的复杂度都进行了分析,除了个别分析起来复杂的题目,大家一定要对一道题的复杂度了如指掌才可以。 有些题目我是故意不写的, 比如所有的回溯题目我都没写, 不过它们全部都是指数的复杂度 我对题目难度进行了分类的保留,因此你可以根据自己的情况刷。我推荐大家从简单开始,逐步加大难度,直到困难。 这里有一张互联网公司面试中经常考察的问题类型总结的思维导图,我们可以结合图片中的信息分析一下。 (图片来自 leetcode) 其中算法,主要是以下几种: 基础技巧:分治、二分、贪心 排序算法:快速排序、归并排序、计数排序 搜索算法:回溯、递归、深度优先遍历,广度优先遍历,二叉搜索树等 图论:最短路径、最小生成树 动态规划:背包问题、最长子序列 数据结构,主要有如下几种: 数组与链表:单 / 双向链表 栈与队列 哈希表 堆:最大堆 / 最小堆 树与图:最近公共祖先、并查集 字符串:前缀树(字典树) / 后缀树 精彩预告0042.trapping-rain-water: 0547.friend-circles: backtrack problems: 0198.house-robber: 0454.4-sum-ii: anki 卡片Anki 主要分为两个部分:一部分是关键点到题目的映射,另一部分是题目到思路,关键点,代码的映射。 全部卡片都在 anki-card 使用方法: anki - 文件 - 导入 - 下拉格式选择“打包的 anki 集合”,然后选中你下载好的文件,确定即可。 更多关于 anki 使用方法的请查看 anki 官网 目前已更新卡片一览(仅列举正面): 二分法解决问题的关键点是什么,相关问题有哪些? 如何用栈的特点来简化操作, 涉及到的题目有哪些? 双指针问题的思路以及相关题目有哪些? 滑动窗口问题的思路以及相关题目有哪些? 回溯法解题的思路以及相关题目有哪些? 数论解决问题的关键点是什么,相关问题有哪些? 位运算解决问题的关键点是什么,相关问题有哪些? 已加入的题目有:#2 #3 #11","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"电子书","slug":"电子书","permalink":"https://lucifer.ren/blog/tags/电子书/"}]},{"title":"来和大家聊聊我是如何刷题的(第二弹)","slug":"shuati-silu2","date":"2020-12-11T16:00:00.000Z","updated":"2023-01-05T12:24:49.860Z","comments":true,"path":"2020/12/12/shuati-silu2/","link":"","permalink":"https://lucifer.ren/blog/2020/12/12/shuati-silu2/","excerpt":"上一篇的地址在这里,没有看过的同学建议先看第一篇 来和大家聊聊我是如何刷题的(第一弹)。 这次继续给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第二篇,本系列至少会出三篇。这次分享的内容是代码书写技巧以及调试技巧。 本系列旨在分享一些所有题目都适用的技巧以及一些刷题经验,帮助大家高效刷题。如果想重点突破某一类题目,可以关注我的专题系列。 话不多说,直接上干货。","text":"上一篇的地址在这里,没有看过的同学建议先看第一篇 来和大家聊聊我是如何刷题的(第一弹)。 这次继续给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第二篇,本系列至少会出三篇。这次分享的内容是代码书写技巧以及调试技巧。 本系列旨在分享一些所有题目都适用的技巧以及一些刷题经验,帮助大家高效刷题。如果想重点突破某一类题目,可以关注我的专题系列。 话不多说,直接上干货。 先刷什么?刷多少?正式介绍技巧之前先回答一个问题,这也是我被问的比较多的两个问题是:我该先刷什么算法?每一种算法我该刷多少? 现在我们就来看下这个问题。 先贴一个 91 天学算法 中某一小节的讲义中的一部分内容: 递归(10) BFS & DFS(20) 双指针(20) 滑动窗口(6) 哈希表(20) 回溯(5) 动态规划(20) 排序(3) 分治(20) 堆(3) 贪心(5) 设计题(5) 图(5) 位运算(5) 并查集(3) 如果你不知道从何刷起,可以参考我的这个刷题顺序,其中括号是我推荐的最小刷题量,就是说再少不能少于这个数字。如果你想多刷,可以按照我的这个比例去刷。等到你自己有个概念,知道自己哪里薄弱了,再去针对加强即可。 具体题目太多了不列举了,给大家几个题目集合做参考: 🔥 热题 HOT 100 👨‍💻 精选 TOP 面试题 企业题库。 比如 🐧 腾讯精选练习 50 题,企业题库 - 字节跳动(当你就想去某一家公司的时候可以用) 剑指 Offer 网友内幕(主要是面经) 力扣的探索和标签 知道该刷什么,以及刷多少了,可能你已经迫不及待投入题海了。 不要着急,可以先看下西法有没有写过相关专题,如果写过, 强烈建议你先看下,一定能让你事半功倍。 我已经开始刷专题了,有没有什么通用的技巧呢?答案是有,而且很多。本文只介绍一部分, 后续我们继续这个话题,给大家带来更多干货技巧。 代码书写技巧代码书写技巧,这次给大家带来三个技巧: 改参数 zip 函数的妙用 关于取模 改参数力扣的参数是可以改名字的,如下图: 你可以将名字改成一个短的或者你熟悉的。比如上面这道题,我写的时候就可以: 123class Solution: def findMedianSortedArrays(self, A: List[int], B: List[int]) -> float: # can use A and B now 这可以使得代码看起来简洁且具有一致性。 经常看我题解的小伙伴应该注意到我的代码比较简洁。一方面是因为我经常用 Python,另一方面就是因为这个技巧。 这里我顺便吐槽一下力扣。力扣的形参命名相当不规范。比如二维数组有时候是 mat,有时候是 nums,有时候是 matrix,有时候又是 grid 。。。 真心不舒服,不过有了这个技巧,大家就不要依赖官方了,自己统一一下就好。 就拿我来说,二维数组我就用 mat 或者 matrix,一维数组用 nums 或者 A 或者 A 和 B(两个一维数组的情况)。比如: 123456# A 和 B 是两个一维的数组def test(A, B): for a in A: # do something for b in B: # do something else 其实不仅仅是形参的命名要统一,我们内部的代码也是一样的。对于我来说: 堆我习惯叫 h 图我习惯叫 graph 队列我习惯叫 q 。。。 大家没有必要和我一样,但是一定要保持一致性,这样可以显著增加代码可读性,可读性高了,调试工作也会变得轻松。 zip 函数的妙用力扣有一些题目会给你两个或者三个一维数组,这两个一维数组的是有关联的。 比如给你两个一维数组 A 和 B,其中 A[i] 表示第 i 个人的体重,B[i] 表示第 i 个人的身高。也就是说都是表示第 i 个人,但是表示的东西不一样。 其实逻辑上就相当于结构体,而且如下结构体的形式在工作中更常见。 1234interface Person { weight: number; height: number;} 但是力扣以两个数组的形式给你了,其实这样不难啊,不就是用一个索引记录么? 1234for(int i= 0;i<A.length;i++) { int weight = A[i] int height = B[i]} 但是如果我需要对重量排序呢?如果你仅仅对 A 排序了,B 也需要进行相应调整的,否则对应关系就乱了。那遇到这样的情况该怎么办呢? 这里介绍一个我经常使用的技巧 zip。 123456zipped = zip(A, B)# 下面我对其进行排序也不会改变相对顺序zipped.sort()# 比如 A 是 [1,2,3] B 是 [4,5,6]# 那么 zipped 就是 [[1,4], [2,5], [3,6]]# 那么 zipped[i][0] 就是第 i 个人的体重,zippd[i][1] 就是第 i 个人的身高 由于 A 和 B “捆绑”到一起了,因此排序也不会改变其相对顺序。 如下是我在力扣 1383 题中使用 zip 技巧的例子: 另外 zip 还有一些其他用处。比如我想要获取当前数组位置的前一项。 不用 zip 可以这么做: 123for i in range(1, len(A)): pre = A[i - 1] cur = A[i] 如果使用 zip 可以这样: 12for pre, cur in zip(A, A[1:]): # do something 这里的原理也很简单。我举个例子你就懂了。比如有一个数组 A :[1,2,3,4]。 那么 A[1:] 就是 [2,3,4] 我将如上两个数组 zip 起来就是 [[1,2], [2,3], [3,4]],所以我对 zip 之后的结果进行遍历就可以方便地写代码了。 这个技巧用处不大,可以不必掌握,大家知道有这么回事就行 有的人可能想问,我的语言没有 zip 怎么办? 我的答案是自行实现 zip。 比如 JavaScript 可以这样实现 zip: 1const zip = (rows) => rows[0].map((_, c) => rows.map((row) => row[c])); 你把它改造成自己的语言版本即可。 关于取模力扣中有很多题目需要你对返回值取模,而且一般都是对 109 + 7 取模。 题目答案让取模那肯定是答案太大了,为啥太大了呢?有啥想法没? 比如 1680. 连接连续二进制数字 题目描述: 1给你一个整数 n ,请你将 1 到 n 的二进制表示连接起来,并返回连接结果对应的 十进制 数字对 109 + 7 取余的结果。 如果我忘记取模或者仅在返回的时候取模都可能会报错,正确的姿势是提前取模。 以上面的题目来说,代码这样写是可以过的。 1234567class Solution: def concatenatedBinary(self, n: int) -> int: ans = 0 mod = 10 **9 + 7 for i in range(1, n + 1): ans = (ans * pow(2, len(bin(i)[2:])) + i) % mod return ans % mod 而如果我这么写会超时(没有提前取模,只是在最后返回才取模): 1234567class Solution: def concatenatedBinary(self, n: int) -> int: ans = 0 mod = 10 **9 + 7 for i in range(1, n + 1): ans = (ans * pow(2, len(bin(i)[2:])) + i) return ans % mod 如果不提前 mod, python 可能超时,其他语言可能溢出。 提前取 mod,会把数值限定在 int 能处理的范围,使用机器自身整数运算功能进行快速运算,而如果之后取 mod,由于 python 对大整数支持的特性,会将 ans 转换为大整数再进行运算,计算相对耗时。 说到溢出,我想起来一个小技巧,那就是二分取中间值的时候如果书写不当也可能溢出。 12# 如下代码,如果 l 和 r 比较大,则可能发生大数溢出mid = (l + r) // 2 这里的 // 是地板除 解决的方案也很简单,这样写就行了: 1mid = (r - l) // 2 + l 另外一个和取模有关的小技巧是判断奇偶。 判断一个数是奇数还是偶数可以通过和 2 取模。 如果返回值是 0 则是偶数,否则是奇数。 需要注意的是和 2 取模为 1 奇数,但是反之不然。即和 2 取模不是 1 也可能是奇数,比如负数,因此还需要多个判断,不如用我上面的方法,即和 2 取模判断是否等于 0。 欢迎大家补充其他小技巧~ 其他技巧这里有一个要和大家强调的点,很多刚刷题的人都不知道,那就是尽量不要使用全局变量。 如果使用全局变量且没有及时清除,不仅可能有性能问题,更可能会在多个测试用例之间形成干扰,导致出错。而且在力扣设计题目通常是会多次调用某一个 api 的。这个时候更是如此,所以不要使用全局变量。 有一些朋友向我反馈“为啥本地好好的,放到力扣上提交就不行”,请先检查下有没有使用全局变量。 调试技巧调试技巧,我们这里先讲两个: 批量测试 数据结构可视化(树的可视化) 批量测试力扣的测试用例其实是可以一次写多个的。 如上图,该题目有两个参数。那两行其实就是一个完整用例。 我这里输入了六行,也就是三个用例。这个时候点击执行,就可以一次执行三个用例。 妈妈再也不用担心我提交太频繁啦~ 执行成功后,我们可以一次查看所有的差异。 如果你老是考虑不到各种边界,那这个功能简直是福音。 另外如果你打比赛,你可以把题目给的测试用例批量复制到这里一次执行看结果,非常有用。 为了方便大家复制所有题目内置的测试用例,我的刷题插件 leetcode-cheatsheet增加了一个功能一键复制所有的内置用例。 正常情况,下点击之后会提示你复制成功,你只需要 ctrl + v 粘贴到测试用例的输入框即可。 但是力扣网站有很多不是很统一的地方,这就需要我不断进行兼容。比如如下兼容代码: 上面代码指的是力扣测试用例的 html 标签是不定的,并且有时候是”输入:“(注意是中文的:),有时候又是”输入:“(注意是英文的:)。 因此难免有我无法兼容的情况。因此就会发生类似这样的情况: 遇到这样的情况,你可以点击弹出信息的反馈文字,给我反馈。不过据我的测试,大部分情况是没问题的。 插件目前已经发布到谷歌商店了,通过谷歌商店安装的朋友审核通过后会自动更新。离线安装的朋友需要手动安装,不过我的更新蛮频繁的,强烈建议在线安装。商店地址:https://chrome.google.com/webstore/detail/leetcode-cheatsheet/fniccleejlofifaakbgppmbbcdfjonle?hl=en-US 上线几天已经有 100 多人安装了, 你确定不试试么? 树的可视化力扣支持大家对树进行可视化,只要点击这个树结构可视化按钮即可(只有树的题目才有这个按钮)。 如果你写了多个数组,也并不会生成多个树,貌似是以最后一次输入为准。 力扣暂时没有提供其他数据结构的可视化,比如数组,链表等。这可能对大部分人来说没什么,但是对于我这样经常写题解,画图的人就不一样了。如果可以快速画图,那么对我效率肯定有大幅度的提升。 lucifer 建议大家也养成写题解的好习惯。 因此我打算在我的刷题插件里面加其他数据结构的可视化功能, 已经在规划啦~ 现在草稿了一些东西。 比如这样的树: 和这样的树: 现在其实还有些问题,而且我想多加几种数据结构方便写题解,所以就之后再说好了。 本地调试技巧我们可以通过按照编辑器插件在本地编辑器中写代码,然后通过编辑器插件将其提交到力扣即可。 这样你在本地的调试插件都可以用于算法调试了。 这里推荐两个可视化调试插件: Cyberbrain vscode-debug-visualizer 推荐一个网站OI Wiki 致力于成为一个免费开放且持续更新的 编程竞赛 (competitive programming) 知识整合站点,大家可以在这里获取与竞赛相关的、有趣又实用的知识。我们为大家准备了竞赛中的基础知识、常见题型、解题思路以及常用工具等内容,帮助大家更快速深入地学习编程竞赛中涉及到的知识。 地址:https://oi-wiki.org/ 其他力扣提交成功之后除了可以看到自己的排名情况(击败百分之多少),还可以查看别人的提交代码。 这里可以看所有分段的分布情况,也可以直接点击对应的柱子,查看别人的代码怎么写的。比如我这里直接点开了击败 100% 的代码,研究下 ta 是怎么写的。 发现确实代码比我的好,于是我就又”学会“了一招(技能++)。 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 预告下期给大家讲更加干货的技巧,一定不要错过哦。 一看就会,一写就废, 如何克服? 如何锁定使用哪种算法。比如我看到了这道题,我怎么知道该用什么解法呢?二分?动态规划?","categories":[{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/categories/刷题方法/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/tags/刷题方法/"}]},{"title":"我是如何把简单题目做成困难的?","slug":"91-tougao-xiaoyang-01","date":"2020-12-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.834Z","comments":true,"path":"2020/12/04/91-tougao-xiaoyang-01/","link":"","permalink":"https://lucifer.ren/blog/2020/12/04/91-tougao-xiaoyang-01/","excerpt":"作者:小漾 来源:https://github.com/suukii/91-days-algorithm 大家好,我是 lucifer,众所周知,我是一个小前端 (不是) 。其实,我是 lucifer 的 1379 号迷妹观察员,我是一粒纳米前端。(不要回答,不要回答,不要回答!!!) 这是第一次投稿,所以可以废话几句,说一下我为什么做题和写题解。刚开始做算法题的时候,只是纯粹觉得好玩,所以不仅没有刷题计划,写题解也只是随便记下几笔,几个月后自己也看不懂的那种。一次偶然机会发现了 lucifer 的明星题解仓库,是找到了 onepiece 的感觉。受他的启发,我也开始写些尽量能让人看懂的题解,虽然还赶不上 lucifer,但跟自己比总算是有了些进步。 身为迷妹观察员,lucifer 的 91 天学算法当然是不能错过的活动,现在活动的第二期正在 🔥 热进行中,有兴趣的同学了解一下呀。言归正传,跟着 91 课程我不再是漫无目的,而是计划清晰,按照课程安排的专题来做题,这样不仅更有利于了解某一类题涉及的相关知识,还能熟悉这类题的套路,再次遇见相似题型也能更快有思路。 废话就这么多,以下是正文部分。等等,还有最后一句,上面的”不要回答”是个三体梗,不知道有没有人 GET 到我。 今天给大家带来一道力扣简单题,官方题解只给出了一种最优解。本文比较贪心,打算带大家用四种姿势来解决这道题。 ​","text":"作者:小漾 来源:https://github.com/suukii/91-days-algorithm 大家好,我是 lucifer,众所周知,我是一个小前端 (不是) 。其实,我是 lucifer 的 1379 号迷妹观察员,我是一粒纳米前端。(不要回答,不要回答,不要回答!!!) 这是第一次投稿,所以可以废话几句,说一下我为什么做题和写题解。刚开始做算法题的时候,只是纯粹觉得好玩,所以不仅没有刷题计划,写题解也只是随便记下几笔,几个月后自己也看不懂的那种。一次偶然机会发现了 lucifer 的明星题解仓库,是找到了 onepiece 的感觉。受他的启发,我也开始写些尽量能让人看懂的题解,虽然还赶不上 lucifer,但跟自己比总算是有了些进步。 身为迷妹观察员,lucifer 的 91 天学算法当然是不能错过的活动,现在活动的第二期正在 🔥 热进行中,有兴趣的同学了解一下呀。言归正传,跟着 91 课程我不再是漫无目的,而是计划清晰,按照课程安排的专题来做题,这样不仅更有利于了解某一类题涉及的相关知识,还能熟悉这类题的套路,再次遇见相似题型也能更快有思路。 废话就这么多,以下是正文部分。等等,还有最后一句,上面的”不要回答”是个三体梗,不知道有没有人 GET 到我。 今天给大家带来一道力扣简单题,官方题解只给出了一种最优解。本文比较贪心,打算带大家用四种姿势来解决这道题。 ​ 题目描述题目地址:https://leetcode-cn.com/problems/shortest-distance-to-a-character 1234567891011给定一个字符串 S 和一个字符 C。返回一个代表字符串 S 中每个字符到字符串 S 中的字符 C 的最短距离的数组。示例 1:输入: S = "loveleetcode", C = 'e'输出: [3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]说明:字符串 S 的长度范围为 [1, 10000]。C 是一个单字符,且保证是字符串 S 里的字符。S 和 C 中的所有字母均为小写字母。 解法 1:中心扩展法思路这是最符合直觉的思路,对每个字符分别进行如下处理: 从当前下标出发,分别向左、右两个方向去寻找目标字符 C。 只在一个方向找到的话,直接计算字符距离。 两个方向都找到的话,取两个距离的最小值。 复杂度分析我们需要对每一个元素都进行一次扩展操作,因此时间复杂度就是 $N$ * 向两边扩展的总时间复杂度。 而最坏的情况是目标字符 C 在字符串 S 的左右两个端点位置,这个时候时间复杂度是 $O(N)$,因此总的时间复杂度就是 $O(N^2)$ 时间复杂度:$O(N^2)$,N 为 S 的长度。 空间复杂度:$O(1)$。 代码JavaScript Code 1234567891011121314151617181920212223242526272829303132333435363738/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { // 结果数组 res var res = Array(S.length).fill(0); for (let i = 0; i < S.length; i++) { // 如果当前是目标字符,就什么都不用做 if (S[i] === C) continue; // 定义两个指针 l, r 分别向左、右两个方向寻找目标字符 C,取最短距离 let l = i, r = i, shortest = Infinity; while (l >= 0) { if (S[l] === C) { shortest = Math.min(shortest, i - l); break; } l--; } while (r < S.length) { if (S[r] === C) { shortest = Math.min(shortest, r - i); break; } r++; } res[i] = shortest; } return res;}; 解法 2:空间换时间思路空间换时间是编程中很常见的一种 trade-off (反过来,时间换空间也是)。 因为目标字符 C 在 S 中的位置是不变的,所以我们可以提前将 C 的所有下标记录在一个数组 cIndices 中。 然后遍历字符串 S 中的每个字符,到 cIndices 中找到距离当前位置最近的下标,计算距离。 复杂度分析和上面方法类似,只是向两边扩展的动作变成了线性扫描 cIndices,因此时间复杂度就是 $N$ * 线性扫描 cIndices的时间复杂度。 时间复杂度:$O(N*K)$,N 是 S 的长度,K 是字符 C 在字符串中出现的次数。由于 $K <= N$。因此时间上一定是优于上面的解法的。 空间复杂度:$O(K)$,K 为字符 C 出现的次数,这是记录字符 C 出现下标的辅助数组消耗的空间。 实际上,由于 cIndices 是一个单调递增的序列,因此我们可以使用二分来确定最近的 index,时间可以优化到 $N*logK$,这个就留给各位来解决吧。如果对二分不熟悉的,可以看看我往期的《二分专题》 代码JavaScript Code 1234567891011121314151617181920212223242526272829303132333435/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { // 记录 C 字符在 S 字符串中出现的所有下标 var cIndices = []; for (let i = 0; i < S.length; i++) { if (S[i] === C) cIndices.push(i); } // 结果数组 res var res = Array(S.length).fill(Infinity); for (let i = 0; i < S.length; i++) { // 目标字符,距离是 0 if (S[i] === C) { res[i] = 0; continue; } // 非目标字符,到下标数组中找最近的下标 for (const cIndex of cIndices) { const dist = Math.abs(cIndex - i); // 小小剪枝一下 // 注:因为 cIndices 中的下标是递增的,后面的 dist 也会越来越大,可以排除 if (dist >= res[i]) break; res[i] = dist; } } return res;}; 解法 3:贪心思路其实对于每个字符来说,它只关心离它最近的那个 C 字符,其他的它都不管。所以这里还可以用贪心的思路: 先 从左往右 遍历字符串 S,用一个数组 left 记录每个字符 左侧 出现的最后一个 C 字符的下标; 再 从右往左 遍历字符串 S,用一个数组 right 记录每个字符 右侧 出现的最后一个 C 字符的下标; 然后同时遍历这两个数组,计算距离最小值。 优化 1 再多想一步,其实第二个数组并不需要。因为对于左右两侧的 C 字符,我们也只关心其中距离更近的那一个,所以第二次遍历的时候可以看情况覆盖掉第一个数组的值: 字符左侧没有出现过 C 字符 i - left > right - i (i 为当前字符下标,left 为字符左侧最近的 C 下标,right 为字符右侧最近的 C 下标) 如果出现以上两种情况,就可以进行覆盖,最后再遍历一次数组计算距离。 优化 2 如果我们是直接记录 C 与当前字符的距离,而不是记录 C 的下标,还可以省掉最后一次遍历计算距离的过程。 复杂度分析上面我说了要开辟一个数组。而实际上题目也要返回一个数组,这个数组的长度也恰好是 $N$,这个空间是不可避免的。因此我们直接利用这个数组,而不需要额外开辟空间,因此这里空间复杂度是 $O(1)$,而不是 $O(N)$,具体可以看下方代码区。 时间复杂度:$O(N)$,N 是 S 的长度。 空间复杂度:$O(1)$。 代码JavaScript Code 1234567891011121314151617181920212223242526272829/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { var res = Array(S.length); // 第一次遍历:从左往右 // 找到出现在左侧的 C 字符的最后下标 for (let i = 0; i < S.length; i++) { if (S[i] === C) res[i] = i; // 如果左侧没有出现 C 字符的话,用 Infinity 进行标记 else res[i] = res[i - 1] === void 0 ? Infinity : res[i - 1]; } // 第二次遍历:从右往左 // 找出现在右侧的 C 字符的最后下标 // 如果左侧没有出现过 C 字符,或者右侧出现的 C 字符距离更近,就更新 res[i] for (let i = S.length - 1; i >= 0; i--) { if (res[i] === Infinity || res[i + 1] - i < i - res[i]) res[i] = res[i + 1]; } // 计算距离 for (let i = 0; i < res.length; i++) { res[i] = Math.abs(res[i] - i); } return res;}; 直接计算距离: JavaScript Code 123456789101112131415161718192021/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { var res = Array(S.length); for (let i = 0; i < S.length; i++) { if (S[i] === C) res[i] = 0; // 记录距离:res[i - 1] + 1 else res[i] = res[i - 1] === void 0 ? Infinity : res[i - 1] + 1; } for (let i = S.length - 1; i >= 0; i--) { // 更新距离:res[i + 1] + 1 if (res[i] === Infinity || res[i + 1] + 1 < res[i]) res[i] = res[i + 1] + 1; } return res;}; Python Code: 12345678910111213class Solution: def shortestToChar(self, S: str, C: str) -> List[int]: pre = -len(S) ans = [] for i in range(len(S)): if S[i] == C: pre = i ans.append(i - pre) pre = len(S) * 2 for i in range(len(S) - 1, -1, -1): if S[i] == C: pre = i ans[i] = min(ans[i], pre - i) return ans 解法 4:窗口思路把 C 看成分界线,将 S 划分成一个个窗口。然后对每个窗口进行遍历,分别计算每个字符到窗口边界的距离最小值,并在遍历的过程中更新窗口信息即可。 复杂度分析由于更新窗口里的“搜索”下一个窗口的操作总共只需要 $N$ 次,因此时间复杂度仍然是 $N$,而不是 $N^2$。 时间复杂度:$O(N)$,N 是 S 的长度。 空间复杂度:$O(1)$。 代码JavaScript Code 1234567891011121314151617181920212223242526/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { // 窗口左边界,如果没有就初始化为 Infinity let l = S[0] === C ? 0 : Infinity, // 窗口右边界 r = S.indexOf(C, 1); const res = Array(S.length); for (let i = 0; i < S.length; i++) { // 计算字符到当前窗口左右边界的最小距离 res[i] = Math.min(Math.abs(i - l), Math.abs(r - i)); // 遍历完了当前窗口的字符后,将整个窗口右移 if (i === r) { l = r; r = S.indexOf(C, l + 1); } } return res;}; 小结本文给大家介绍了这道题的四种解法,从直觉思路入手,到使用空间换时间的策略,再到贪心算法思想。最后是一个窗口的解法简单直白,同时复杂度也是最优的思路。 对于刚开始做题的人来说,”做出来”是首要任务,但如果你有余力的话,也可以试试这样”一题多解”,多锻炼一下自己。 但无论怎样,只要你对算法感兴趣,一定要考虑关注 lucifer 这个算法灯塔哦。不要嫌我啰嗦,真话不啰嗦。 更多题解可以访问:https://github.com/suukii/91-days-algorithm end大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 我整理的 1000 多页的电子书已限时免费下载,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"图解数据结构","slug":"91-tougao-tianxing-01","date":"2020-12-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.709Z","comments":true,"path":"2020/12/02/91-tougao-tianxing-01/","link":"","permalink":"https://lucifer.ren/blog/2020/12/02/91-tougao-tianxing-01/","excerpt":"参加了 lucifer 的 91 天学算法活动,不知不觉中已经一月有余。从盲目地做到有目的、有套路地去做。 在 lucifer 的 91 课程中,从基础到进阶到专题,在这个月中,经历了基础篇的洗礼,不管在做题思路,还是做题速度都有了很大的提升,这个课程,没什么好说的,点赞点赞再点赞。也意识到学习好数据结构有多重要,不仅是思维方式的改变,还是在工程上的应用。 对一个问题使用画图、举例、分解这 3 种方法将其化繁为简,形成清晰思路再动手写代码,一张好的图能够更好地帮助去理解一个算法。因此本次分享如何使用画图同时结合经典的题目的方法去阐述数据结构。 ​","text":"参加了 lucifer 的 91 天学算法活动,不知不觉中已经一月有余。从盲目地做到有目的、有套路地去做。 在 lucifer 的 91 课程中,从基础到进阶到专题,在这个月中,经历了基础篇的洗礼,不管在做题思路,还是做题速度都有了很大的提升,这个课程,没什么好说的,点赞点赞再点赞。也意识到学习好数据结构有多重要,不仅是思维方式的改变,还是在工程上的应用。 对一个问题使用画图、举例、分解这 3 种方法将其化繁为简,形成清晰思路再动手写代码,一张好的图能够更好地帮助去理解一个算法。因此本次分享如何使用画图同时结合经典的题目的方法去阐述数据结构。 ​ 数据结构与算法有用么?这里我摘录了一个知乎的高赞回答给大家做参考: 个人认为数据结构是编程最重要的基本功没有之一!学了顺序表和链表,你就知道,在查询操作更多的程序中,你应该用顺序表;而修改操作更多的程序中,你要使用链表;而单向链表不方便怎么办,每次都从头到尾好麻烦啊,怎么办?你这时就会想到双向链表 or 循环链表。学了栈之后,你就知道,很多涉及后入先出的问题,例如函数递归就是个栈模型、Android 的屏幕跳转就用到栈,很多类似的东西,你就会第一时间想到:我会用这东西来去写算法实现这个功能。学了队列之后,你就知道,对于先入先出要排队的问题,你就要用到队列,例如多个网络下载任务,我该怎么去调度它们去获得网络资源呢?再例如操作系统的进程(or 线程)调度,我该怎么去分配资源(像 CPU)给多个任务呢?肯定不能全部一起拥有的,资源只有一个,那就要排队!那么怎么排队呢?用普通的队列?但是对于那些优先级高的线程怎么办?那也太共产主义了吧,这时,你就会想到了优先队列,优先队列怎么实现?用堆,然后你就有疑问了,堆是啥玩意?自己查吧,敲累了。总之好好学数据结构就对了。我觉得数据结构就相当于:我塞牙了,那么就要用到牙签这“数据结构”,当然你用指甲也行,只不过“性能”没那么好;我要拧螺母,肯定用扳手这个“数据结构”,当然你用钳子也行,只不过也没那么好用。学习数据结构,就是为了了解以后在 IT 行业里搬砖需要用到什么工具,这些工具有什么利弊,应用于什么场景。以后用的过程中,你会发现这些基础的“工具”也存在着一些缺陷,你不满足于此工具,此时,你就开始自己在这些数据结构的基础上加以改造,这就叫做自定义数据结构。而且,你以后还会造出很多其他应用于实际场景的数据结构。。你用这些数据结构去造轮子,不知不觉,你成了又一个轮子哥。 既然这么有用,那我们怎么学习呢?我的建议是先把常见的数据结构学个大概,然后开始安装专题的形式突破算法。这篇文章就是给大家快速过一下一部分常见的数据结构。 从逻辑上分,数据结构分为线性和非线性两大类。 线性数据结构包括数组、链表、栈、队列。 非线性结构包括树、哈希表、堆、图。 而我们常用的数据结构主要是数组、链表、栈、树,这同时也是本文要讲的内容。 数据结构一览数组数组的定义为存放在连续内存空间上的相同类型数据的集合。因为内存空间连续,所以能在 O(1)的时间进行存取。 剑指 offer03.数组中的重复的数字题目描述: 12在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 分析: 重复意味至少出现两次,那么找重复就变成了统计数字出现的频率了。那如何统计数字频率呢?(不使用哈希表),我们可以开辟一个长度为 n 的数组 count_nums,并且初始化为 0,遍历数组 nums,使用 nums[i]为 count_nums 赋值. 图解: (注意:数组下标从 0 开始) 剑指 offer21. 调整数组顺序使奇数位于偶数前面题目描述: 12输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 分析: 根据题目要求,需要我们调整数组中奇偶数的顺序,那这样的话,我们可以从数组的两端同时开始遍历,右边遇到奇数的时候停下,左边遇到偶数的时候停下,然后进行交换。 1122.数组的相对排序题目描述: 1234567891011给你两个数组,arr1 和 arr2,arr2 中的元素各不相同arr2 中的每个元素都出现在 arr1 中对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。示例输入:arr1 = [2,3,1,3,2,4,6,7,9,2], arr2 = [2,1,4,3,9,6]输出:[2,2,2,1,4,3,3,9,6,7] 分析: 观察输出,发现数字,因为 arr1 总是根据 arr2 中元素的相对大小来排序,所以只相当于在 arr2 中进行填充,每个地方该填充多少呢?这个时候就需要去统计 arr1 中每个数字出现的频率。 小结在数组中,因为数组是一个有序的结构,这里的有序是指在位置上的有序,所以大多数只需要考虑顺序或者相对顺序即可。 链表链表是一种线性数据结构,其中的每个元素实际上是一个单独的对象,每一个节点里存到下一个节点的指针(Pointer)。就像我们玩的寻宝游戏一样,当我们找到一个宝箱的时候,里面还存在寻找下一个宝箱的藏宝图,依次类推,每一个宝箱都是如此,一直到找到最终宝藏。 通过单链表,可以衍生出循环链表,双向链表等。 我们来看下链表中比较经典的几个题目。 面试题 02.02. 返回倒数第 k 个节点题目描述: 1234567实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。示例:输入: 1->2->3->4->5 和 k = 2输出: 4 分析: 想要找到倒数第 k 个节点,如果此时在数组中,那我们只需要用最后一个数组的索引减去 k 就能找到这个值,但是链表是不能直接通过索引得到的。如果此时,我们知道最后一个节点的位置,然后往前找 k 个不就找到我们需要的节点了吗?等价于我们要找的节点和最后一个节点相隔 k 个位置。所以当有一个指针 front 出发 k 步后,我们再出发,等 front 到达终点时,我们刚好到达倒数第 k 个节点。 我们把这种解法叫做双指针,或者快慢指针,或者前后指针,这种方法可以用于寻找链表中间节点,判断是链表中是否存在环(循环链表)并寻找环入口。 61. 旋转链表题目描述: 12345678给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。示例:输入: 1->2->3->4->5->NULL, k = 2输出: 4->5->1->2->3->NULL 分析: 每个数字向后移动 k 位,那么最后 k 位就需要移动到前面,和找倒数第 k 位数字很相似,k 位后面的都移到开头。唯一需要注意的地方就是,k 的值可能大于链表长度的 2 倍及以上,所以需要算出链表的长度,以保证尽快找到倒数 k 的位置。 解法 1找到位置后,直接断开 解法 2制作循环链表,然后再找倒数第 k 个数,然后断开循环链表 24. 两两交换链表中的节点题目描述: 12345678给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。示例:输入:head = [1,2,3,4]输出:[2,1,4,3] 分析: 原理很简单,两个指针,分别指向相邻的两个节点,然后再添加一个临时指针做换交换的中介添加 dummy 节点,不用考虑头节点的情况,更加方便。直接上图: 除了同时操作一个链表之外,有的题目也会给出两个或者更多的链表,如两数相加,如 leetcode 中 2.两数相加、21.合并两个有序链表、160.相交链表 21.相交节点题目描述: 12编写一个程序,找到两个单链表相交的起始节点。 如下面的两个链表 分析: 我们知道,对于任意两个数 ab,一定有 a+b-c=b+a-c, 基于 a+b-c=b+a-c,我们可以设置两个指针,分别指向 A 和 B,以相同的步长同时移动,并在第一次到达尾节点的时候,指向另一个链表,如果存在相交节点,也就是说 c > 0,那么两个指针一定会相遇,相遇处也就是相交节点。而当不存在时,即 c=0,那么两个指针最终都会指向空节点。 小结链表中的操作无非就是两种,插入,删除。解题方法无非就是添加 dummy 节点(解决头节点的判断问题)、快慢指针(快慢不一定是单次步长一样,应该理解为平均步长,即使用了相同的时间,走的路程的长度来定义快慢)。 栈栈是一种先进后出(FILO, First In Last Out)的数据结构,可以把栈理解为 ![image](https://p.ipic.vip/sdiphq.png) 没错,就是上图的罐装薯片,想要吃到最底下的那片,必须依次吃完上面的。而在装薯片的时候,最底下的反而是最先装进去的。 在 leetcode 里面关于栈比较经典的题目有:20.有效的括号;150.逆波兰表达式求值 20.有效的括号题目描述: 123456789给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。示例:"{[()][]}()"| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 || --- | --- | --- | --- | --- | --- | --- | --- | --- | --- || { | [ | ( | ) | ] | [ | ] | } | ( | ) | 分析: 一个有效的括号为,右边必须和左边对应,且存在至少一对有效括号的索引为[i, i+1]。 那么,我们只要是括号左边部分,就入栈,右边部分,就和栈顶元素比较。 图解: 150.逆波兰表达式求值题目描述: 123456根据 逆波兰表示法,求表达式的值。有效的运算符包括 +, -, \\*, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。示例:["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+"] 分析: 根据运算法则,我们可以知道,一个运算有 num1,operation,num2 三部分组成。在一个逆波兰表达式中,运算符前面两个 num 就是这个运算的组成。 我们要做事情就是,找到一个运算符的时候,同时找到他前面的两个数,而栈的现金先去特性满足这个需求,使用栈来解决。 227.基本计算器 II题目描述: 1234实现一个基本的计算器来计算一个简单的字符串表达式的值。字符串表达式仅包含非负整数,+, - ,\\*,/ 四种运算符和空格 。 整数除法仅保留整数部分。示例:3+5\\*2/2-3 分析: 与逆波兰表达式不同的地方是,这里运算符两边是操作数。但是,这又有什么问题呢?万变不离核心,我们只需要在找到运算符的同时,得到运算符两边的操作数。问题来了,还需要考虑运算符的优先级,想到的一个方法就是,只进行乘除法运算,最后进行加法运算,不进行减法运算(减去一个数 ⟺ 加上这个数的负数) 如果能够把这个字符串表达式相似地转换位逆波兰表达式,那就能直接套用逆波兰表达式的代码了,回顾一下,逆波兰表达式是,每次有操作符的时候,就从栈顶出来两个元素。可以通过使用两个栈来实现,一个栈用来存储操作数,一个栈用来存储操作符。如果比栈顶的操作符符优先级低或者相同,那么就从操作符栈取出栈顶运算符号 496.下一个更大元素 I题目描述: 12345678给定两个 没有重复元素的数组 nums1 和 nums2 ,其中 nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1。示例:输入: nums1 = [4,1,2], nums2 = [1,3,4,2].输出: [-1,3,-1] 分析: 题目要求我们找出 nums1 中每个元素在 nums2 中的下一个比这个元素大的值,又提到 nums1 是 nums2 的一个子集,我们不妨找出 nums2 中每个元素的下一个比他大的值,然后映射到 nums1 中。 那如何找出 nums2 中每个元素的下一个比他大的值呢?对于当前元素,若下一个元素比他大,则找到了,否则的话,就把这个元素添加到栈中,一直到找到一个元素比栈顶元素大,这时候把栈里面所有小于这个元素的元素都出栈,听起来很绕,无妨,看图——> 最后栈中依然有数据存在,这是为什么呢?因为这些元素后面找不到比他更大的值了。观察示例数据,4 后面没有比他更大的值了,5 和 1 也是。我们还能观察到栈中元素是从大到小的,可以称这个栈为==单调递减栈==(如 1019.寻找链表中的下一个更大节点,503.下一个更大元素 II、402.移掉 k 位数字,39.每日温度,在 1673.找出最具有竞争力的子序列中,其实只需要构建一个单调递增栈,然后截取前 k 个。)。 回到题目,需要找到 nums1 中元素在 nums2 中的下一个比其大的值,只需要在刚才保存的信息中进行查找,找不到的则不存在,可以使用哈比表保存每个数对应的下一个较大值。 小结栈由于其随时可以出栈和进栈,产生非常多的组合,带来了非常多的变化,所以读懂题目非常重要,然后选择方法,正所谓题目是有限的,方法是有限的。所以紧跟 lucifer 大佬学习套路,是一条值得坚持的道路,毕竟自古深情留不住,唯有套路得人心,这里推荐 lucifer 大佬的《单调栈模板带你秒杀八道题》,带你乱杀。 树树虽相比于链表来说,至少有两个节点(n 个节点就叫 n 叉树),但是树是一个抽象的概念,可以理解为一个不停做选择的过程,给定一个起始条件,会产生多种结果,而这些结果又成为新的条件,以此类推,直到不再有新的条件。在树种,起始条件就是根节点,不再产生新的条件的就是叶子节点。在树种,使用较多的是二叉树。一颗二叉树不管有多大,我们都可以把他拆分为五种形式, 不管是在树上进行什么操作,都需要进行遍历,遍历的方式有两种:广度优先遍历(BFS)和深度优先遍历(DFS)。简单来说,广度就是先找到有多少种可能,然后找出这些可能有多少种可能;而深度就是每次只根据一个条件来找,直到最终没有条件。话不多说,上图。 如果是试错的话,广度是一次把所有的结果都试一试,深度则是一条路走到黑。 这里直接借用 lucifer 大佬的广度、深度优先遍历模板(套路) 12345678function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } for (const child of root.children) { dfs(child) }} 深度优先遍历根据逻辑处理(==敲黑板,很重要==)的先后分为前序遍历、中序遍历、后序遍历 1234567891011121314151617181920212223242526// 前序遍历function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } // 主要逻辑 dfs(root.left) dfs(root.right)//中序遍历function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) // 主要逻辑 dfs(root.right)// 后序遍历function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) dfs(root.right) // 主要逻辑}} 接下来就要实操了 199. 二叉树的右视图题目描述: 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 示例: 输入: [1,2,3,null,5,null,4] 输出: [1, 3, 4] 解释: 分析: 此题即可以使用广度优先,也可以深度优先。使用广度优先,只需要将每一层的节点用一个数组保存下来,然后输出最后一个使用深度优先,这里我使用的是根右左的方式,这样能保证在每进入到一个新的层时,第一个访问到的就是最右边的元素。 上图: 112. 路径总和题目描述: 12给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。说明: 叶子节点是指没有子节点的节点。 示例: 分析: 求一条路径(根节点到叶子节点),这不就一条路走到底吗,没什么好犹豫的,选择深度优先遍历。因为需要获得路径上的和,我们需要把每个节点的值(状态)传递给下一个节点。 在 113. 路径总和 II 中,和本题类似,只需要把节点加入到数组中传递给下一个节点;在 129. 求根到叶子节点数字之和,需要把当前值*10 传递给下一个节点。 662. 二叉树最大宽度题目描述: 12给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的 null 节点也计入长度)之间的长度。 示例: 分析: 最大宽度,不就是找出哪一层最长吗?广度优先搜索会更加方便,需要注意的是,非两端节点的 null 节点也要算到长度中,所以现在每一层存储的不仅仅是有值节点。 上图 513. 找树左下角的值题目描述: 1给定一个二叉树,在树的最后一行找到最左边的值。 示例: </center>分析:两个关键信息,一个最后一行,一个最左边。好像广度,深度都可以找到,在这里以深度进行说明,最后一行就是depth最大的,所以在深度遍历的时候,每次给一层传递depth信息。 与此题类似的还有 111. 二叉树的最小深度,104.二叉树的最大深度 感觉,就这?好像也没什么难的啊,学完 lucifer 的课程,我就是这么膨胀。 小结无非就是,深度遍历时,是否传递信息给下一层,给下一层传递什么信息;广度遍历时,是否保存每一层,是否保存空节点。 总结本次给大家介绍了四种比较常见的数据结构,分别是数组,链表,栈和树。这四种只有树是逻辑上的非线性数据结构,因为一个节点可能有多个孩子,而其他数据结构只有一个前驱和一个后继。 由于先进后出的特性,我们可以用数组轻松地在 $O(1)$ 时间复杂度模拟栈的操作。但是队列就没那么好命了,我们必须使用链表来优化时间复杂度。 链表的考题相对比较单一,只要记住那几个点就好了。 树的题目比较丰富,和它的非线性数据结构有很大关系。由于其是非线性的,因此有了各种遍历方式,常见的是广度优先和深度优先,很多题目都是灵活运用这两种遍历方式问题就迎刃而解。 关注 lucifer,学习算法不迷路。 参考: 基础的数据结构(总览) 几乎刷完了力扣所有的链表题,我发现了这些东西 几乎刷完了力扣所有的树题,我发现了这些东西 回炉重铸, 91 天见证不一样的自己(第二期)","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"来和大家聊聊我是如何刷题的(第一弹)","slug":"shuati-silu","date":"2020-11-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.897Z","comments":true,"path":"2020/11/29/shuati-silu/","link":"","permalink":"https://lucifer.ren/blog/2020/11/29/shuati-silu/","excerpt":"今天给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第一篇。 话不多说,直接上干货。","text":"今天给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第一篇。 话不多说,直接上干货。 我建议大家 BFS我的做法是集中时间只刷某一类的题目。这样对某一类题目就很有心得,做题就有题感,不会做一道是一道,下次碰到类似的题,甚至原题都不会。其实很多算法都是息息相关的,等你攻克了足够多的专题之后,算法知识才能融会贯通。 我建议大家刷题的时候是广度优先,逐个突破 碰到不会的适当放弃,而不是深度优先,”死磕某一个知识“。比如大家在刷树的专题, 碰到一个树型 DP 不会, 这个时候应该果断放弃,等大家刷到 DP 的时候再回过头捡起来。 听起来简单,但是我从哪个专题开始, 题目那么多我该刷哪个呢? 下面是我的 91 天刷题活动的目录: 可以看出,我们的章节安排就是一个专题一个专题, 从简单到困难。大家也可以参考这个模式。如果你实在不知道。 刷题路线可以从网上找,你如果懒得找,而且也不嫌弃在下的话,可以参考我的 leetcode 题解仓库,把里面的题目刷下,或者参加我的 91 天学算法。 BFS 就是在必要的时候不求甚解。比如,我在 穿上衣服我就不认识你了?来聊聊最长上升子序列 中提取了很多 LIS(最长上升子序列)题目。很多人评论说”这效率不行,不如贪心啊!“。 这点我承认。但是我这里的主要目的是给大家横向对比题目,做到多题同解。 大家想看效率高的,其实也不难。 LIS 也可以用 贪心 + 二分 达到不错的效率。代码如下: 代码文字版如下: 12345678910class Solution: def lengthOfLIS(self, A: List[int]) -> int: d = [] for a in A: i = bisect.bisect_left(d, a) if i < len(d): d[i] = a elif not d or d[-1] < a: d.append(a) return len(d) 所以我的意思是,大家在适当的时候要不求甚解,不去追求这些东西。等大家把一个套路学的差不多,咱再学下一个。所谓君子报仇,十年不晚 ^_^ 另外插一句题外话, LIS 真的很有用,大家一定要掌握,掌握了平方的解法再去看看 $NlogN$ 的解法,一些 HARD 题目必须要 $NlogN$ 才能过。 比如这道题: 题目后的提示如下: 1233 <= nums.length <= 10001 <= nums[i] <= 109题目保证 nums 删除一些元素后一定能得到山形数组。 看到这些,大概估算我们的时间复杂度 $N^2logN$,基本过是没问题的,果然就过了。 再次印证了,刷题的多少是次要的,吃透一类题才是王道,这其实就和我的BFS 刷题大法相呼应。 套路很重要以上的这些,其实都是帮助大家识别套路,提高刷题效率的。知道了广度优先,也知道了刷什么题也是不够的。比如: 这些专题有哪些考点?如何应对? 有模板么? 我如何想到用这种解法? 等等 针对这些问题,我写了很多文章给大家。比如前面一段时间,我给大家写了两篇专题: 几乎刷完了力扣所有的树题,我发现了这些东西 几乎刷完了力扣所有的链表题,我发现了这些东西 大家的反响大部分都是不错的。 在之前, 我还写了几篇解套篇,就是将力扣相同解法的题目汇总起来,帮助大家解套,比如: 穿上衣服我就不认识你了?来聊聊最长上升子序列 你的衣服我扒了 - 《最长公共子序列》 甚至还写了母题系列(不过大家不太喜欢,就没继续更新了): 《我是你的妈妈呀》 - 第一期 你认真看完我写的,基本上覆盖了专题下的大部分考点。 你接下来想看啥? 欢迎去我的刷题群告诉我(关注公众号《力扣加加》回复 leetcode 根据提示操作即可)。 掌握多个编程语言刷题以及打比赛都讲究速度,天下武功唯快不破。 这个快,一方面是运行速度快,另一方面是编码速度快。你可以看出很多人刷题,打比赛都会不断切换语言的。我们要承认不同语言效率是不一样的,这个效率可能是执行,也可能是编码。具体使用哪种语言,看你的需求。 论编码速度,那肯定动态语言快,论执行速度那肯定静态语言快。 所以我的建议是大家至少掌握一静一动,即掌握一个动态语言,一个静态语言。 我个人动态语言用的 Python 和 JS,静态语言用的 Java 和 CPP,大家可以作为参考。 一个小建议是你选择的语言要是题解比较热门的。那什么语言是热门的?其实很容易。力扣题解区,语言排名高的基本就是了,如下图: 掌握语言不仅能帮助你在效率中运用自如,并且还容易看懂别人的题解。除此之外还有一个用,那就是回头复习的时候用。拿我来说, 我会不固定回去刷以前做过的题,但是一道题做过了就没新鲜感了,这个时候我就换个语言继续刷,又是一番滋味。 使用模拟面试这个技巧,我之前提到过。力扣也有模拟面试的功能,大家也可以线下真人白板面试。不管如何,建议大家一定要有时间观念和一次 AC 的标准。 使用模板很多题目都是模板题。你如我在 二分法专题 就给大家总结了无数的模板,其实还有很多专题都有,大家去我的历史文章翻翻就有。 但是大家一定理解之后再去用模板。 不要没理解直接套,这是不好的。 更多技巧,期待下次。 预告最后最后给大家一个小道消息,和上面的解题模板有关。 接下来,力扣加加的刷题插件计划推出刷题模板功能。 给大家提供多种刷题模板,可以直接复制使用。 各个模板都有都有的题目,大家可以直达题目进行”默写“。 更多功能,等你来提~","categories":[{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/categories/刷题方法/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/tags/刷题方法/"}]},{"title":"几乎刷完了力扣所有的树题,我发现了这些东西。。。","slug":"tree","date":"2020-11-22T16:00:00.000Z","updated":"2023-01-05T12:24:50.071Z","comments":true,"path":"2020/11/23/tree/","link":"","permalink":"https://lucifer.ren/blog/2020/11/23/tree/","excerpt":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。(就是本文)","text":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。(就是本文) 一点絮叨首先亮一下本文的主角 - 树(我的化妆技术还行吧^_^): 树标签在 leetcode 一共有 175 道题。 为了准备这个专题,我花了几天时间将 leetcode 几乎所有的树题目都刷了一遍。 除了 35 个上锁的,1 个不能做的题(1628 题不知道为啥做不了), 4 个标着树的标签但却是图的题目,其他我都刷了一遍。通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。 食用指南大家好,我是 lucifer。今天给大家带来的是《树》专题。另外为了保持章节的聚焦性和实用性,省去了一些内容,比如哈夫曼树,前缀树,平衡二叉树(红黑树等),二叉堆。这些内容相对来说实用性没有那么强,如果大家对这些内容也感兴趣,可以关注下我的仓库 leetcode 算法题解,大家有想看的内容也可以留言告诉我哦~ 另外要提前告知大家的是本文所讲的很多内容都很依赖于递归。关于递归的练习我推荐大家把递归过程画到纸上,手动代入几次。等大脑熟悉了递归之后就不用这么辛苦了。 实在懒得画图的同学也可以找一个可视化递归的网站,比如 https://recursion.now.sh/。 等你对递归有了一定的理解之后就仔细研究一下树的各种遍历方法,再把本文看完,最后把文章末尾的题目做一做,搞定个递归问题不大。 文章的后面《两个基本点 - 深度优先遍历》部分,对于如何练习树的遍历的递归思维我也提出了一种方法 最后要强调的是,本文只是帮助你搞定树题目的常见套路,但不是说树的所有题目涉及的考点都讲。比如树状 DP 这种不在本文的讨论范围,因为这种题更侧重的是 DP,如果你不懂 DP 多半是做不出来的,你需要的是学完树和 DP 之后再去学树状 DP。如果你对这些内容感兴趣,可以期待我的后续专题。 前言提到树大家更熟悉的是现实中的树,而现实中的树是这样的: 而计算机中的树其实是现实中的树的倒影。 计算机的数据结构是对现实世界物体间关系的一种抽象。比如家族的族谱,公司架构中的人员组织关系,电脑中的文件夹结构,html 渲染的 dom 结构等等,这些有层次关系的结构在计算机领域都叫做树。 首先明确一下,树其实是一种逻辑结构。比如笔者平时写复杂递归的时候,尽管笔者做的题目不是树,也会画一个递归树帮助自己理解。 树是一种重要的思维工具 以最简单的计算 fibonacci 数列为例: 12345function fn(n) { if (n == 0 || n == 1) return n; return fn(n - 1) + fn(n - 2);} 很明显它的入参和返回值都不是树,但是却不影响我们用树的思维去思考。 继续回到上面的代码,根据上面的代码可以画出如下的递归树。 其中树的边表示的是返回值,树节点表示的是需要计算的值,即 fn(n)。 以计算 5 的 fibbonacci 为例,过程大概是这样的(动图演示): 这其实就是一个树的后序遍历,你说树(逻辑上的树)是不是很重要?关于后序遍历咱们后面再讲,现在大家知道是这么回事就行。 大家也可以去 这个网站 查看上面算法的单步执行效果。当然这个网站还有更多的算法的动画演示。 上面的图箭头方向是为了方便大家理解。其实箭头方向变成向下的才是真的树结构。 广义的树真的很有用,但是它范围太大了。 本文所讲的树的题目是比较狭隘的树,指的是输入(参数)或者输出(返回值)是树结构的题目。 基本概念 树的基本概念难度都不大,为了节省篇幅,我这里简单过一下。对于你不熟悉的点,大家自行去查找一下相关资料。我相信大家也不是来看这些的,大家应该想看一些不一样的东西,比如说一些做题的套路。 树是一种非线性数据结构。树结构的基本单位是节点。节点之间的链接,称为分支(branch)。节点与分支形成树状,结构的开端,称为根(root),或根结点。根节点之外的节点,称为子节点(child)。没有链接到其他子节点的节点,称为叶节点(leaf)。如下图是一个典型的树结构: 每个节点可以用以下数据结构来表示: 1234Node { value: any; // 当前节点的值 children: Array<Node>; // 指向其儿子} 其他重要概念: 树的高度:节点到叶子节点的最大值就是其高度。 树的深度:高度和深度是相反的,高度是从下往上数,深度是从上往下。因此根节点的深度和叶子节点的高度是 0。 树的层:根开始定义,根为第一层,根的孩子为第二层。 二叉树,三叉树,。。。 N 叉树,由其子节点最多可以有几个决定,最多有 N 个就是 N 叉树。 二叉树二叉树是树结构的一种,两个叉就是说每个节点最多只有两个子节点,我们习惯称之为左节点和右节点。 注意这个只是名字而已,并不是实际位置上的左右 二叉树也是我们做算法题最常见的一种树,因此我们花大篇幅介绍它,大家也要花大量时间重点掌握。 二叉树可以用以下数据结构表示: 12345Node { value: any; // 当前节点的值 left: Node | null; // 左儿子 right: Node | null; // 右儿子} 二叉树分类 完全二叉树 满二叉树 二叉搜索树 平衡二叉树 红黑树 。。。 二叉树的表示 链表存储 数组存储。非常适合完全二叉树 树题难度几何?很多人觉得树是一个很难的专题。实际上,只要你掌握了诀窍,它并没那么难。 从官方的难度标签来看,树的题目处于困难难度的一共是 14 道, 这其中还有 1 个标着树的标签但是却是图的题目,因此困难率是 13 / 175 ,也就是 7.4 % 左右。如果排除上锁的 5 道,困难的只有 9 道。大多数困难题,相信你看完本节的内容,也可以做出来。 从通过率来看,只有不到三分之一的题目平均通过率在 50% 以下,其他(绝大多数的题目)通过率都是 50%以上。50% 是一个什么概念呢?这其实很高了。举个例子来说, BFS 的平均通过率差不多在 50%。 而大家认为比较难的二分法和动态规划的平均通过率差不多 40%。 大家不要对树有压力, 树和链表一样是相对容易的专题,今天 lucifer 给大家带来了一个口诀一个中心,两个基本点,三种题型,四个重要概念,七个技巧,帮助你克服树这个难关。 一个中心一个中心指的是树的遍历。整个树的专题只有一个中心点,那就是树的遍历,大家务必牢牢记住。 不管是什么题目,核心就是树的遍历,这是一切的基础,不会树的遍历后面讲的都是白搭。 其实树的遍历的本质就是去把树里边儿的每个元素都访问一遍(任何数据结构的遍历不都是如此么?)。但怎么访问的?我不能直接访问叶子节点啊,我必须得从根节点开始访问,然后根据子节点指针访问子节点,但是子节点有多个(二叉树最多两个)方向,所以又有了先访问哪个的问题,这造成了不同的遍历方式。 左右子节点的访问顺序通常不重要,极个别情况下会有一些微妙区别。比如说我们想要访问一棵树的最左下角节点,那么顺序就会产生影响,但这种题目会比较少一点。 而遍历不是目的,遍历是为了更好地做处理,这里的处理包括搜索,修改树等。树虽然只能从根开始访问,但是我们可以选择在访问完毕回来的时候做处理,还是在访问回来之前做处理,这两种不同的方式就是后序遍历和先序遍历。 关于具体的遍历,后面会给大家详细讲,现在只要知道这些遍历是怎么来的就行了。 而树的遍历又可以分为两个基本类型,分别是深度优先遍历和广度优先遍历。这两种遍历方式并不是树特有的,但却伴随树的所有题目。值得注意的是,这两种遍历方式只是一种逻辑而已,因此理论可以应用于任何数据结构,比如 365. 水壶问题 中,就可以对水壶的状态使用广度优先遍历,而水壶的状态可以用一个二元组来表示。 遗憾的是这道题的广度优先遍历解法在 LeetCode 上提交会超时 树的遍历迭代写法很多小朋友表示二叉树前中后序的递归写法没问题,但是迭代就写不出来,问我有什么好的方法没有。 这里就给大家介绍一种写迭代遍历树的实操技巧,统一三种树的遍历方式,包你不会错,这个方法叫做双色标记法。 如果你会了这个技巧,那么你平时练习大可只用递归。然后面试的时候,真的要求用迭代或者是对性能有特别要求的那种题目,那你就用我的方法套就行了,下面我来详细讲一下这种方法。 我们知道垃圾回收算法中,有一种算法叫三色标记法。 即: 用白色表示尚未访问 灰色表示尚未完全访问子节点 黑色表示子节点全部访问 那么我们可以模仿其思想,使用双色标记法来统一三种遍历。 其核心思想如下: 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。 如果遇到的节点为灰色,则将节点的值输出。 使用这种方法实现的中序遍历如下: 123456789101112131415class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: WHITE, GRAY = 0, 1 res = [] stack = [(WHITE, root)] while stack: color, node = stack.pop() if node is None: continue if color == WHITE: stack.append((WHITE, node.right)) stack.append((GRAY, node)) stack.append((WHITE, node.left)) else: res.append(node.val) return res 可以看出,实现上 WHITE 就表示的是递归中的第一次进入过程,Gray 则表示递归中的从叶子节点返回的过程。 因此这种迭代的写法更接近递归写法的本质。 如要实现前序、后序遍历,也只需要调整左右子节点的入栈顺序即可,其他部分是无需做任何变化。 (前中后序遍历只需要调整这三句话的位置即可) 注:这张示意图的前序和后序画反了 可以看出使用三色标记法,其写法类似递归的形式,因此便于记忆和书写。 有的同学可能会说,这里的每一个节点都会入栈出栈两次,相比普通的迭代入栈和出栈次数整整加了一倍,这性能可以接受么?我要说的是这种时间和空间的增加仅仅是常数项的增加,大多数情况并不会都程序造成太大的影响。 除了有时候比赛会比较恶心人,会卡常(卡常是指通过计算机原理相关的、与理论复杂度无关的方法对代码运行速度进行优化)。反过来看,大家写的代码大多数是递归,要知道递归由于内存栈的开销,性能通常比这里的二色标记法更差才对, 那为啥不用一次入栈的迭代呢?更极端一点,为啥大家不都用 morris 遍历 呢? morris 遍历 是可以在常数的空间复杂度完成树的遍历的一种算法。 我认为在大多数情况下,大家对这种细小的差异可以不用太关注。另外如果这种遍历方式完全掌握了,再根据递归的思想去写一次入栈的迭代也不是难事。 无非就是调用函数的时候入栈,函数 return 时候出栈罢了。更多二叉树遍历的内容,大家也可以访问我之前写的专题《二叉树的遍历》。 小结简单总结一下,树的题目一个中心就是树的遍历。树的遍历分为两种,分别是深度优先遍历和广度优先遍历。关于树的不同深度优先遍历(前序,中序和后序遍历)的迭代写法是大多数人容易犯错的地方,因此我介绍了一种统一三种遍历的方法 - 二色标记法,这样大家以后写迭代的树的前中后序遍历就再也不用怕了。如果大家彻底熟悉了这种写法,再去记忆和练习一次入栈甚至是 Morris 遍历即可。 其实用一次入栈和出栈的迭代实现递归也很简单,无非就是还是用递归思想,只不过你把递归体放到循环里边而已。大家可以在熟悉递归之后再回头看看就容易理解了。树的深度遍历的递归技巧,我们会在后面的《两个基本点》部分讲解。 两个基本点上面提到了树的遍历有两种基本方式,分别是深度优先遍历(以下简称 DFS)和广度优先遍历(以下简称 BFS),这就是两个基本点。这两种遍历方式下面又会细分几种方式。比如 DFS 细分为前中后序遍历, BFS 细分为带层的和不带层的。 DFS 适合做一些暴力枚举的题目,DFS 如果借助函数调用栈,则可以轻松地使用递归来实现。 BFS 不是 层次遍历而 BFS 适合求最短距离,这个和层次遍历是不一样的,很多人搞混。这里强调一下,层次遍历和 BFS 是完全不一样的东西。 层次遍历就是一层层遍历树,按照树的层次顺序进行访问。 (层次遍历图示) BFS 的核心在于求最短问题时候可以提前终止,这才是它的核心价值,层次遍历是一种不需要提前终止的 BFS 的副产物。这个提前终止不同于 DFS 的剪枝的提前终止,而是找到最近目标的提前终止。比如我要找距离最近的目标节点,BFS 找到目标节点就可以直接返回。而 DFS 要穷举所有可能才能找到最近的,这才是 BFS 的核心价值。实际上,我们也可以使用 DFS 实现层次遍历的效果,借助于递归,代码甚至会更简单。 如果找到任意一个满足条件的节点就好了,不必最近的,那么 DFS 和 BFS 没有太大差别。同时为了书写简单,我通常会选择 DFS。 以上就是两种遍历方式的简单介绍,下面我们对两者进行一个详细的讲解。 深度优先遍历深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止,属于盲目搜索。 深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。因发明「深度优先搜索算法」,约翰 · 霍普克洛夫特与罗伯特 · 塔扬在 1986 年共同获得计算机领域的最高奖:图灵奖。 截止目前(2020-02-21),深度优先遍历在 LeetCode 中的题目是 129 道。在 LeetCode 中的题型绝对是超级大户了。而对于树的题目,我们基本上都可以使用 DFS 来解决,甚至我们可以基于 DFS 来做层次遍历,而且由于 DFS 可以基于递归去做,因此算法会更简洁。 在对性能有很高要求的场合,我建议你使用迭代,否则尽量使用递归,不仅写起来简单快速,还不容易出错。 DFS 图解: (图片来自 https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) 算法流程 首先将根节点放入stack中。 从stack中取出第一个节点,并检验它是否为目标。如果找到所有的节点,则结束搜寻并回传结果。否则将它某一个尚未检验过的直接子节点加入stack中。 重复步骤 2。 如果不存在未检测过的直接子节点。将上一级节点加入stack中。重复步骤 2。 重复步骤 4。 若stack为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。 这里的 stack 可以理解为自己实现的栈,也可以理解为调用栈。如果是调用栈的时候就是递归,如果是自己实现的栈的话就是迭代。 算法模板一个典型的通用的 DFS 模板可能是这样的: 12345678910111213const visited = {}function dfs(i) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } visited[i] = true // 将当前状态标为已搜索 for (根据i能到达的下个状态j) { if (!visited[j]) { // 如果状态j没有被搜索过 dfs(j) } }} 上面的 visited 是为了防止由于环的存在造成的死循环的。 而我们知道树是不存在环的,因此树的题目大多数不需要 visited,除非你对树的结构做了修改,比如就左子树的 left 指针指向自身,此时会有环。再比如 138. 复制带随机指针的链表 这道题需要记录已经复制的节点,这些需要记录 visited 信息的树的题目少之又少。 因此一个树的 DFS 更多是: 123456789function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } for (const child of root.children) { dfs(child) }} 而几乎所有的题目几乎都是二叉树,因此下面这个模板更常见。 1234567function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) dfs(root.right)} 而我们不同的题目除了 if (满足特定条件部分不同之外),还会写一些特有的逻辑,这些逻辑写的位置不同,效果也截然不同。那么位置不同会有什么影响,什么时候应该写哪里呢?接下来,我们就聊聊两种常见的 DFS 方式。 两种常见分类前序遍历和后序遍历是最常见的两种 DFS 方式。而另外一种遍历方式 (中序遍历)一般用于平衡二叉树,这个我们后面的四个重要概念部分再讲。 前序遍历如果你的代码大概是这么写的(注意主要逻辑的位置): 12345678function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } // 主要逻辑 dfs(root.left) dfs(root.right)} 那么此时我们称为前序遍历。 后续遍历而如果你的代码大概是这么写的(注意主要逻辑的位置): 12345678function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) dfs(root.right) // 主要逻辑} 那么此时我们称为后序遍历。 值得注意的是, 我们有时也会会写出这样的代码: 123456789function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } // 做一些事 dfs(root.left) dfs(root.right) // 做另外的事} 如上代码,我们在进入和退出左右子树的时候分别执行了一些代码。那么这个时候,是前序遍历还是后续遍历呢?实际上,这属于混合遍历了。不过我们这里只考虑主逻辑的位置,关键词是主逻辑。 如果代码主逻辑在左右子树之前执行,那么就是前序遍历。如果代码主逻辑在左右子树之后执行,那么就是后序遍历。关于更详细的内容, 我会在七个技巧 中的前后遍历部分讲解,大家先留个印象,知道有着两种方式就好。 递归遍历的学习技巧上面的《一个中心》部分,给大家介绍了一种干货技巧《双色遍历》统一三种遍历的迭代写法。 而树的遍历的递归的写法其实大多数人都没问题。为什么递归写的没问题,用栈写迭代就有问题呢? 本质上其实还是对递归的理解不够。那 lucifer 今天给大家介绍一种练习递归的技巧。其实文章开头也提到了,那就是画图 + 手动代入。有的同学不知道怎么画,这里我抛砖引玉分享一下我学习递归的画法。 比如我们要前序遍历一棵这样的树: 12345 1 / \\2 3 / \\ 4 5 图画的还算比较清楚, 就不多解释了。大家遇到题目多画几次这样的递归图,慢慢就对递归有感觉了。 广度优先遍历树的遍历的两种方式分别是 DFS 和 BFS,刚才的 DFS 我们简单过了一下前序和后序遍历,对它们有了一个简单印象。这一小节,我们来看下树的另外一种遍历方式 - BFS。 BFS 也是图论中算法的一种,不同于 DFS, BFS 采用横向搜索的方式,在数据结构上通常采用队列结构。 注意,DFS 我们借助的是栈来完成,而这里借助的是队列。 BFS 比较适合找最短距离/路径和某一个距离的目标。比如给定一个二叉树,在树的最后一行找到最左边的值。,此题是力扣 513 的原题。这不就是求距离根节点最远距离的目标么? 一个 BFS 模板就解决了。 BFS 图解: (图片来自 https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) 算法流程 首先将根节点放入队列中。 从队列中取出第一个节点,并检验它是否为目标。 如果找到目标,则结束搜索并回传结果。 否则将它所有尚未检验过的直接子节点加入队列中。 若队列为空,表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传“找不到目标”。 重复步骤 2。 算法模板12345678910111213141516const visited = {}function bfs() { let q = new Queue() q.push(初始状态) while(q.length) { let i = q.pop() if (visited[i]) continue if (i 是我们要找的目标) return 结果 for (i的可抵达状态j) { if (j 合法) { q.push(j) } } } return 没找到} 两种常见分类BFS 我目前使用的模板就两种,这两个模板可以解决所有的树的 BFS 问题。 前面我提到了“BFS 比较适合找最短距离/路径和某一个距离的目标”。 如果我需要求的是最短距离/路径,我是不关心我走到第几步的,这个时候可是用不标记层的目标。而如果我需要求距离某个节点距离等于 k 的所有节点,这个时候第几步这个信息就值得被记录了。 小于 k 或者 大于 k 也是同理。 标记层一个常见的 BFS 模板,代入题目只需要根据题目微调即可。 12345678910111213141516171819202122class Solution: def bfs(k): # 使用双端队列,而不是数组。因为数组从头部删除元素的时间复杂度为 N,双端队列的底层实现其实是链表。 queue = collections.deque([root]) # 记录层数 steps = 0 # 需要返回的节点 ans = [] # 队列不空,生命不止! while queue: size = len(queue) # 遍历当前层的所有节点 for _ in range(size): node = queue.popleft() if (step == k) ans.append(node) if node.right: queue.append(node.right) if node.left: queue.append(node.left) # 遍历完当前层所有的节点后 steps + 1 steps += 1 return ans 不标记层不带层的模板更简单,因此大家其实只需要掌握带层信息的目标就够了。 一个常见的 BFS 模板,代入题目只需要根据题目微调即可。 1234567891011121314class Solution: def bfs(k): # 使用双端队列,而不是数组。因为数组从头部删除元素的时间复杂度为 N,双端队列的底层实现其实是链表。 queue = collections.deque([root]) # 队列不空,生命不止! while queue: node = queue.popleft() # 由于没有记录 steps,因此我们肯定是不需要根据层的信息去判断的。否则就用带层的模板了。 if (node 是我们要找到的) return node if node.right: queue.append(node.right) if node.left: queue.append(node.left) return -1 以上就是 BFS 的两种基本方式,即带层和不带层,具体使用哪种看题目是否需要根据层信息做判断即可。 小结树的遍历是后面所有内容的基础,而树的遍历的两种方式 DFS 和 BFS 到这里就简单告一段落,现在大家只要知道 DFS 和 BFS 分别有两种常见的方式就够了,后面我会给大家详细补充。 三种题型树的题目就三种类型,分别是:搜索类,构建类和修改类,而这三类题型的比例也是逐渐降低的,即搜索类的题目最多,其次是构建类,最后是修改类。这一点和链表有很大的不同,链表更多的是修改类。 接下来,lucifer 给大家逐一讲解这三种题型。 搜索类搜索类的题目是树的题目的绝对大头。而搜索类只有两种解法,那就是 DFS 和 BFS,下面分别介绍。 几乎所有的搜索类题目都可以方便地使用递归来实现,关于递归的技巧会在七个技巧中的单/双递归部分讲解。还有一小部分使用递归不好实现,我们可以使用 BFS,借助队列轻松实现,比如最经典的是求二叉树任意两点的距离,树的距离其实就是最短距离,因此可以用 BFS 模板解决。这也是为啥我说DFS 和 BFS是树的题目的两个基本点的原因。 所有搜索类的题目只要把握三个核心点,即开始点,结束点 和 目标即可。 DFS 搜索DFS 搜索类的基本套路就是从入口开始做 dfs,然后在 dfs 内部判断是否是结束点,这个结束点通常是叶子节点或空节点,关于结束这个话题我们放在七个技巧中的边界部分介绍,如果目标是一个基本值(比如数字)直接返回或者使用一个全局变量记录即可,如果是一个数组,则可以通过扩展参数的技巧来完成,关于扩展参数,会在七个技巧中的参数扩展部分介绍。 这基本就是搜索问题的全部了,当你读完后面的七个技巧,回头再回来看这个会更清晰。 套路模板: 123456789101112131415161718192021222324252627# 其中 path 是树的路径, 如果需要就带上,不需要就不带def dfs(root, path): # 空节点 if not root: return # 叶子节点 if not root.left and not root.right: return path.append(root) # 逻辑可以写这里,此时是前序遍历 dfs(root.left) dfs(root.right) # 需要弹出,不然会错误计算。 # 比如对于如下树: \"\"\" 5 / \\ 4 8 / / \\ 11 13 4 / \\ / \\ 7 2 5 1 \"\"\" # 如果不 pop,那么 5 -> 4 -> 11 -> 2 这条路径会变成 5 -> 4 -> 11 -> 7 -> 2,其 7 被错误地添加到了 path path.pop() # 逻辑也可以写这里,此时是后序遍历 return 你想返回的数据 比如剑指 Offer 34. 二叉树中和为某一值的路径 这道题,题目是:输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。 这不就是从根节点开始,到叶子节点结束的所有路径搜索出来,挑选出和为目标值的路径么?这里的开始点是根节点, 结束点是叶子节点,目标就是路径。 对于求这种满足特定和的题目,我们都可以方便地使用前序遍历 + 参数扩展的形式,关于这个,我会在七个技巧中的前后序部分展开。 由于需要找到所有的路径,而不仅仅是一条,因此这里适合使用回溯暴力枚举。关于回溯,可以参考我的 回溯专题 12345678910111213141516171819202122class Solution: def pathSum(self, root: TreeNode, target: int) -> List[List[int]]: def backtrack(nodes, path, cur, remain): # 空节点 if not cur: return # 叶子节点 if cur and not cur.left and not cur.right: if remain == cur.val: nodes.append((path + [cur.val]).copy()) return # 选择 path.append(cur.val) # 递归左右子树 backtrack(nodes, path, cur.left, remain - cur.val) backtrack(nodes, path, cur.right, remain - cur.val) # 撤销选择 path.pop(-1) ans = [] # 入口,路径,目标值全部传进去,其中路径和path都是扩展的参数 backtrack(ans, [], root, target) return ans 再比如:1372. 二叉树中的最长交错路径,题目描述: 1234567891011给你一棵以 root 为根的二叉树,二叉树中的交错路径定义如下:选择二叉树中 任意 节点和一个方向(左或者右)。如果前进方向为右,那么移动到当前节点的的右子节点,否则移动到它的左子节点。改变前进方向:左变右或者右变左。重复第二步和第三步,直到你在树中无法继续移动。交错路径的长度定义为:访问过的节点数目 - 1(单个节点的路径长度为 0 )。请你返回给定树中最长 交错路径 的长度。比如: 12此时需要返回 3解释:蓝色节点为树中最长交错路径(右 -> 左 -> 右)。 这不就是从任意节点开始,到任意节点结束的所有交错路径全部搜索出来,挑选出最长的么?这里的开始点是树中的任意节点,结束点也是任意节点,目标就是最长的交错路径。 对于入口是任意节点的题目,我们都可以方便地使用双递归来完成,关于这个,我会在七个技巧中的单/双递归部分展开。 对于这种交错类的题目,一个好用的技巧是使用 -1 和 1 来记录方向,这样我们就可以通过乘以 -1 得到另外一个方向。 886. 可能的二分法 和 785. 判断二分图 都用了这个技巧。 用代码表示就是: 1next_direction = cur_direction * - 1 这里我们使用双递归即可解决。 如果题目限定了只从根节点开始,那就可以用单递归解决了。值得注意的是,这里内部递归需要 cache 一下 , 不然容易因为重复计算导致超时。 我的代码是 Python,这里的 lru_cache 就是一个缓存,大家可以使用自己语言的字典模拟实现。 12345678910111213class Solution: @lru_cache(None) def dfs(self, root, dir): if not root: return 0 if dir == -1: return int(root.left != None) + self.dfs(root.left, dir * -1) return int(root.right != None) + self.dfs(root.right, dir * -1) def longestZigZag(self, root: TreeNode) -> int: if not root: return 0 return max(self.dfs(root, 1), self.dfs(root, -1), self.longestZigZag(root.left), self.longestZigZag(root.right)) 这个代码不懂没关系,大家只有知道搜索类题目的大方向即可,具体做法我们后面会介绍,大家留个印象就行。更多的题目以及这些技巧的详细使用方式放在七个技巧部分展开。 BFS 搜索这种类型相比 DFS,题目数量明显降低,套路也少很多。题目大多是求距离,套用我上面的两种 BFS 模板基本都可以轻松解决,这个不多介绍了。 构建类除了搜索类,另外一个大头是构建类。构建类又分为两种:普通二叉树的构建和二叉搜索树的构建。 普通二叉树的构建而普通二叉树的构建又分为三种: 给你两种 DFS 的遍历的结果数组,让你构建出原始的树结构。比如根据先序遍历和后序遍历的数组,构造原始二叉树。这种题我在构造二叉树系列 系列里讲的很清楚了,大家可以去看看。 这种题目假设输入的遍历的序列中都不含重复的数字,想想这是为什么。 给你一个 BFS 的遍历的结果数组,让你构建出原始的树结构。 最经典的就是 剑指 Offer 37. 序列化二叉树。我们知道力扣的所有的树表示都是使用数字来表示的,而这个数组就是一棵树的层次遍历结果,部分叶子节点的子节点(空节点)也会被打印。比如:[1,2,3,null,null,4,5],就表示的是如下的一颗二叉树: 我们是如何根据这样的一个层次遍历结果构造出原始二叉树的呢?这其实就属于构造二叉树的内容,这个类型目前力扣就这一道题。这道题如果你彻底理解 BFS,那么就难不倒你。 还有一种是给你描述一种场景,让你构造一个符合条件的二叉树。这种题和上面的没啥区别,套路简直不要太像,比如 654. 最大二叉树,我就不多说了,大家通过这道题练习一下就知道了。 除了这种静态构建,还有一种很很罕见的动态构建二叉树的,比如 894. 所有可能的满二叉树 ,对于这个题,直接 BFS 就好了。由于这种题很少,因此不做多的介绍。大家只要把最核心的掌握了,这种东西自然水到渠成。 二叉搜索树的构建普通二叉树无法根据一种序列重构的原因是只知道根节点,无法区分左右子树。如果是二叉搜索树,那么就有可能根据一种遍历序列构造出来。 原因就在于二叉搜索树的根节点的值大于所有的左子树的值,且小于所有的右子树的值。因此我们可以根据这一特性去确定左右子树的位置,经过这样的转换就和上面的普通二叉树没有啥区别了。比如 1008. 前序遍历构造二叉搜索树 修改类上面介绍了两种常见的题型:搜索类和构建类。还有一种比例相对比较小的题目类型是修改类。 当然修改类的题目也是要基于搜索算法的,不找到目标怎么删呢? 修改类的题目有两种基本类型。 题目要求的修改一种是题目让你增加,删除节点,或者是修改节点的值或者指向。 修改指针的题目一般不难,比如 116. 填充每个节点的下一个右侧节点指针,这不就是 BFS 的时候顺便记录一下上一次访问的同层节点,然后增加一个指针不就行了么?关于 BFS ,套用我的带层的 BFS 模板就搞定了。 增加和删除的题目一般稍微复杂,比如 450. 删除二叉搜索树中的节点 和 669. 修剪二叉搜索树。西法我教你两个套路,面对这种问题就不带怕的。那就是后续遍历 + 虚拟节点,这两个技巧同样放在后面的七个技巧部分讲解。是不是对七个技巧很期待?^_^ 实际工程中,我们也可以不删除节点,而是给节点做一个标记,表示已经被删除了,这叫做软删除。 算法需要,自己修改另外一种是为了方便计算,自己加了一个指针。 比如 863. 二叉树中所有距离为 K 的结点 通过修改树的节点类,增加一个指向父节点的引用 parent,问题就转化为距离目标节点一定距离的问题了,此时可是用我上面讲的带层的 BFS 模板解决。 动态语言可以直接加属性(比如上面的 parent),而静态语言是不允许的,因此你需要增加一个新的类定义。不过你也可以使用字典来实现, key 是 node 引用, value 是你想记录的东西,比如这里的 parent 节点。 比如对于 Java 来说,我们可以: 12345678910class Solution { Map<TreeNode, TreeNode> parent; public void dfs(TreeNode node, TreeNode parent) { if (node != null) { parent.put(node, parent); dfs(node.left, node); dfs(node.right, node); } }} 简单回顾一下这一小节的知识。 接下来是做树的题目不得不知的四个重要概念。 四个重要概念二叉搜索树二叉搜索树(Binary Search Tree),亦称二叉查找树。 二叉搜索树具有下列性质的二叉树: 若左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若右子树不空,则右子树上所有节点的值均大于它的根节点的值; 左、右子树也分别为二叉排序树; 没有键值相等的节点。 对于一个二叉查找树,常规操作有插入,查找,删除,找父节点,求最大值,求最小值。 天生适合查找二叉查找树,之所以叫查找树就是因为其非常适合查找。 举个例子,如下一颗二叉查找树,我们想找节点值小于且最接近 58 的节点,搜索的流程如图所示: (图片来自 https://www.geeksforgeeks.org/floor-in-binary-search-tree-bst/) 可以看出每次向下走,都会排除了一个分支,如果一颗二叉搜索树同时也是一颗二叉平衡树的话,那么其搜索过程时间复杂度就是 $O(logN)$。实际上,平衡二叉搜索树的查找和有序数组的二分查找本质都是一样的,只是数据的存储方式不同罢了。那为什么有了有序数组二分,还需要二叉搜索树呢?原因在于树的结构对于动态数据比较友好,比如数据是频繁变动的,比如经常添加和删除,那么就可以使用二叉搜索树。理论上添加和删除的时间复杂度都是 $O(h)$,其中 h 为树的高度,如果是一颗平衡二叉搜索树,那么时间复杂度就是 $O(logN)$。而数组的添加和删除的时间复杂度为 $O(N)$,其中 N 为数组长度。 方便搜索,是二叉搜索树核心的设计初衷。不让查找算法时间复杂度退化到线性是平衡二叉树的初衷。 我们平时说的二分很多是数组的二分,因为数组可以随机访问嘛。不过这种二分实在太狭义了,二分的本质是将问题规模缩小到一半,因此二分和数据结构没有本质关系,但是不同的数据结构却给二分赋予了不同的色彩。比如跳表就是链表的二分,二叉搜索树就是树的二分等。随着大家对算法和数据结构的了解的加深,会发现更多有意思的东西^_^ 中序遍历是有序的另外二叉查找树有一个性质,这个性质对于做题很多帮助,那就是: 二叉搜索树的中序遍历的结果是一个有序数组。 比如 98. 验证二叉搜索树 就可以直接中序遍历,并一边遍历一边判断遍历结果是否是单调递增的,如果不是则提前返回 False 即可。 再比如 99. 恢复二叉搜索树,官方难度为困难。题目大意是给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。 我们可以先中序遍历发现不是递增的节点,他们就是被错误交换的节点,然后交换恢复即可。这道题难点就在于一点,即错误交换可能错误交换了中序遍历的相邻节点或者中序遍历的非相邻节点,这是两种 case,需要分别讨论。 类似的题目很多,不再赘述。练习的话大家可以做一下这几道题。 94. 二叉树的中序遍历 98. 验证二叉搜索树 173. 二叉搜索树迭代器 250. 统计同值子树 大家如果碰到二叉搜索树的搜索类题目,一定先想下能不能利用这个性质来做。 完全二叉树一棵深度为 k 的有 n 个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为 i(1≤i≤n)的结点与满二叉树中编号为 i 的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。 如下就是一颗完全二叉树: 直接考察完全二叉树的题目虽然不多,貌似只有一道 222. 完全二叉树的节点个数(二分可解),但是理解完全二叉树对你做题其实帮助很大。 如上图,是一颗普通的二叉树。如果我将其中的空节点补充完全,那么它就是一颗完全二叉树了。 这有什么用呢?这很有用!我总结了两个用处: 我们可以给完全二叉树编号,这样父子之间就可以通过编号轻松求出。比如我给所有节点从左到右从上到下依次从 1 开始编号。那么已知一个节点的编号是 i,那么其左子节点就是 2 _ i,右子节点就是 2 _ 1 + 1,父节点就是 (i + 1) / 2。 熟悉二叉堆的同学可能发现了,这就是用数组实现的二叉堆,其实二叉堆就是完全二叉树的一个应用。 有的同学会说,”但是很多题目都不是完全二叉树呀,那不是用不上了么?“其实不然,我们只要想象它存在即可,我们将空节点脑补上去不就可以了?比如 662. 二叉树最大宽度。题目描述: 12345678910111213141516给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。示例 1:输入: 1 / \\ 3 2 / \\ \\ 5 3 9输出: 4解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。 很简单,一个带层的 BFS 模板即可搞定,简直就是默写题。不过这里需要注意两点: 入队的时候除了要将普通节点入队,还要空节点入队。 出队的时候除了入队节点本身,还要将节点的位置信息入队,即下方代码的 pos。 参考代码: 12345678910111213141516171819202122232425262728# Definition for a binary tree node.# class TreeNode:# def __init__(self, x):# self.val = x# self.left = None# self.right = Noneclass Solution: def widthOfBinaryTree(self, root: TreeNode) -> int: q = collections.deque([(root, 0)]) steps = 0 cur_depth = leftmost = ans = 0 while q: for _ in range(len(q)): node, pos = q.popleft() if node: # 节点编号关关系是不是用上了? q.append((node.left, pos * 2)) q.append((node.right, pos * 2 + 1)) # 逻辑开始 if cur_depth != steps: cur_depth = steps leftmost = pos ans = max(ans, pos - leftmost + 1) # 逻辑结束 steps += 1 return ans 再比如剑指 Offer 37. 序列化二叉树。如果我将一个二叉树的完全二叉树形式序列化,然后通过 BFS 反序列化,这不就是力扣官方序列化树的方式么?比如: 12345 1 / \\2 3 / \\ 4 5 序列化为 “[1,2,3,null,null,4,5]”。 这不就是我刚刚画的完全二叉树么?就是将一个普通的二叉树硬生生当成完全二叉树用了。 其实这并不是序列化成了完全二叉树,下面会纠正。 将一颗普通树序列化为完全二叉树很简单,只要将空节点当成普通节点入队处理即可。代码: 12345678910111213141516class Codec: def serialize(self, root): q = collections.deque([root]) ans = '' while q: cur = q.popleft() if cur: ans += str(cur.val) + ',' q.append(cur.left) q.append(cur.right) else: # 除了这里不一样,其他和普通的不记录层的 BFS 没区别 ans += 'null,' # 末尾会多一个逗号,我们去掉它。 return ans[:-1] 细心的同学可能会发现,我上面的代码其实并不是将树序列化成了完全二叉树,这个我们稍后就会讲到。另外后面多余的空节点也一并序列化了。这其实是可以优化的,优化的方式也很简单,那就是去除末尾的 null 即可。 你只要彻底理解我刚才讲的我们可以给完全二叉树编号,这样父子之间就可以通过编号轻松求出。比如我给所有节点从左到右从上到下依次从 1 开始编号。那么已知一个节点的编号是 i,那么其左子节点就是 2 * i,右子节点就是 2 * i + 1,父节点就是 i / 2。 这句话,那么反序列化对你就不是难事。 如果我用一个箭头表示节点的父子关系,箭头指向节点的两个子节点,那么大概是这样的: 我们刚才提到了: 1 号节点的两个子节点的 2 号 和 3 号。 2 号节点的两个子节点的 4 号 和 5 号。 。。。 i 号节点的两个子节点的 2 * i 号 和 2 * i + 1 号。 此时你可能会写出类似这样的代码: 1234567891011121314151617181920212223def deserialize(self, data): if data == 'null': return None nodes = data.split(',') root = TreeNode(nodes[0]) # 从一号开始编号,编号信息一起入队 q = collections.deque([(root, 1)]) while q: cur, i = q.popleft() # 2 * i 是左节点,而 2 * i 编号对应的其实是索引为 2 * i - 1 的元素, 右节点同理。 if 2 * i - 1 < len(nodes): lv = nodes[2 * i - 1] if 2 * i < len(nodes): rv = nodes[2 * i] if lv != 'null': l = TreeNode(lv) # 将左节点和 它的编号 2 * i 入队 q.append((l, 2 * i)) cur.left = l if rv != 'null': r = TreeNode(rv) # 将右节点和 它的编号 2 * i + 1 入队 q.append((r, 2 * i + 1)) cur.right = r return root 但是上面的代码是不对的,因为我们序列化的时候其实不是完全二叉树,这也是上面我埋下的伏笔。因此遇到类似这样的 case 就会挂: 这也是我前面说”上面代码的序列化并不是一颗完全二叉树“的原因。 其实这个很好解决, 核心还是上面我画的那种图: 其实我们可以: 用三个指针分别指向数组第一项,第二项和第三项(如果存在的话),这里用 p1,p2,p3 来标记,分别表示当前处理的节点,当前处理的节点的左子节点和当前处理的节点的右子节点。 p1 每次移动一位,p2 和 p3 每次移动两位。 p1.left = p2; p1.right = p3。 持续上面的步骤直到 p1 移动到最后。 因此代码就不难写出了。反序列化代码如下: 123456789101112131415161718192021def deserialize(self, data): if data == 'null': return None nodes = data.split(',') root = TreeNode(nodes[0]) q = collections.deque([root]) i = 0 while q and i < len(nodes) - 2: cur = q.popleft() lv = nodes[i + 1] rv = nodes[i + 2] i += 2 if lv != 'null': l = TreeNode(lv) q.append(l) cur.left = l if rv != 'null': r = TreeNode(rv) q.append(r) cur.right = r return root 这个题目虽然并不是完全二叉树的题目,但是却和完全二叉树很像,有借鉴完全二叉树的地方。 路径关于路径这个概念,leetcode 真的挺喜欢考察的,不信你自己去 leetcode 官网搜索一下路径,看有多少题。树的路径这种题目的变种很多,算是一种经典的考点了。 要明白路径的概念,以及如何解决这种题,只需要看一个题目就好了 124.二叉树中的最大路径和,虽然是困难难度,但是搞清楚概念的话,和简单难度没啥区别。 接下来,我们就以这道题讲解一下。 这道题的题目是 给定一个非空二叉树,返回其最大路径和。路径的概念是:一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。这听起来真的不容易理解,力扣给的 demo 我也没搞懂,这里我自己画了几个图来给大家解释一下这个概念。 首先是官网给的两个例子: 接着是我自己画的一个例子: 如图红色的部分是最大路径上的节点。 可以看出: 路径可以由一个节点做成,可以由两个节点组成,也可以由三个节点组成等等,但是必须连续。 路径必须是”直来直去“的,不能有分叉。 比如上图的路径的左下角是 3,当然也可以是 2,但是 2 比较小。但是不可以 2 和 3 同时选。 我们继续回到 124 题。题目说是 ”从任意节点出发…….“ 看完这个描述我会想到大概率是要么全局记录最大值,要么双递归。 如果使用双递归,那么复杂度就是 $O(N^2)$,实际上,子树的路径和计算出来了,可以推导出父节点的最大路径和,因此如果使用双递归会有重复计算。一个可行的方式是记忆化递归。 如果使用全局记录最大值,只需要在递归的时候 return 当前的一条边(上面提了不能拐),并在函数内部计算以当前节点出发的最大路径和,并更新全局最大值即可。 这里的核心其实是 return 较大的一条边,因为较小的边不可能是答案。 这里我选择使用第二种方法。 代码: 12345678910111213class Solution: ans = float('-inf') def maxPathSum(self, root: TreeNode) -> int: def dfs(node): if not node: return 0 l = dfs(node.left) r = dfs(node.right) # 选择当前的节点,并选择左右两边,当然左右两边也可以不选。必要时更新全局最大值 self.ans = max(self.ans, max(l,0) + max(r, 0) + node.val) # 只返回一边,因此我们挑大的返回。当然左右两边也可以不选 return max(l, r, 0) + node.val dfs(root) return self.ans 类似题目 113. 路径总和 I 距离和路径类似,距离也是一个相似且频繁出现的一个考点,并且二者都是搜索类题目的考点。原因就在于最短路径就是距离,而树的最短路径就是边的数目。 这两个题练习一下,碰到距离的题目基本就稳了。 834.树中距离之和 863.二叉树中所有距离为 K 的结点 七个技巧上面数次提到了七个技巧,相信大家已经迫不及待想要看看这七个技巧了吧。那就让我拿出本章压箱底的内容吧~ 注意,这七个技巧全部是基于 dfs 的,bfs 掌握了模板就行,基本没有什么技巧可言。 认真学习的小伙伴可以发现了, 上面的内容只有二叉树的迭代写法(双色标记法) 和 两个 BFS 模板 具有实操性,其他大多是战略思想上的。算法思想固然重要,但是要结合具体实践落地才能有实践价值,才能让我们把知识消化成自己的。而这一节满满的全是实用干货ヽ( ̄ ω  ̄( ̄ ω  ̄〃)ゝ。 dfs(root)第一个技巧,也是最容易掌握的一个技巧。我们写力扣的树题目的时候,函数的入参全都是叫 root。而这个技巧是说,我们在写 dfs 函数的时候,要将函数中表示当前节点的形参也写成 root。即: 12def dfs(root): # your code 而之前我一直习惯写成 node,即: 12def dfs(node): # your code 可能有的同学想问:” 这有什么关系么?“。我总结了两个原因。 第一个原因是:以前 dfs 的形参写的是 node, 而我经常误写成 root,导致出错(这个错误并不会抛错,因此不是特别容易发现)。自从换成了 root 就没有发生这样的问题了。 第二个原因是:这样写相当于把 root 当成是 current 指针来用了。最开始 current 指针指向 root,然后不断修改指向树的其它节点。这样就概念就简化了,只有一个当前指针的概念。如果使用 node,就是当前指针 + root 指针两个概念了。 (一开始 current 就是 root) (后面 current 不断改变。具体如何改变,取决于你的搜索算法,是 dfs 还是 bfs 等) 单/双递归上面的技巧稍显简单,但是却有用。这里介绍一个稍微难一点的技巧,也更加有用。 我们知道递归是一个很有用的编程技巧,灵活使用递归,可以使自己的代码更加简洁,简洁意味着代码不容易出错,即使出错了,也能及时发现问题并修复。 树的题目大多数都可以用递归轻松地解决。如果一个递归不行,那么来两个。(至今没见过三递归或更多递归) 单递归大家写的比较多了,其实本篇文章的大部分递归都是单递归。 那什么时候需要两个递归呢?其实我上面已经提到了,那就是如果题目有类似,任意节点开始 xxxx 或者所有 xxx这样的说法,就可以考虑使用双递归。但是如果递归中有重复计算,则可以使用双递归 + 记忆化 或者直接单递归。 比如 面试题 04.12. 求和路径,再比如 563.二叉树的坡度 这两道题的题目说法都可以考虑使用双递归求解。 双递归的基本套路就是一个主递归函数和一个内部递归函数。主递归函数负责计算以某一个节点开始的 xxxx,内部递归函数负责计算 xxxx,这样就实现了以所有节点开始的 xxxx。 其中 xxx 可以替换成任何题目描述,比如路径和等 一个典型的加法双递归是这样的: 1234567def dfs_inner(root): # 这里写你的逻辑,就是前序遍历 dfs_inner(root.left) dfs_inner(root.right) # 或者在这里写你的逻辑,那就是后序遍历def dfs_main(root): return dfs_inner(root) + dfs_main(root.left) + dfs_main(root.right) 大家可以用我的模板去套一下上面两道题试试。 前后遍历前面我的链表专题也提到了前后序遍历。由于链表只有一个 next 指针,因此只有两种遍历。而二叉树有两个指针,因此常见的遍历有三个,除了前后序,还有一个中序。而中序除了二叉搜索树,其他地方用的并不多。 和链表一样, 要掌握树的前后序,也只需要记住一句话就好了。那就是如果是前序遍历,那么你可以想象上面的节点都处理好了,怎么处理的不用管。相应地如果是后序遍历,那么你可以想象下面的树都处理好了,怎么处理的不用管。这句话的正确性也是毋庸置疑。 前后序对链表来说比较直观。对于树来说,其实更形象地说应该是自顶向下或者自底向上。自顶向下和自底向上在算法上是不同的,不同的写法有时候对应不同的书写难度。比如 https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/,这种题目就适合通过参数扩展 + 前序来完成。 关于参数扩展的技巧,我们在后面展开。 自顶向下就是在每个递归层级,首先访问节点来计算一些值,并在递归调用函数时将这些值传递到子节点,一般是通过参数传到子树中。 自底向上是另一种常见的递归方法,首先对所有子节点递归地调用函数,然后根据返回值和根节点本身的值得到答案。 关于前后序的思维技巧,可以参考我的这个文章 的前后序部分。 总结下我的经验: 大多数树的题使用后序遍历比较简单,并且大多需要依赖左右子树的返回值。比如 1448. 统计二叉树中好节点的数目 不多的问题需要前序遍历,而前序遍历通常要结合参数扩展技巧。比如 1022. 从根到叶的二进制数之和 如果你能使用参数和节点本身的值来决定什么应该是传递给它子节点的参数,那就用前序遍历。 如果对于树中的任意一个节点,如果你知道它子节点的答案,你能计算出当前节点的答案,那就用后序遍历。 如果遇到二叉搜索树则考虑中序遍历 虚拟节点是的!不仅仅链表有虚拟节点的技巧,树也是一样。关于这点大家可能比较容易忽视。 回忆一下链表的虚拟指针的技巧,我们通常在什么时候才会使用? 其中一种情况是链表的头会被修改。这个时候通常需要一个虚拟指针来做新的头指针,这样就不需要考虑第一个指针的问题了(因为此时第一个指针变成了我们的虚拟指针,而虚拟指针是不用参与题目运算的)。树也是一样,当你需要对树的头节点(在树中我们称之为根节点)进行修改的时候, 就可以考虑使用虚拟指针的技巧了。 另外一种是题目需要返回树中间的某个节点(不是返回根节点)。实际上也可借助虚拟节点。由于我上面提到的指针的操作,实际上,你可以新建一个虚拟头,然后让虚拟头在恰当的时候(刚好指向需要返回的节点)断开连接,这样我们就可以返回虚拟头的 next 就 ok 了。 更多关于虚拟指针的技巧可以参考这个文章 的虚拟头部分。 下面就力扣中的两道题来看一下。 【题目一】814. 二叉树剪枝题目描述: 12345678910111213给定二叉树根结点 root ,此外树的每个结点的值要么是 0,要么是 1。返回移除了所有不包含 1 的子树的原二叉树。( 节点 X 的子树为 X 本身,以及所有 X 的后代。)示例1:输入: [1,null,0,0,1]输出: [1,null,0,null,1]解释:只有红色节点满足条件“所有不包含 1 的子树”。右图为返回的答案。 12345示例2:输入: [1,0,1,0,0,0,1]输出: [1,null,1,null,1] 123示例3:输入: [1,1,0,1,1,0,1,0]输出: [1,1,0,1,1,null,1] 1234说明:给定的二叉树最多有 100 个节点。每个节点的值只会为 0 或 1 。 根据题目描述不难看出, 我们的根节点可能会被整个移除掉。这就是我上面说的根节点被修改的情况。 这个时候,我们只要新建一个虚拟节点当做新的根节点,就不需要考虑这个问题了。 此时的代码是这样的: 123456789var pruneTree = function (root) { function dfs(root) { // do something } ans = new TreeNode(-1); ans.left = root; dfs(ans); return ans.left;}; 接下来,只需要完善 dfs 框架即可。 dfs 框架也很容易,我们只需要将子树和为 0 的节点移除即可,而计算子树和是一个难度为 easy 的题目,只需要后序遍历一次并收集值即可。 计算子树和的代码如下: 123456function dfs(root) { if (!root) return 0; const l = dfs(root.left); const r = dfs(root.right); return root.val + l + r;} 有了上面的铺垫,最终代码就不难写出了。 完整代码(JS): 1234567891011121314var pruneTree = function (root) { function dfs(root) { if (!root) return 0; const l = dfs(root.left); const r = dfs(root.right); if (l == 0) root.left = null; if (r == 0) root.right = null; return root.val + l + r; } ans = new TreeNode(-1); ans.left = root; dfs(ans); return ans.left;}; 【题目一】1325. 删除给定值的叶子节点题目描述: 123456789给你一棵以 root 为根的二叉树和一个整数 target ,请你删除所有值为 target 的 叶子节点 。注意,一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。也就是说,你需要重复此过程直到不能继续删除。 示例 1: 12345678输入:root = [1,2,3,2,null,2,4], target = 2输出:[1,null,3,null,4]解释:上面左边的图中,绿色节点为叶子节点,且它们的值与 target 相同(同为 2 ),它们会被删除,得到中间的图。有一个新的节点变成了叶子节点且它的值与 target 相同,所以将再次进行删除,从而得到最右边的图。示例 2: 12345输入:root = [1,3,3,3,2], target = 3输出:[1,3,null,null,2]示例 3: 1234567891011121314151617181920输入:root = [1,2,null,2,null,2], target = 2输出:[1]解释:每一步都删除一个绿色的叶子节点(值为 2)。示例 4:输入:root = [1,1,1], target = 1输出:[]示例 5:输入:root = [1,2,3], target = 1输出:[1,2,3] 提示:1 <= target <= 1000每一棵树最多有 3000 个节点。每一个节点值的范围是 [1, 1000] 。 和上面题目类似,这道题的根节点也可能被删除,因此这里我们采取和上面题目类似的技巧。 由于题目说明了一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。也就是说,你需要重复此过程直到不能继续删除。 因此这里使用后序遍历会比较容易,因为形象地看上面的描述过程你会发现这是一个自底向上的过程,而自底向上通常用后序遍历。 上面的题目,我们可以根据子节点的返回值决定是否删除子节点。而这道题是根据左右子树是否为空,删除自己,关键字是自己。而树的删除和链表删除类似,树的删除需要父节点,因此这里的技巧和链表类似,记录一下当前节点的父节点即可,并通过参数扩展向下传递。至此,我们的代码大概是: 123456789class Solution: def removeLeafNodes(self, root: TreeNode, target: int) -> TreeNode: # 单链表只有一个 next 指针,而二叉树有两个指针 left 和 right,因此要记录一下当前节点是其父节点的哪个孩子 def dfs(node, parent, is_left=True): # do something ans = TreeNode(-1) ans.left = root dfs(root, ans) return ans.left 有了上面的铺垫,最终代码就不难写出了。 完整代码(Python): 12345678910111213class Solution: def removeLeafNodes(self, root: TreeNode, target: int) -> TreeNode: def dfs(node, parent, is_left=True): if not node: return dfs(node.left, node, True) dfs(node.right, node, False) if node.val == target and parent and not node.left and not node.right: if is_left: parent.left = None else: parent.right = None ans = TreeNode(-1) ans.left = root dfs(root, ans) return ans.left 边界发现自己老是边界考虑不到,首先要知道这是正常的,人类的本能。 大家要克服这种本能, 只有多做,慢慢就能克服。 就像改一个坏习惯一样,除了坚持,一个有用的技巧是奖励和惩罚,我也用过这个技巧。 上面我介绍了树的三种题型。对于不同的题型其实边界考虑的侧重点也是不一样的,下面我们展开聊聊。 搜索类搜索类的题目,树的边界其实比较简单。 90% 以上的题目边界就两种情况。 树的题目绝大多树又是搜索类,你想想掌握这两种情况多重要。 空节点 伪代码: 123def dfs(root): if not root: print('是空节点,你需要返回合适的值') # your code here` 叶子节点 伪代码: 1234def dfs(root): if not root: print('是空节点,你需要返回合适的值') if not root.left and not root.right: print('是叶子节点,你需要返回合适的值')# your code here` 一张图总结一下: 经过这样的处理,后面的代码基本都不需要判空了。 构建类相比于搜索类, 构建就比较麻烦了。我总结了两个常见的边界。 参数扩展的边界 比如 1008 题, 根据前序遍历构造二叉搜索树。我就少考虑的边界。 1234567891011121314151617181920def bstFromPreorder(self, preorder: List[int]) -> TreeNode: def dfs(start, end): if start > end: return None if start == end: return TreeNode(preorder[start]) root = TreeNode(preorder[start]) mid = -1 for i in range(start + 1, end + 1): if preorder[i] > preorder[start]: mid = i break if mid == -1: root.left = dfs(start + 1, end) else: root.left = dfs(start + 1, mid - 1) root.right = dfs(mid, end) return root return dfs(0, len(preorder) - 1) 注意上面的代码没有判断 start == end 的情况,加下面这个判断就好了。 1if start == end: return TreeNode(preorder[start]) 虚拟节点 除了搜索类的技巧可以用于构建类外,也可以考虑用我上面的讲的虚拟节点。 参数扩展大法参数扩展这个技巧非常好用,一旦掌握你会爱不释手。 如果不考虑参数扩展, 一个最简单的 dfs 通常是下面这样: 12def dfs(root): # do something 而有时候,我们需要 dfs 携带更多的有用信息。典型的有以下三种情况: 携带父亲或者爷爷的信息。 1234def dfs(root, parent): if not root: return dfs(root.left, root) dfs(root.right, root) 携带路径信息,可以是路径和或者具体的路径数组等。 路径和: 123456def dfs(root, path_sum): if not root: # 这里可以拿到根到叶子的路径和 return path_sum dfs(root.left, path_sum + root.val) dfs(root.right, path_sum + root.val) 路径: 123456789def dfs(root, path): if not root: # 这里可以拿到根到叶子的路径 return path path.append(root.val) dfs(root.left, path) dfs(root.right, path) # 撤销 path.pop() 学会了这个技巧,大家可以用 面试题 04.12. 求和路径 来练练手。 以上几个模板都很常见,类似的场景还有很多。总之当你需要传递额外信息给子节点(关键字是子节点)的时候,请务必掌握这种技巧。这也解释了为啥参数扩展经常用于前序遍历。 二叉搜索树的搜索题大多数都需要扩展参考,甚至怎么扩展都是固定的。 二叉搜索树的搜索总是将最大值和最小值通过参数传递到左右子树,类似 dfs(root, lower, upper),然后在递归过程更新最大和最小值即可。这里需要注意的是 (lower, upper) 是的一个左右都开放的区间。 比如有一个题783. 二叉搜索树节点最小距离是求二叉搜索树的最小差值的绝对值。当然这道题也可以用我们前面提到的二叉搜索树的中序遍历的结果是一个有序数组这个性质来做。只需要一次遍历,最小差一定出现在相邻的两个节点之间。 这里我用另外一种方法,该方法就是扩展参数大法中的 左右边界法。 12345678910class Solution:def minDiffInBST(self, root): def dfs(node, lower, upper): if not node: return upper - lower left = dfs(node.left, lower, node.val) right = dfs(node.right, node.val, upper) # 要么在左,要么在右,不可能横跨(因为是 BST) return min(left, right) return dfs(root, float('-inf'), float('inf') 其实这个技巧不仅适用二叉搜索树,也可是适用在别的树,比如 1026. 节点与其祖先之间的最大差值,题目大意是:给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。 使用类似上面的套路轻松求解。 12345678class Solution:def maxAncestorDiff(self, root: TreeNode) -> int: def dfs(root, lower, upper): if not root: return upper - lower # 要么在左,要么在右,要么横跨。 return max(dfs(root.left, min(root.val, lower), max(root.val, upper)), dfs(root.right, min(root.val, lower), max(root.val, upper))) return dfs(root, float('inf'), float('-inf')) 返回元组/列表通常,我们的 dfs 函数的返回值是一个单值。而有时候为了方便计算,我们会返回一个数组或者元祖。 对于个数固定情况,我们一般使用元组,当然返回数组也是一样的。 这个技巧和参数扩展有异曲同工之妙,只不过一个作用于函数参数,一个作用于函数返回值。 返回元祖返回元组的情况还算比较常见。比如 865. 具有所有最深节点的最小子树,一个简单的想法是 dfs 返回深度,我们通过比较左右子树的深度来定位答案(最深的节点位置)。 代码: 123456789class Solution: def subtreeWithAllDeepest(self, root: TreeNode) -> int: def dfs(node, d): if not node: return d l_d = dfs(node.left, d + 1) r_d = dfs(node.right, d + 1) if l_d >= r_d: return l_d return r_d return dfs(root, -1) 但是题目要求返回的是树节点的引用啊,这个时候应该考虑返回元祖,即除了返回深度,也要把节点给返回。 12345678910class Solution: def subtreeWithAllDeepest(self, root: TreeNode) -> TreeNode: def dfs(node, d): if not node: return (node, d) l, l_d = dfs(node.left, d + 1) r, r_d = dfs(node.right, d + 1) if l_d == r_d: return (node, l_d) if l_d > r_d: return (l, l_d) return (r, r_d) return dfs(root, -1)[0] 返回数组dfs 返回数组比较少见。即使题目要求返回数组,我们也通常是声明一个数组,在 dfs 过程不断 push,最终返回这个数组。而不会选择返回一个数组。绝大多数情况下,返回数组是用于计算笛卡尔积。因此你需要用到笛卡尔积的时候,考虑使用返回数组的方式。 一般来说,如果需要使用笛卡尔积的情况还是比较容易看出的。另外一个不太准确的技巧是,如果题目有”所有可能“,”所有情况“,可以考虑使用此技巧。 一个典型的题目是 1530.好叶子节点对的数量 题目描述: 123456789给你二叉树的根节点 root 和一个整数 distance 。如果二叉树中两个叶节点之间的 最短路径长度 小于或者等于 distance ,那它们就可以构成一组 好叶子节点对 。返回树中 好叶子节点对的数量 。 示例 1: 12345678 输入:root = [1,2,3,null,4], distance = 3输出:1解释:树的叶节点是 3 和 4 ,它们之间的最短路径的长度是 3 。这是唯一的好叶子节点对。示例 2: 123456789101112131415161718192021222324输入:root = [1,2,3,4,5,6,7], distance = 3输出:2解释:好叶子节点对为 [4,5] 和 [6,7] ,最短路径长度都是 2 。但是叶子节点对 [4,6] 不满足要求,因为它们之间的最短路径长度为 4 。示例 3:输入:root = [7,1,4,6,null,5,3,null,null,null,null,null,2], distance = 3输出:1解释:唯一的好叶子节点对是 [2,5] 。示例 4:输入:root = [100], distance = 1输出:0示例 5:输入:root = [1,1,1], distance = 2输出:1 提示:tree 的节点数在 [1, 2^10] 范围内。每个节点的值都在 [1, 100] 之间。1 <= distance <= 10 上面我们学习了路径的概念,在这道题又用上了。 其实两个叶子节点的最短路径(距离)可以用其最近的公共祖先来辅助计算。即两个叶子节点的最短路径 = 其中一个叶子节点到最近公共祖先的距离 + 另外一个叶子节点到最近公共祖先的距离。 因此我们可以定义 dfs(root),其功能是计算以 root 作为出发点,到其各个叶子节点的距离。 如果其子节点有 8 个叶子节点,那么就返回一个长度为 8 的数组, 数组每一项的值就是其到对应叶子节点的距离。 如果子树的结果计算出来了,那么父节点只需要把子树的每一项加 1 即可。这点不难理解,因为父到各个叶子节点的距离就是父节点到子节点的距离(1) + 子节点到各个叶子节点的距离。 由上面的推导可知需要先计算子树的信息,因此我们选择前序遍历。 完整代码(Python): 12345678910111213141516171819class Solution: def countPairs(self, root: TreeNode, distance: int) -> int: self.ans = 0 def dfs(root): if not root: return [] if not root.left and not root.right: return [0] ls = [l + 1 for l in dfs(root.left)] rs = [r + 1 for r in dfs(root.right)] # 笛卡尔积 for l in ls: for r in rs: if l + r <= distance: self.ans += 1 return ls + rs dfs(root) return self.ans 894. 所有可能的满二叉树 也是一样的套路,大家用上面的知识练下手吧~ 经典题目推荐大家先把本文提到的题目都做一遍,然后用本文学到的知识做一下下面十道练习题,检验一下自己的学习成果吧! 剑指 Offer 55 - I. 二叉树的深度 剑指 Offer 34. 二叉树中和为某一值的路径 101. 对称二叉树 226. 翻转二叉树 543. 二叉树的直径 662. 二叉树最大宽度 971. 翻转二叉树以匹配先序遍历 987. 二叉树的垂序遍历 863. 二叉树中所有距离为 K 的结点 面试题 04.06. 后继者 总结树的题目一种中心点就是遍历,这是搜索问题和修改问题的基础。 而遍历从大的方向分为广度优先遍历和深度优先遍历,这就是我们的两个基本点。两个基本点可以进一步细分,比如广度优先遍历有带层信息的和不带层信息的(其实只要会带层信息的就够了)。深度优先遍历常见的是前序和后序,中序多用于二叉搜索树,因为二叉搜索树的中序遍历是严格递增的数组。 树的题目从大的方向上来看就三种,一种是搜索类,这类题目最多,这种题目牢牢把握开始点,结束点 和 目标即可。构建类型的题目我之前的专题以及讲过了,一句话概括就是根据一种遍历结果确定根节点位置,根据另外一种遍历结果(如果是二叉搜索树就不需要了)确定左右子树。修改类题目不多,这种问题边界需要特殊考虑,这是和搜索问题的本质区别,可以使用虚拟节点技巧。另外搜索问题,如果返回值不是根节点也可以考虑虚拟节点。 树有四个比较重要的对做题帮助很大的概念,分别是完全二叉树,二叉搜索树,路径和距离,这里面相关的题目推荐大家好好做一下,都很经典。 最后我给大家介绍了七种干货技巧,很多技巧都说明了在什么情况下可以使用。好不好用你自己去找几个题目试试就知道了。 以上就是树专题的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 我整理的 1000 多页的电子书已经开发下载了,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"树","slug":"树","permalink":"https://lucifer.ren/blog/categories/树/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"树","slug":"树","permalink":"https://lucifer.ren/blog/tags/树/"}]},{"title":"浏览器事件模型","slug":"browser-event","date":"2020-11-14T16:00:00.000Z","updated":"2023-01-05T12:24:49.905Z","comments":true,"path":"2020/11/15/browser-event/","link":"","permalink":"https://lucifer.ren/blog/2020/11/15/browser-event/","excerpt":"我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢?为什么 NodeJS 也是事件驱动的 ? 两者是一回事么? 实际上不管是浏览器还是 Nodejs 都是事件驱动的,都有自己的事件模型。在这里,我们只讲解浏览器端的事件模型,如果对 Nodejs 事件模型感兴趣的,请期待我的 Nodejs 部分的讲解。","text":"我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢?为什么 NodeJS 也是事件驱动的 ? 两者是一回事么? 实际上不管是浏览器还是 Nodejs 都是事件驱动的,都有自己的事件模型。在这里,我们只讲解浏览器端的事件模型,如果对 Nodejs 事件模型感兴趣的,请期待我的 Nodejs 部分的讲解。 什么是事件驱动事件驱动通俗地来说就是什么都抽象为事件。 一次点击是一个事件 键盘按下是一个事件 一个网络请求成功是一个事件 页面加载是一个事件 页面报错是一个事件 … 浏览器依靠事件来驱动 APP 运行下去,如果没有了事件驱动,那么 APP 会直接从头到尾运行完,然后结束,事件驱动是浏览器的基石。 本篇文章不讲解事件循环的内容,事件循环部分会在本章的其他章节讲解,敬请期待。 一个简单的例子其实现实中的红绿灯就是一种事件,它告诉我们现在是红灯状态,绿灯状态,还是黄灯状态。 我们需要根据这个事件自己去完成一些操作,比如红灯和黄灯我们需要等待,绿灯我们可以过马路。 下面我们来看一个最简单的浏览器端的事件: html 代码: 1<button>Change color</button> js 代码: 12345var btn = document.querySelector(\"button\");btn.onclick = function () { console.log(\"button clicked\");}; 代码很简单,我们在 button 上注册了一个事件,这个事件的 handler 是一个我们定义的匿名函数。当用户点击了这个被注册了事件的 button 的时候,这个我们定义好的匿名函数就会被执行。 如何绑定事件我们有三种方法可以绑定事件,分别是行内绑定,直接赋值,用addEventListener。 内联 这个方法非常不推荐 html 代码: 1<button onclick=\"handleClick()\">Press me</button> 然后在 script 标签内写: 123function handleClick() { console.log(\"button clicked\");} 直接赋值 和我上面举的例子一样: 12345var btn = document.querySelector(\"button\");btn.onclick = function () { console.log(\"button clicked\");}; 这种方法有两个缺点 不能添加多个同类型的 handler 12btn.onclick = functionA;btn.onclick = functionB; 这样只有 functionB 有效,这可以通过addEventListener来解决。 不能控制在哪个阶段来执行,这个会在后面将事件捕获/冒泡的时候讲到。这个同样可以通过addEventListener来解决。 因此 addEventListener 横空出世,这个也是目前推荐的写法。 addEventListener 旧版本的addEventListener第三个参数是 bool,新版版的第三个参数是对象,这样方便之后的扩展,承载更多的功能, 我们来重点介绍一下它。 addEventListener 可以给 Element,Document,Window,甚至 XMLHttpRequest 等绑定事件,当指定的事件发生的时候,绑定的回调函数就会被以某种机制进行执行,这种机制我们稍后就会讲到。 语法: 123target.addEventListener(type, listener[, options]);target.addEventListener(type, listener[, useCapture]);target.addEventListener(type, listener[, useCapture, wantsUntrusted ]); // Gecko/Mozilla only type 是你想要绑定的事件类型,常见的有 click, scroll, touch, mouseover 等,旧版本的第三个参数是 bool,表示是否是捕获阶段,默认是 false,即默认为冒泡阶段。新版本是一个对象,其中有 capture(和上面功能一样),passive 和 once。 once 用来执行是否只执行一次,passive 如果被指定为 true 表示永远不会执行preventDefault(),这在实现丝滑柔顺的滚动的效果中很重要。更多请参考Improving scrolling performance with passive listeners 框架中的事件实际上,我们现在大多数情况都是用框架来写代码,因此上面的情况其实在现实中是非常少见的,我们更多看到的是框架封装好的事件,比如 React 的合成事件,感兴趣的可以看下这几篇文章。 React SyntheticEvent Vue 和 React 的优点分别是什么?两者的最核心差异对比是什么? 虽然我们很少时候会接触到原生的事件,但是了解一下事件对象,事件机制,事件代理等还是很有必要的,因为框架的事件系统至少在这方面还是一致的,这些内容我们接下来就会讲到。 事件对象所有的事件处理函数在被浏览器执行的时候都会带上一个事件对象,举个例子: 12345function handleClick(e) { console.log(e);}btn.addEventListener(\"click\", handleClick); 这个 e 就是事件对象,即event object。 这个对象有一些很有用的属性和方法,下面举几个常用的属性和方法。 属性 target x, y 等位置信息 timeStamp eventPhase … 方法 preventDefault 用于阻止浏览器的默认行为,比如 a 标签会默认进行跳转,form 会默认校验并发送请求到 action 指定的地址等 stopPropagation 用于阻止事件的继续冒泡行为,后面讲事件传播的时候会提到。 … 事件传播前面讲到了事件默认是绑定到冒泡阶段的,如果你显式令 useCapture 为 true,则会绑定到捕获阶段。 事件捕获很有意思,以至于我会经常出事件的题目加上一点事件传播的机制,让候选人进行回答,这很能体现一个人的水平。了解事件的传播机制,对于一些特定问题有着非常大的作用。 一个 Element 上绑定的事件触发了,那么其实会经过三个阶段。 第一个阶段 - 捕获阶段 从最外层即 HTML 标签开始,检查当前元素有没有绑定对应捕获阶段事件,如果有则执行,没有则继续往里面传播,这个过程递归执行直到触达触发这个事件的元素为止。 伪代码: 12345678910111213141516function capture(e, currentElement) { if (currentElement.listners[e.type] !== void 0) { currentElement.listners[e.type].forEach((fn) => fn(e)); } // pass down if (currentElement !== e.target) { // getActiveChild用于获取当前事件传播链路上的子节点 capture(e, getActiveChild(currentElement, e)); } else { bubble(e, currentElement); }}// 这个Event对象由引擎创建capture(new Event(), document.querySelector(\"html\")); 第二个阶段 - 目标阶段 上面已经提到了,这里省略了。 第三个阶段 - 冒泡阶段 从触发这个事件的元素开始,检查当前元素有没有绑定对应冒泡阶段事件,如果有则执行,没有则继续往里面传播,这个过程递归执行直到触达 HTML 为止。 伪代码: 123456789function bubble(e, currentElement) { if (currentElement.listners[e.type] !== void 0) { currentElement.listners[e.type].forEach((fn) => fn(e)); } // returning if (currentElement !== document.querySelector(\"html\")) { bubble(e, currentElement.parent); }} 上述的过程用图来表示为: 如果你不希望事件继续冒泡,可以用之前我提到的stopPropagation。 伪代码: 12345678910111213141516171819function bubble(e, currentElement) { let stopped = false; function cb() { stopped = true; } if (currentElement.listners[e.type] !== void 0) { currentElement.listners[e.type].forEach((fn) => { fn({ ...e, stopPropagation: cb, }); if (stopped) return; }); } // returning if (currentElement !== document.querySelector(\"html\")) { bubble(e, currentElement.parent); }} 事件代理利用上面提到的事件冒泡机制,我们可以选择做一些有趣的东西。 举个例子: 我们有一个如下的列表,我们想在点击对应列表项的时候,输出是点击了哪个元素。 HTML 代码: 123456<ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li></ul> JS 代码: 123document .querySelector(\"ul\") .addEventListener(\"click\", (e) => console.log(e.target.innerHTML)); 在线地址 上面说了addEventListener会默认绑定到冒泡阶段,因此事件会从目标阶段开始,向外层冒泡,到我们绑定了事件的 ul 上,ul 中通过事件对象的 target 属性就能获取到是哪一个元素触发的。 “事件会从目标阶段开始”,并不是说事件没有捕获阶段,而是我们没有绑定捕获阶段,我描述给省略了。 我们只给外层的 ul 绑定了事件处理函数,但是可以看到 li 点击的时候,实际上会打印出对应 li 的内容(1,2,3 或者 4)。 我们无须给每一个 li 绑定事件处理函数,不仅从代码量还是性能上都有一定程度的提升。 这个有趣的东西,我们给了它一个好听的名字“事件代理”。在实际业务中我们会经常使用到这个技巧,这同时也是面试的高频考点。 总结事件其实不是浏览器特有的,和 JS 语言也没有什么关系,这也是我为什么没有将其划分到 JS 部分的原因。很多地方都有事件系统,但是各种事件模型又不太一致。 我们今天讲的是浏览器的事件模型,浏览器基于事件驱动,将很多东西都抽象为事件,比如用户交互,网络请求,页面加载,报错等,可以说事件是浏览器正常运行的基石。 我们在使用的框架都对事件进行了不同程度的封装和处理,除了了解原生的事件和原理,有时候了解一下框架本身对事件的处理也是很有必要的。 当发生一个事件的时候,浏览器会初始化一个事件对象,然后将这个事件对象按照一定的逻辑进行传播,这个逻辑就是事件传播机制。 我们提到了事件传播其实分为三个阶段,按照时间先后顺序分为捕获阶段,目标阶段和冒泡阶段。开发者可以选择监听不同的阶段,从而达到自己想要的效果。 事件对象有很多属性和方法,允许你在事件处理函数中进行读取和操作,比如读取点击的坐标信息,阻止冒泡等。 最后我们通过一个例子,说明了如何利用冒泡机制来实现事件代理。 本文只是一个浏览器事件机制的科普文,并没有也不会涉及到很多细节。希望这篇文章能让你对浏览器时间有更深的理解,如果你对 nodejs 时间模型感兴趣,请期待我的 nodejs 事件模型。 事件循环和事件循环也有千丝万缕的联系,如果有时间,我会出一篇关于时间循环的文章。","categories":[{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/categories/浏览器/"},{"name":"事件","slug":"浏览器/事件","permalink":"https://lucifer.ren/blog/categories/浏览器/事件/"}],"tags":[{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/tags/浏览器/"},{"name":"事件","slug":"事件","permalink":"https://lucifer.ren/blog/tags/事件/"}]},{"title":"用最优雅的方式打开终端","slug":"iterm-window-hotkey","date":"2020-11-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.908Z","comments":true,"path":"2020/11/13/iterm-window-hotkey/","link":"","permalink":"https://lucifer.ren/blog/2020/11/13/iterm-window-hotkey/","excerpt":"如何快速呼出终端先看效果: 视频地址 Gif 太大, 放不了。 放一个 MP4 文件给大家看吧。 接下来就教你随时随地,用最优雅的方式。","text":"如何快速呼出终端先看效果: 视频地址 Gif 太大, 放不了。 放一个 MP4 文件给大家看吧。 接下来就教你随时随地,用最优雅的方式。 工具 iTerm2 步骤 打开 iTerm2 打开 Preference(快捷键 cmd + ,) 几乎所有的应用打开 Preference 都是 cmd + , 切换到 profiles,并点击 +。 即新建一个 profile 起一个名字,设置一下窗口位置浮在当前屏幕上(如图),这样可以随时查看终端。 绑定一个快捷键。切换到 keys 标签,点击最下方的 Config HotKey Window。(如图) 一点后话我的快捷键看起来很复杂, 实际上我把键盘右侧的 Alt 映射成了超级键。 · 先把 右侧的 Alt 映射到了 Caps Lock 然后将 Caps Lock 映射到 cmd + control + option + shift","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"}],"tags":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"},{"name":"效率","slug":"效率","permalink":"https://lucifer.ren/blog/tags/效率/"},{"name":"命令行","slug":"命令行","permalink":"https://lucifer.ren/blog/tags/命令行/"}]},{"title":"几乎刷完了力扣所有的链表题,我发现了这些东西。。。","slug":"linked-list","date":"2020-11-07T16:00:00.000Z","updated":"2023-01-05T12:24:49.939Z","comments":true,"path":"2020/11/08/linked-list/","link":"","permalink":"https://lucifer.ren/blog/2020/11/08/linked-list/","excerpt":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 大家好,我是 lucifer。今天给大家带来的专题是《链表》。很多人觉得链表是一个很难的专题。实际上,只要你掌握了诀窍,它并没那么难。接下来,我们展开说说。 链表标签在 leetcode 一共有 54 道题。 为了准备这个专题,我花了几天时间将 leetcode 几乎所有的链表题目都刷了一遍。 可以看出,除了六个上锁的,其他我都刷了一遍。而实际上,这六个上锁的也没有什么难度,甚至和其他 48 道题差不多。 通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。","text":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 大家好,我是 lucifer。今天给大家带来的专题是《链表》。很多人觉得链表是一个很难的专题。实际上,只要你掌握了诀窍,它并没那么难。接下来,我们展开说说。 链表标签在 leetcode 一共有 54 道题。 为了准备这个专题,我花了几天时间将 leetcode 几乎所有的链表题目都刷了一遍。 可以看出,除了六个上锁的,其他我都刷了一遍。而实际上,这六个上锁的也没有什么难度,甚至和其他 48 道题差不多。 通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。 简介各种数据结构,不管是队列,栈等线性数据结构还是树,图的等非线性数据结构,从根本上底层都是数组和链表。不管你用的是数组还是链表,用的都是计算机内存,物理内存是一个个大小相同的内存单元构成的,如图: (图 1. 物理内存) 而数组和链表虽然用的都是物理内存,都是两者在对物理的使用上是非常不一样的,如图: (图 2. 数组和链表的物理存储图) 不难看出,数组和链表只是使用物理内存的两种方式。 数组是连续的内存空间,通常每一个单位的大小也是固定的,因此可以按下标随机访问。而链表则不一定连续,因此其查找只能依靠别的方式,一般我们是通过一个叫 next 指针来遍历查找。链表其实就是一个结构体。 比如一个可能的单链表的定义可以是: 1234interface ListNode<T> { data: T; next: ListNode<T>;} data 是数据域,存放数据,next 是一个指向下一个节点的指针。 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。 从上面的物理结构图可以看出数组是一块连续的空间,数组的每一项都是紧密相连的,因此如果要执行插入和删除操作就很麻烦。对数组头部的插入和删除时间复杂度都是$O(N)$,而平均复杂度也是$O(N)$,只有对尾部的插入和删除才是$O(1)$。简单来说”数组对查询特别友好,对删除和添加不友好“。为了解决这个问题,就有了链表这种数据结构。链表适合在数据需要有一定顺序,但是又需要进行频繁增删除的场景,具体内容参考后面的《链表的基本操作》小节。 (图 3. 一个典型的链表逻辑表示图) 后面所有的图都是基于逻辑结构,而不是物理结构 链表只有一个后驱节点 next,如果是双向链表还会有一个前驱节点 pre。 有没有想过为啥只有二叉树,而没有一叉树。实际上链表就是特殊的树,即一叉树。 链表的基本操作要想写出链表的题目, 熟悉链表的各种基本操作和复杂度是必须的。 插入插入只需要考虑要插入位置前驱节点和后继节点(双向链表的情况下需要更新后继节点)即可,其他节点不受影响,因此在给定指针的情况下插入的操作时间复杂度为O(1)。这里给定指针中的指针指的是插入位置的前驱节点。 伪代码: 1234temp = 待插入位置的前驱节点.next待插入位置的前驱节点.next = 待插入指针待插入指针.next = temp 如果没有给定指针,我们需要先遍历找到节点,因此最坏情况下时间复杂度为 O(N)。 提示 1: 考虑头尾指针的情况。 提示 2: 新手推荐先画图,再写代码。等熟练之后,自然就不需要画图了。 删除只需要将需要删除的节点的前驱指针的 next 指针修正为其下下个节点即可,注意考虑边界条件。 伪代码: 1待删除位置的前驱节点.next = 待删除位置的前驱节点.next.next 提示 1: 考虑头尾指针的情况。 提示 2: 新手推荐先画图,再写代码。等熟练之后,自然就不需要画图了。 遍历遍历比较简单,直接上伪代码。 迭代伪代码: 12345当前指针 = 头指针while 当前节点不为空 { print(当前节点) 当前指针 = 当前指针.next} 一个前序遍历的递归的伪代码: 12345dfs(cur) { if 当前节点为空 return print(cur.val) return dfs(cur.next)} 链表和数组到底有多大的差异?熟悉我的小伙伴应该经常听到我说过一句话,那就是数组和链表同样作为线性的数组结构,二者在很多方便都是相同的,只在细微的操作和使用场景上有差异而已。而使用场景,很难在题目中直接考察。 实际上,使用场景是可以死记硬背的。 因此,对于我们做题来说,二者的差异通常就只是细微的操作差异。这么说大家可能感受不够强烈,我给大家举几个例子。 数组的遍历: 1234for(int i = 0; i < arr.size();i++) { print(arr[i])} 链表的遍历: 123for (ListNode cur = head; cur != null; cur = cur.next) { print(cur.val)} 是不是很像? 可以看出二者逻辑是一致的,只不过细微操作不一样。比如: 数组是索引 ++ 链表是 cur = cur.next 如果我们需要逆序遍历呢? 123for(int i = arr.size() - 1; i > - 1;i--) { print(arr[i])} 如果是链表,通常需要借助于双向链表。而双向链表在力扣的题目很少,因此大多数你没有办法拿到前驱节点,这也是为啥很多时候会自己记录一个前驱节点 pre 的原因。 123for (ListNode cur = tail; cur != null; cur = cur.pre) { print(cur.val)} 如果往数组末尾添加一个元素就是: 1arr.push(1) 链表的话,很多语言没有内置的数组类型。比如力扣通常使用如下的类来模拟。 1234567public class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; }} 我们是不能直接调用 push 方法的。想一下,如果让你实现这个,你怎么做?你可以先自己想一下,再往下看。 3…2…1 ok,其实很简单。 123// 假设 tail 是链表的尾部节点tail.next = new ListNode('lucifer')tail = tail.next 经过上面两行代码之后, tail 仍然指向尾部节点。是不是很简单,你学会了么? 这有什么用?比如有的题目需要你复制一个新的链表, 你是不是需要开辟一个新的链表头,然后不断拼接(push)复制的节点?这就用上了。 对于数组的底层也是类似的,一个可能的数组 push 底层实现: 12arr.length += 1arr[arr.length - 1] = 'lucifer' 总结一下, 数组和链表逻辑上二者有很多相似之处,不同的只是一些使用场景和操作细节,对于做题来说,我们通常更关注的是操作细节。关于细节,接下来给大家介绍,这一小节主要让大家知道二者在思想和逻辑的神相似。 有些小伙伴做链表题先把链表换成数组,然后用数组做,本人不推荐这种做法,这等于是否认了链表存在的价值,小朋友不要模仿。 链表题难度几何?链表题真的不难。说链表不难是有证据的。就拿 LeetCode 平台来说,处于困难难度的题目只有两个。 其中 第 23 题基本没有什么链表操作,一个常规的“归并排序”即可搞定,而合并两个有序链表是一个简单题。如果你懂得数组的归并排序和合并两个有序链表,应该轻松拿下这道题。 合并两个有序数组也是一个简单题目,二者难度几乎一样。 而对于第 25 题, 相信你看完本节的内容,也可以做出来。 不过,话虽这么说,但是还是有很多小朋友给我说 ”指针绕来绕去就绕晕了“, ”老是死循环“ 。。。。。。链表题目真的那么难么?我们又该如何破解? lucifer 给大家准备了一个口诀 一个原则, 两种题型,三个注意,四个技巧,让你轻松搞定链表题,再也不怕手撕链表。 我们依次来看下这个口诀的内容。 一个原则一个原则就是 画图,尤其是对于新手来说。不管是简单题还是难题一定要画图,这是贯穿链表题目的一个准则。 画图可以减少我们的认知负担,这其实和打草稿,备忘录道理是一样的,将存在脑子里的东西放到纸上。举一个不太恰当的例子就是你的脑子就是 CPU,脑子的记忆就是寄存器。寄存器的容量有限,我们需要把不那么频繁使用的东西放到内存,把寄存器用在真正该用的地方,这个内存就是纸或者电脑平板等一切你可以画图的东西。 画的好看不好看都不重要,能看清就行了。用笔随便勾画一下, 能看出关系就够了。 两个考点我把力扣的链表做了个遍。发现一个有趣的现象,那就是链表的考点很单一。除了设计类题目,其考点无法就两点: 指针的修改 链表的拼接 指针的修改其中指针修改最典型的就是链表反转。其实链表反转不就是修改指针么? 对于数组这种支持随机访问的数据结构来说, 反转很容易, 只需要头尾不断交换即可。 12345678910function reverseArray(arr) { let left = 0; let right = arr.length - 1; while (left < right) { const temp = arr[left]; arr[left++] = arr[right]; arr[right--] = temp; } return arr;} 而对于链表来说,就没那么容易了。力扣关于反转链表的题简直不要太多了。 今天我给大家写了一个最完整的链表反转,以后碰到可以直接用。当然,前提是大家要先理解再去套。 接下来,我要实现的一个反转任意一段链表 1reverse(self, head: ListNode, tail: ListNode)。 其中 head 指的是需要反转的头节点,tail 是需要反转的尾节点。 不难看出,如果 head 是整个链表的头,tail 是整个链表的尾,那就是反转整个链表,否则就是反转局部链表。接下来,我们就来实现它。 首先,我们要做的就是画图。这个我在一个原则部分讲过了。 如下图,是我们需要反转的部分链表: 而我们期望反转之后的长这个样子: 不难看出, 最终返回 tail 即可。 由于链表的递归性,实际上,我们只要反转其中相邻的两个,剩下的采用同样的方法完成即可。 链表是一种递归的数据结构,因此采用递归的思想去考虑往往事半功倍,关于递归思考链表将在后面《三个注意》部分展开。 对于两个节点来说,我们只需要下修改一次指针即可,这好像不难。 1cur.next = pre 就是这一个操作,不仅硬生生有了环,让你死循环。还让不应该一刀两断的它们分道扬镳。 关于分道扬镳这个不难解决, 我们只需要反转前,记录一下下一个节点即可: 1234next = cur.nextcur.next = precur = next 那么环呢? 实际上, 环不用解决。因为如果我们是从前往后遍历,那么实际上,前面的链表已经被反转了,因此上面我的图是错的。正确的图应该是: 至此为止,我们可以写出如下代码: 1234567891011121314# 翻转一个子链表,并返回新的头与尾def reverse(self, head: ListNode, tail: ListNode): cur = head pre = None while cur != tail: # 留下联系方式 next = cur.next # 修改指针 cur.next = pre # 继续往下走 pre = cur cur = next # 反转后的新的头尾节点返回出去 return tail, head 如果你仔细观察,会发现,我们的 tail 实际上是没有被反转的。解决方法很简单,将 tail 后面的节点作为参数传进来呗。 12345678910111213141516class Solution: # 翻转一个子链表,并且返回新的头与尾 def reverse(self, head: ListNode, tail: ListNode, terminal:ListNode): cur = head pre = None while cur != terminal: # 留下联系方式 next = cur.next # 修改指针 cur.next = pre # 继续往下走 pre = cur cur = next # 反转后的新的头尾节点返回出去 return tail, head 相信你对反转链表已经有了一定的了解。后面我们还会对这个问题做更详细的讲解,大家先留个印象就好。 链表的拼接大家有没有发现链表总喜欢穿来穿去(拼接)的?比如反转链表 II,再比如合并有序链表等。 为啥链表总喜欢穿来穿去呢?实际上,这就是链表存在的价值,这就是设计它的初衷呀! 链表的价值就在于其不必要求物理内存的连续性,以及对插入和删除的友好。这在文章开头的链表和数组的物理结构图就能看出来。 因此链表的题目很多拼接的操作。如果上面我讲的链表基本操作你会了,我相信这难不倒你。除了环,边界 等 。。。 ^_^。 这几个问题我们后面再看。 三个注意链表最容易出错的地方就是我们应该注意的地方。链表最容易出的错 90 % 集中在以下三种情况: 出现了环,造成死循环。 分不清边界,导致边界条件出错。 搞不懂递归怎么做 接下来,我们一一来看。 环环的考点有两个: 题目就有可能环,让你判断是否有环,以及环的位置。 题目链表没环,但是被你操作指针整出环了。 这里我们只讨论第二种,而第一种可以用我们后面提到的快慢指针算法。 避免出现环最简单有效的措施就是画图,如果两个或者几个链表节点构成了环,通过图是很容易看出来的。因此一个简单的实操技巧就是先画图,然后对指针的操作都反应在图中。 但是链表那么长,我不可能全部画出来呀。其实完全不用,上面提到了链表是递归的数据结构, 很多链表问题天生具有递归性,比如反转链表,因此仅仅画出一个子结构就可以了。这个知识,我们放在后面的前后序部分讲解。 边界很多人错的是没有考虑边界。一个考虑边界的技巧就是看题目信息。 如果题目的头节点可能被移除,那么考虑使用虚拟节点,这样头节点就变成了中间节点,就不需要为头节点做特殊判断了。 题目让你返回的不是原本的头节点,而是尾部节点或者其他中间节点,这个时候要注意指针的变化。 以上两者部分的具体内容,我们在稍后讲到的虚拟头部分讲解。老规矩,大家留个印象即可。 前后序ok,是时候填坑了。上面提到了链表结构天生具有递归性,那么使用递归的解法或者递归的思维都会对我们解题有帮助。 在 二叉树遍历 部分,我讲了二叉树的三种流行的遍历方法,分别是前序遍历,中序遍历和后序遍历。 前中后序实际上是指的当前节点相对子节点的处理顺序。如果先处理当前节点再处理子节点,那么就是前序。如果先处理左节点,再处理当前节点,最后处理右节点,就是中序遍历。后序遍历自然是最后处理当前节点了。 实际过程中,我们不会这么扣的这么死。比如: 12345def traverse(root): print('pre') traverse(root.left) traverse(root.righ) print('post') 如上代码,我们既在进入左右节点前有逻辑, 又在退出左右节点之后有逻辑。这算什么遍历方式呢?一般意义上,我习惯只看主逻辑的位置,如果你的主逻辑是在后面就是后序遍历,主逻辑在前面就是前序遍历。 这个不是重点,对我们解题帮助不大,对我们解题帮助大的是接下来要讲的内容。 绝大多数的题目都是单链表,而单链表只有一个后继指针。因此只有前序和后序,没有中序遍历。 还是以上面讲的经典的反转链表来说。 如果是前序遍历,我们的代码是这样的: 12345678def dfs(head, pre): if not head: return pre next = head.next # # 主逻辑(改变指针)在后面 head.next = pre dfs(next, head)dfs(head, None) 后续遍历的代码是这样的: 123456789def dfs(head): if not head or not head.next: return head res = dfs(head.next) # 主逻辑(改变指针)在进入后面的节点的后面,也就是递归返回的过程会执行到 head.next.next = head head.next = None return res 可以看出,这两种写法不管是边界,入参,还是代码都不太一样。为什么会有这样的差异呢? 回答这个问题也不难,大家只要记住一个很简单的一句话就好了,那就是如果是前序遍历,那么你可以想象前面的链表都处理好了,怎么处理的不用管。相应地如果是后序遍历,那么你可以想象后面的链表都处理好了,怎么处理的不用管。这句话的正确性也是毋庸置疑。 如下图,是前序遍历的时候,我们应该画的图。大家把注意力集中在中间的框(子结构)就行了,同时注意两点。 前面的已经处理好了 后面的还没处理好 据此,我们不难写出以下递归代码,代码注释很详细,大家看注释就好了。 123456789def dfs(head, pre): if not head: return pre # 留下联系方式(由于后面的都没处理,因此可以通过 head.next 定位到下一个) next = head.next # 主逻辑(改变指针)在进入后面节点的前面(由于前面的都已经处理好了,因此不会有环) head.next = pre dfs(next, head)dfs(head, None) 如果是后序遍历呢?老规矩,秉承我们的一个原则,先画图。 不难看出,我们可以通过 head.next 拿到下一个元素,然后将下一个元素的 next 指向自身来完成反转。 用代码表示就是: 1head.next.next = head 画出图之后,是不是很容易看出图中有一个环? 现在知道画图的好处了吧?就是这么直观,当你很熟练了,就不需要画了,但是在此之前,请不要偷懒。 因此我们需要将 head.next 改为不会造成环的一个值,比如置空。 12345678910def dfs(head): if not head or not head.next: return head # 不需要留联系方式了,因为我们后面已经走过了,不需走了,现在我们要回去了。 res = dfs(head.next) # 主逻辑(改变指针)在进入后面的节点的后面,也就是递归返回的过程会执行到 head.next.next = head # 置空,防止环的产生 head.next = None return res 值得注意的是,前序遍历很容易改造成迭代,因此推荐大家使用前序遍历。我拿上面的迭代和这里的前序遍历给大家对比一下。 那么为什么前序遍历很容易改造成迭代呢?实际上,这句话我说的不准确,准确地说应该是前序遍历容易改成不需要栈的递归,而后续遍历需要借助栈来完成。这也不难理解,由于后续遍历的主逻辑在函数调用栈的弹出过程,而前序遍历则不需要。 这里给大家插播一个写递归的技巧,那就是想象我们已经处理好了一部分数据,并把他们用手挡起来,但是还有一部分等待处理,接下来思考”如何根据已经处理的数据和当前的数据来推导还没有处理的数据“就行了。 四个技巧针对上面的考点和注意点,我总结了四个技巧来应对,这都是在平时做题中非常实用的技巧。 虚拟头来了解虚拟头的意义之前,先给大家做几个小测验。 Q1: 如下代码 ans.next 指向什么? 1234ans = ListNode(1)ans.next = headhead = head.nexthead = head.next A1: 最开始的 head。 Q2:如下代码 ans.next 指向什么? 1234ans = ListNode(1)head = anshead.next = ListNode(3)head.next = ListNode(4) A2: ListNode(4) 似乎也不难,我们继续看一道题。 Q3: 如下代码 ans.next 指向什么? 12345ans = ListNode(1)head = anshead.next = ListNode(3)head = ListNode(2)head.next = ListNode(4) A3: ListNode(3) 如果三道题你都答对了,那么恭喜你,这一部分可以跳过。 如果你没有懂也没关系,我这里简单解释一下你就懂了。 ans.next 指向什么取决于最后切断 ans.next 指向的地方在哪。比如 Q1,ans.next 指向的是 head,我们假设其指向的内存编号为 9527。 之后执行 head = head.next (ans 和 head 被切断联系了),此时的内存图: 我们假设头节点的 next 指针指向的节点的内存地址为 10200 不难看出,ans 没变。 对于第二个例子。一开始和上面例子一样,都是指向 9527。而后执行了: 12head.next = ListNode(3)head.next = ListNode(4) ans 和 head 又同时指向 ListNode(3) 了。如图: head.next = ListNode(4) 也是同理。因此最终的指向 ans.next 是 ListNode(4)。 我们来看最后一个。前半部分和 Q2 是一样的。 123ans = ListNode(1)head = anshead.next = ListNode(3) 按照上面的分析,此时 head 和 ans 的 next 都指向 ListNode(3)。关键是下面两行: 12head = ListNode(2)head.next = ListNode(4) 指向了 head = ListNode(2) 之后, head 和 ans 的关系就被切断了,当前以及之后所有的 head 操作都不会影响到 ans,因此 ans 还指向被切断前的节点,因此 ans.next 输出的是 ListNode(3)。 花了这么大的篇幅讲这个东西的原因就是,指针操作是链表的核心,如果这些基础不懂, 那么就很难做。接下来,我们介绍主角 - 虚拟头。 相信做过链表的小伙伴都听过这么个名字。为什么它这么好用?它的作用无非就两个: 将头节点变成中间节点,简化判断。 通过在合适的时候断开链接,返回链表的中间节点。 我上面提到了链表的三个注意,有一个是边界。头节点是最常见的边界,那如果我们用一个虚拟头指向头节点,虚拟头就是新的头节点了,而虚拟头不是题目给的节点,不参与运算,因此不需要特殊判断,虚拟头就是这个作用。 如果题目需要返回链表中间的某个节点呢?实际上也可借助虚拟节点。由于我上面提到的指针的操作,实际上,你可以新建一个虚拟头,然后让虚拟头在恰当的时候(刚好指向需要返回的节点)断开连接,这样我们就可以返回虚拟头的 next 就 ok 了。25. K 个一组翻转链表 就用到了这个技巧。 不仅仅是链表, 二叉树等也经常用到这个技巧。 比如我让你返回二叉树的最左下方的节点怎么做?我们也可以利用上面提到的技巧。新建一个虚拟节点,虚拟节点 next 指向当前节点,并跟着一起走,在递归到最左下的时候断开链接,最后返回 虚拟节点的 next 指针即可。 快慢指针判断链表是否有环,以及环的入口都是使用快慢指针即可解决。这种题就是不知道不会,知道了就不容易忘。不多说了,大家可以参考我之前的题解 https://github.com/azl397985856/leetcode/issues/274#issuecomment-573985706 。 除了这个,求链表的交点也是快慢指针,算法也是类似的。不这都属于不知道就难,知道了就容易。且下次写不容易想不到或者出错。 这部分大家参考我上面的题解理一下, 写一道题就可以掌握。接下来,我们来看下穿针引线大法。 另外由于链表不支持随机访问,因此如果想要获取数组中间项和倒数第几项等特定元素就需要一些特殊的手段,而这个手段就是快慢指针。比如要找链表中间项就搞两个指针,一个大步走(一次走两步),一个小步走(一次走一步),这样快指针走到头,慢指针刚好在中间。 如果要求链表倒数第 2 个,那就让快指针先走一步,慢指针再走,这样快指针走到头,慢指针刚好在倒数第二个。这个原理不难理解吧?这种技巧属于会了就容易,且不容易忘。不会就很难想出的类型,因此大家学会了拿几道题练一下就可以放下了。 穿针引线这是链表的第二个考点 - 拼接链表。我在 25. K 个一组翻转链表,61. 旋转链表 和 92. 反转链表 II 都用了这个方法。穿针引线是我自己起的一个名字,起名字的好处就是方便记忆。 这个方法通常不是最优解,但是好理解,方便书写,不易出错,推荐新手用。 还是以反转链表为例,只不过这次是反转链表的中间一部分,那我们该怎么做? 反转前面我们已经讲过了,于是我假设链表已经反转好了,那么如何将反转好的链表拼后去呢? 我们想要的效果是这样的: 那怎么达到图上的效果呢?我的做法是从做到右给断点编号。如图有两个断点,共涉及到四个节点。于是我给它们依次编号为 a,b,c,d。 其实 a,d 分别是需要反转的链表部分的前驱和后继(不参与反转),而 b 和 c 是需要反转的部分的头和尾(参与反转)。 因此除了 cur, 多用两个指针 pre 和 next 即可找到 a,b,c,d。 找到后就简单了,直接穿针引线。 12a.next = cb.next = d 这不就好了么?我记得的就有 25 题,61 题 和 92 题都是这么做的,清晰不混乱。 先穿再排后判空这是四个技巧的最后一个技巧了。虽然是最后讲,但并不意味着它不重要。相反,它的实操价值很大。 继续回到上面讲的链表反转题。 1234567891011cur = headpre = Nonewhile cur != tail: # 留下联系方式 next = cur.next # 修改指针 cur.next = pre # 继续往下走 pre = cur cur = next# 反转后的新的头尾节点返回出去 什么时候需要判断 next 是否存在,上面两行代码先写哪个呢? 是这样? 12next = cur.nextcur.next = pre 还是这样? 12cur.next = prenext = cur.next 先穿我给你的建议是:先穿。这里的穿是修改指针,包括反转链表的修改指针和穿针引线的修改指针。先别管顺序,先穿。 再排穿完之后,代码的总数已经确定了,无非就是排列组合让代码没有 bug。 因此第二步考虑顺序,那上面的两行代码哪个在前?应该是先 next = cur.next ,原因在于后一条语句执行后 cur.next 就变了。由于上面代码的作用是反转,那么其实经过 cur.next = pre 之后链表就断开了,后面的都访问不到了,也就是说此时你只能返回头节点这一个节点。 实际上,有假如有十行穿的代码,我们很多时候没有必要全考虑。我们需要考虑的仅仅是被改变 next 指针的部分。比如 cur.next = pre 的 cur 被改了 next。因此下面用到了 cur.next 的地方就要考虑放哪。其他代码不需要考虑。 后判空和上面的原则类似,穿完之后,代码的总数已经确定了,无非就是看看哪行代码会空指针异常。 和上面的技巧一样,我们很多时候没有必要全考虑。我们需要考虑的仅仅是被改变 next 指针的部分。 比如这样的代码 123while cur: cur = cur.next 我们考虑 cur 是否为空呢? 很明显不可能,因为 while 条件保证了,因此不需判空。 那如何是这样的代码呢? 123while cur: next = cur.next n_next = next.next 如上代码有两个 next,第一个不用判空,上面已经讲了。而第二个是需要的,因为 next 可能是 null。如果 next 是 null ,就会引发空指针异常。因此需要修改为类似这样的代码: 1234while cur: next = cur.next if not next: break n_next = next.next 以上就是我们给大家的四个技巧了。相信有了这四个技巧,写链表题就没那么艰难啦~ ^_^ 题目推荐最后推荐几道题给大家,用今天学到的知识解决它们吧~ 21. 合并两个有序链表 82. 删除排序链表中的重复元素 II 83. 删除排序链表中的重复元素 86. 分隔链表 92. 反转链表 II 138. 复制带随机指针的链表 141. 环形链表 142. 环形链表 II 143. 重排链表 148. 排序链表 206. 反转链表 234. 回文链表 总结数组和栈从逻辑上没有大的区别,你看基本操作都是差不多的。如果是单链表,我们无法在 $O(1)$ 的时间拿到前驱节点,这也是为什么我们遍历的时候老是维护一个前驱节点的原因。但是本质原因其实是链表的增删操作都依赖前驱节点。这是链表的基本操作,是链表的特性天生决定的。 可能有的同学有这样的疑问”考点你只讲了指针的修改和链表拼接,难道说链表就只会这些就够了?那我做的题怎么还需要我会前缀和啥的呢?你是不是坑我呢?“ 我前面说了,所有的数据结构底层都是数组和链表中的一种或两种。而我们这里讲的链表指的是考察链表的基本操作的题目。因此如果题目中需要你使用归并排序去合并链表,那其实归并排序这部分已经不再本文的讨论范围了。 实际上,你去力扣或者其他 OJ 翻链表题会发现他们的链表题大都指的是入参是链表,且你需要对链表进行一些操作的题目。再比如树的题目大多数是入参是树,你需要在树上进行搜索的题目。也就是说需要操作树(比如修改树的指针)的题目很少,比如有一道题让你给树增加一个 right 指针,指向同级的右侧指针,如果已经是最右侧了,则指向空。 链表的基本操作就是增删查,牢记链表的基本操作和复杂度是解决问题的基本。有了这些基本还不够,大家要牢记我的口诀”一个原则,两个考点,三个注意,四个技巧“。 做链表的题,要想入门,无它,唯画图尔。能画出图,并根据图进行操作你就入门了,甭管你写的代码有没有 bug 。 而链表的题目核心的考察点只有两个,一个是指针操作,典型的就是反转。另外一个是链表的拼接。这两个既是链表的精髓,也是主要考点。 知道了考点肯定不够,我们写代码哪些地方容易犯错?要注意什么? 这里我列举了三个容易犯错的地方,分别是环,边界和前后序。 其中环指的是节点之间的相互引用,环的题目如果题目本身就有环, 90 % 双指针可以解决,如果本身没有环,那么环就是我们操作指针的时候留下的。如何解决出现环的问题?那就是画图,然后聚焦子结构,忽略其他信息。 除了环,另外一个容易犯错的地方往往是边界的条件, 而边界这块链表头的判断又是一个大头。克服这点,我们需要认真读题,看题目的要求以及返回值,另外一个很有用的技巧是虚拟节点。 如果大家用递归去解链表的题, 一定要注意自己写的是前序还是后序。 如果是前序,那么只思考子结构即可,前面的已经处理好了,怎么处理的,不用管。非要问,那就是同样方法。后面的也不需考虑如何处理,非要问,那就是用同样方法 如果是后续,那么只思考子结构即可,后面的已经处理好了,怎么处理的,不用管。非要问,那就是同样方法。前面的不需考虑如何处理。非要问,那就是用同样方法 如果你想递归和迭代都写, 我推荐你用前序遍历。因为前序遍历容易改成不用栈的递归。 以上就是链表专题的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 我整理的 1000 多页的电子书已经开发下载了,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/categories/链表/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/tags/链表/"}]},{"title":"单调栈解题模板秒杀八道题","slug":"monotone-stack","date":"2020-11-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.609Z","comments":true,"path":"2020/11/03/monotone-stack/","link":"","permalink":"https://lucifer.ren/blog/2020/11/03/monotone-stack/","excerpt":"顾名思义, 单调栈是一种栈。因此要学单调栈,首先要彻底搞懂栈。","text":"顾名思义, 单调栈是一种栈。因此要学单调栈,首先要彻底搞懂栈。 栈是什么? 栈是一种受限的数据结构, 体现在只允许新的内容从一个方向插入或删除,这个方向我们叫栈顶,而从其他位置获取内容是不被允许的 栈最显著的特征就是 LIFO(Last In, First Out - 后进先出) 举个例子: 栈就像是一个放书本的抽屉,进栈的操作就好比是想抽屉里放一本书,新进去的书永远在最上层,而退栈则相当于从里往外拿书本,永远是从最上层开始拿,所以拿出来的永远是最后进去的哪一个 栈的常用操作 进栈 - push - 将元素放置到栈顶 退栈 - pop - 将栈顶元素弹出 栈顶 - top - 得到栈顶元素的值 是否空栈 - isEmpty - 判断栈内是否有元素 栈的常用操作时间复杂度由于栈只在尾部操作就行了,我们用数组进行模拟的话,可以很容易达到 O(1)的时间复杂度。当然也可以用链表实现,即链式栈。 进栈 - O(1) 出栈 - O(1) 应用 函数调用栈 浏览器前进后退 匹配括号 单调栈用来寻找下一个更大(更小)元素 题目推荐 394. 字符串解码 946. 验证栈序列 1381. 设计一个支持增量操作的栈 单调栈又是什么?单调栈是一种特殊的栈。栈本来就是一种受限的数据结构了,单调栈在此基础上又受限了一次(受限++)。 单调栈要求栈中的元素是单调递增或者单调递减的。 是否严格递减或递减可以根据实际情况来。 这里我用 [a,b,c] 表示一个栈。 其中 左侧为栈底,右侧为栈顶。单调增还是单调减取决于出栈顺序。如果出栈的元素是单调增的,那就是单调递增栈,如果出栈的元素是单调减的,那就是单调递减栈。 比如: [1,2,3,4] 就是一个单调递减栈(因为此时的出栈顺序是 4,3,2,1。下同,不再赘述) [3,2,1] 就是一个单调递增栈 [1,3,2] 就不是一个合法的单调栈 那这个限制有什么用呢?这个限制(特性)能够解决什么用的问题呢? 适用场景单调栈适合的题目是求解下一个大于 xxx或者下一个小于 xxx这种题目。所有当你有这种需求的时候,就应该想到单调栈。 那么为什么单调栈适合求解下一个大于 xxx或者下一个小于 xxx这种题目?原因很简单,我这里通过一个例子给大家讲解一下。 这里举的例子是单调递减栈 比如我们需要依次将数组 [1,3,4,5,2,9,6] 压入单调栈。 首先压入 1,此时的栈为:[1] 继续压入 3,此时的栈为:[1,3] 继续压入 4,此时的栈为:[1,3,4] 继续压入 5,此时的栈为:[1,3,4,5] 如果继续压入 2,此时的栈为:[1,3,4,5,2] 不满足单调递减栈的特性, 因此需要调整。如何调整?由于栈只有 pop 操作,因此我们只好不断 pop,直到满足单调递减为止。 上面其实我们并没有压入 2,而是先 pop,pop 到压入 2 依然可以保持单调递减再 压入 2,此时的栈为:[1,2] 继续压入 9,此时的栈为:[1,2,9] 如果继续压入 6,则不满足单调递减栈的特性, 我们故技重施,不断 pop,直到满足单调递减为止。此时的栈为:[1,2,6] 注意这里的栈仍然是非空的,如果有的题目需要用到所有数组的信息,那么很有可能因没有考虑边界而不能通过所有的测试用例。 这里介绍一个技巧 - 哨兵法,这个技巧经常用在单调栈的算法中。 我们可以在数组前后分别添加一个无限大的数(或者无限小的数)来充当哨兵,起到简化逻辑的作用 对于上面的例子,我可以在原数组 [1,3,4,5,2,9,6] 的右侧添加一个小于数组中最小值的项即可,比如 -1。此时的数组是 [1,3,4,5,2,9,6,-1]。这种技巧可以简化代码逻辑,大家尽量掌握。 上面的例子如果你明白了,就不难理解为啥单调栈适合求解下一个大于 xxx或者下一个小于 xxx这种题目了。比如上面的例子,我们就可以很容易地求出在其之后第一个小于其本身的位置。比如 3 的索引是 1,小于 3 的第一个索引是 4,2 的索引 4,小于 2 的第一个索引是 0,但是其在 2 的索引 4 之后,因此不符合条件,也就是不存在在 2 之后第一个小于 2 本身的位置。 上面的例子,我们在第 6 步开始 pop,第一个被 pop 出来的是 5,因此 5 之后的第一个小于 5 的索引就是 4。同理被 pop 出来的 3,4,5 也都是 4。 如果用 ans 来表示在其之后第一个小于其本身的位置,ans[i] 表示 arr[i] 之后第一个小于 arr[i] 的位置, ans[i] 为 -1 表示这样的位置不存在,比如前文提到的 2。那么此时的 ans 是 [-1,4,4,4,-1,-1,-1]。 第 8 步,我们又开始 pop 了。此时 pop 出来的是 9,因此 9 之后第一个小于 9 的索引就是 6。 这个算法的过程用一句话总结就是,如果压栈之后仍然可以保持单调性,那么直接压。否则先弹出栈的元素,直到压入之后可以保持单调性。这个算法的原理用一句话总结就是,被弹出的元素都是大于当前元素的,并且由于栈是单调减的,因此在其之后小于其本身的最近的就是当前元素了 下面给大家推荐几道题,大家趁着知识还在脑子来,赶紧去刷一下吧~ 伪代码上面的算法可以用如下的伪代码表示,同时这是一个通用的算法模板,大家遇到单调栈的题目可以直接套。 建议大家用自己熟悉的编程语言实现一遍,以后改改符号基本就能用。 12345678910class Solution: def monostoneStack(self, arr: List[int]) -> List[int]: stack = [] ans = 定义一个长度和 arr 一样长的数组,并初始化为 -1 循环 i in arr: while stack and arr[i] > arr[栈顶元素]: peek = 弹出栈顶元素 ans[peek] = i - peek stack.append(i) return ans 复杂度分析 时间复杂度:由于 arr 的元素最多只会入栈,出栈一次,因此时间复杂度仍然是 $O(N)$,其中 N 为数组长度。 空间复杂度:由于使用了栈, 并且栈的长度最大是和 arr 长度一致,因此空间复杂度是 $O(N)$,其中 N 为数组长度。 代码这里提高两种编程语言的单调栈模板供大家参考。 Python3: 12345678910class Solution: def monostoneStack(self, T: List[int]) -> List[int]: stack = [] ans = [0] * len(T) for i in range(len(T)): while stack and T[i] > T[stack[-1]]: peek = stack.pop(-1) ans[peek] = i - peek stack.append(i) return ans JS: 12345678910111213var monostoneStack = function (T) { let stack = []; let result = []; for (let i = 0; i < T.length; i++) { result[i] = 0; while (stack.length > 0 && T[stack[stack.length - 1]] < T[i]) { let peek = stack.pop(); result[peek] = i - peek; } stack.push(i); } return result;}; 单调栈的特性由于单调栈的特性可知, 单调栈适合求解下一个更大的或者下一个更小的数。而实际上,也适合求上一个更大的或者更小的。 因为上一个其实就是 pop 出来后,栈顶的数。下一个其实就是导致你被 pop 出来的那个数。 知道了这一点,大家可以使用 2104. 子数组范围和 练习一下。 题目推荐下面几个题帮助你理解单调栈, 并让你明白什么时候可以用单调栈进行算法优化。 42. 接雨水 84. 柱状图中最大的矩形 739.每日温度 去除重复字母 移掉 K 位数字 下一个更大元素 I 最短无序连续子数组 股票价格跨度 总结单调栈本质就是栈, 栈本身就是一种受限的数据结构。其受限指的是只能在一端进行操作。而单调栈在栈的基础上进一步受限,即要求栈中的元素始终保持单调性。 由于栈中都是单调的,因此其天生适合解决在其之后第一个小于其本身的位置的题目。大家如果遇到题目需要找在其之后第一个小于其本身的位置的题目,就可是考虑使用单调栈。 单调栈的写法相对比较固定,大家可以自己参考我的伪代码自己总结一份模板,以后直接套用可以大大提高做题效率和容错率。","categories":[{"name":"栈","slug":"栈","permalink":"https://lucifer.ren/blog/categories/栈/"}],"tags":[{"name":"栈","slug":"栈","permalink":"https://lucifer.ren/blog/tags/栈/"},{"name":"单调栈","slug":"单调栈","permalink":"https://lucifer.ren/blog/tags/单调栈/"}]},{"title":"前端测试最佳实践(持续更新,建议收藏)","slug":"fe-test-best-practice","date":"2020-11-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.734Z","comments":true,"path":"2020/11/02/fe-test-best-practice/","link":"","permalink":"https://lucifer.ren/blog/2020/11/02/fe-test-best-practice/","excerpt":"最近公司在推行单元测试,但是一些同事对于单元测试只是了解,甚至不怎么了解。因此推动单元测试的阻碍是有的,这种阻碍除了人的层面,还有基础设施的层面。希望通过本文,一方面加深大家对前端测试最佳实践的认知,另一方面可以作为手册,在日常开发中做参考。本文也会不断更新,期待你的参与。 如果大家对前端测试不太清楚,可以先看下文末我写的科普短文。如果你已经对前端测试有所了解,并且希望对前端测试有更深入的了解,以及对如何写出更好的单元测试有兴趣的话,那就让我们开始吧。","text":"最近公司在推行单元测试,但是一些同事对于单元测试只是了解,甚至不怎么了解。因此推动单元测试的阻碍是有的,这种阻碍除了人的层面,还有基础设施的层面。希望通过本文,一方面加深大家对前端测试最佳实践的认知,另一方面可以作为手册,在日常开发中做参考。本文也会不断更新,期待你的参与。 如果大家对前端测试不太清楚,可以先看下文末我写的科普短文。如果你已经对前端测试有所了解,并且希望对前端测试有更深入的了解,以及对如何写出更好的单元测试有兴趣的话,那就让我们开始吧。 写易于测试的代码(Writing test-friendly code)这是一个非常宽泛的话题,本文试图从几个具体的切入点来阐述这个庞大且模糊的话题。 纯函数(Pure Function)关于纯函数可以参考之前我写的一篇函数式教程中的入门篇。 简单来说,纯函数就是数学中的函数。有两个好处: 断言容易了。 (可推导性) 我可以多次,顺序无关地执行测试用例。 (无副作用) 我举一个例子,这是一个稍微高级一点的技巧。不过你一旦理解了其意图,就会发现其思想是多么的简单。 12345678const app = { name: `lucifer's site` start(html) { document.querySelector('#app').innerHTM = html; }}app.start(<div>inner</div>); 上面代码如果要测试,首先你要在 node 环境模拟 document。 如果换一种写法呢? 12345678const app = { name: `lucifer's site` start(querySelector, html) { querySelector('#app').innerHTM = html; }}app.start(document.querySelector, <div>inner</div>); 这样模拟 querySelector 就会变得容易起来。eg: 123// .test.jsimport app from \"./app\";app.start(() => <div id=\"app\">lucifer</div>, <div>inner</div>); 如果你熟悉这种看成方法的话,可能知道它的名字控制反转,英文名 IoC。 单一职责(Single Responsibility Principle)如果一个函数承担了一个以上的职责。那么对我们测试有什么影响呢? 如果对于一个函数 f,其功能有 A 和 B。 A 的输入我们计作 ia,输出计作 oa。 B 的输入我们计作 ib,输出计作 ob。 那么 f 的圈复杂度会增加很多,具体来说。 如果 A 功能和 B 功能相关的话,其测试用例的长度增长是笛卡尔积。 如果 A 功能和 B 功能无关的话,其测试用例的长度增长是线性增长。 eg: 123456function math(a, b, operator) { if (operator === \"+\") return a + b; if (operator === \"-\") return a - b; if (operator === \"*\") return a * b; if (operator === \"/\") return a / b;} 如上代码有四个功能,并且四个功能互相独立。测试用例增长是线性的,也就说将其拆分为四个函数之后,测试用例的数量不变,但是单一函数的圈复杂度降低了,虽然总的软件复杂度并没有降低。 如果四个功能相互耦合的话,后果会更严重。这种情况,拆分多个功能块已经无法解决问题了。这个时候需要对功能进行再次拆解,直到子功能块相互独立。 写清晰直白的测试描述(Wrting Deadly Simply Description)这里我给一个简单的判断标准。 当这个测试报错的时候, 其他人能够只看报错信息,就知道出了什么问题。 比如这样写是好的: 12345describe(`math -> add`, () => { it(\"3 + 2 should equal to 5\", () => { expect(3 + 2).to.be.equal(5); });}); 而这样是不好的: 12345describe(`math -> add`, () => { it(\"add two numbers\", () => { expect(3 + 2).to.be.equal(5); });}); 我举的例子大家可能不屑一顾, 但是当你以我的标准去衡量的时候会发现很多用例都不合格。 逻辑覆盖率(Logic Coverage)很多人关注的是单元测试的物理覆盖率,比如行覆盖率,文件覆盖率等,而大家往往会忽略逻辑覆盖率。 eg: 1234567891011// a.jsexport default (a, b) => a / b// a.test.jsimport divide './a.js'describe(`math -> divide`, () => { it(\"2 / 2 should be 1\", () => { expect(divide(2, 2)).to.be(1); });}); 如上物理覆盖率可以达到 100%,但是很明显逻辑覆盖率却不可以。因为它连最简单的被除数不能为 0 都没包括。 一个更格式的例子,应该是: 123456789101112131415161718192021// a.jsexport default (a, b) => { if (b === 0 or b === -0) throw new Error('dividend should not be zero!') if (Number(a) !== a || Number(b)=== b) throw new Error(`divisor and dividend should be number,but got ${a, b}`) return a / b}// a.test.jsimport divide './a.js'describe(`math -> divide`, () => { it(\"when dividend it zero, there should throw an corresponding eror\", () => { expect(divide(3, 0)).toThrowError(/dividend should not be zero/); }); it(\"when dividend it zero, there should throw an corresponding eror\", () => { expect(divide(3, 'f')).toThrowError(/divisor and dividend should be number/); }); it(\"2 / 2 should be 1\", () => { expect(divide(2, 2)).to.be(1); });}); 逻辑的严密性是双向的,一方面他让你的测试用例更严密,更无懈可击。另一方面你的测试用例越严密, 就越驱使你写出更严密的代码。如上 divide 方法就是我根据测试用例反馈的结果后添加上去的。 然后我上面的测试逻辑上还是很不严密,比如: 没有考虑大数的溢出。 没有考虑无限循环小数。 这么一个简单的除法就有这么多 edge cases,如果是我们实际的业务的话,情况会更加复杂。因此写好测试从来都不是一件简单的事情。 给测试增加 lint(Add Linting)测试代码也是需要 lint 的。除了源码的一些 lint 规则,测试应该要加入一些独特的规则。 比如,你的测试代码只是把代码跑了一遍,没有进行任何断言。亦或者是直接断言expect(true.to.be(true)),都是不应该被允许的。 比如,断言的时候使用非全等,这也不好的实践。 再比如,使用toBeNull()断言,而不是: 12345expect(null).toBe(null);expect(null).toEqual(null);expect(null).toStrictEqual(null); … 类似的例子还有很多,总之测试代码也是需要 lint 的 ,并且相比于被测试代码,其应该有额外的特殊规则,来避免测试代码的腐烂问题。 CI本地测试(Local CI)可以仅对修改的文件进行测试,eg: 1jest -o 分阶段测试(Tags)我们可以按照一定分类标准对测试用例进行分类。 举个例子,我按照测试是否有 IO 将用例分为 IO 类型和 非 IO 类型。那么我就可以在提交的时候只执行非 IO 类型,这样反馈更快。等到我推送到远程的时候执行一次全量操作。 eg: 1234567describe(`\"face swiping\" -> alipay #io`, () => { it(\"it should go to http://www.alipay.com/identify when user choose alipay\", () => { // simulate click // do heavy io // expect });}); 我们可以这么做 1jest -t = \"#io\"; 同样,我可以按照其他纬度对用例进行切分,比如各种业务纬度。这在业务达到一定规模之后,收益非常明显。eg: 1jest -t = "[#io|#cold|#biz]"; 如上会仅测试有io,cold,biz 三个标签中的一个或者多个的用例。 文件夹和文件名本身也是一种 tag,合理利用可以减少很多工作。 框架相关(Framework)大家问的比较多的问题是如何测试视图,以及如何测试特定的某一种框架下的代码。 Vue一个典型的 Vue 项目可能有如下文件类型: html vue js ts json css 图片,音视频等媒体资源 如何对他们进行测试呢?JS 和 TS 我们暂时讨论,这个和框架相关性不大。而我们这里关心框架相关的 vue 文件和视图相关的文件。而json,图片,音视频等媒体资源是没有必要测试的。 那么如何测试 html,vue 和 css 文件呢?而大多数情况, 大家应用都是 CSR 的,html 只是一个傀儡文件,没有测试的价值。css 的话,如果要测试,只有两种情况,一种是对 CSSOM 进行测试,另外一种是对渲染树的内容进行测试。而一般大家都会对渲染树进行测试。为什么呢?留给大家来思考,欢迎文章后留言讨论。因此本文主要讨论 vue 文件,以及渲染树的测试。 实际上, vue 文件会导出一个 vue 的构造函数,并且合适的时候完成实例化和挂载的过程。而其真正渲染到中的时候,会把 template 标签,style 标签内容一并带过去,当然这中间有一些复杂逻辑存在,这不是本文重点,故不做延伸。 那么,对基于 vue 框架的应用测试主要关注一点,渲染树本身。 其实你用别的框架,或者不用框架也是一样的。 不同的是,vue 是一种基于数据驱动的框架。 1(props) => view; 因此我们是不是只要测试不同的 props 组合,是否展示我们期望的 view 就可以了? 是也不是。 我们先假定”是“。那么我们的问题转化为: 如何组合合适的 props 如何断言 view 是否正确渲染 对于第一个问题,这个是组件设计的时候应该考虑的事情。对于第二个问题,答案是 vue-test-utils。 vue-test-utils 本身就是解决这个问题的,如果我将一个 app 看成是组件的有机体(组件以及组件之间的通信协作),并将组件看成函数的话。那么vue-test-utils 的核心功能就是: 帮你执行这些函数。 改变函数内部的状态。 触发函数之间的通信。 。。。 vue-test-utils 的 wrapper 同时完成了上面两件事setProps 和 assert。vue-test-utils 还帮你做了很多事情, 比如组件嵌套(类似函数调用栈)如何测试,怎么 mock props,router 等。 一句话来说,就像是一双无形的手,帮你操作 app 的初始化, 挂载,更新,卸载等,并且直接或者间接提供断言机制。 更多可以参考 https://vue-test-utils.vuejs.org/ 以上内容基于一个事实 我们只要测试不同的 props 组合,是否展示我们期望的 view 就可以。然而, vue 虽然将其抽象为函数,但是要注意这个函数和我上文讲到的纯函数相差甚远,就连以函数式友好闻名的 React 也做不到这一点。 也就是说,你还需要考虑副作用。从这一点上来看,这是和我上文提到的最佳实践背离的。但是真正地将副作用全部抽离开的框架不怎么流行,比如 cyclejs, elm。因此我们必须接受这个事实。我们虽然无法避免这种事情的发生,但是我们可以限制其在我们可控制的范围,典型的技巧就是沙箱机制,这同样超出了本文的论述范围,故不做引申。 ReactTODO 其他(Others)Make it Red, Make it Green其实这就是测试驱动开发的本质。 先写用例,甭管飘红不飘红,先把测试用例写好,定义好问题边界。 然后一个个将红色的变成绿色。 再结合上面我提到的技巧,做持续集成。在你打字的时候可以执行的测试用例有哪些,在你提交到本地仓库的时候可以执行的用例有哪些。 参考(Reference) 两年前写的前端测试短文 eslint-plugin-jest","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"测试","slug":"前端/测试","permalink":"https://lucifer.ren/blog/categories/前端/测试/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"测试","slug":"测试","permalink":"https://lucifer.ren/blog/tags/测试/"},{"name":"单元测试","slug":"单元测试","permalink":"https://lucifer.ren/blog/tags/单元测试/"},{"name":"vue","slug":"vue","permalink":"https://lucifer.ren/blog/tags/vue/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第二期)","slug":"91-algo-2","date":"2020-10-18T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/10/19/91-algo-2/","link":"","permalink":"https://lucifer.ren/blog/2020/10/19/91-algo-2/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。 活动时间2020-11-01 至 2021-1-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少参与一次打卡 违反上述条件的人员会被强制清退 课程大纲 第一期部分公开的讲义: 【91 算法-基础篇】05.双指针 动态规划问题为什么要画表格? 二期会对题目和讲义进行再次加工,质量会更改, 敬请期待~ 基础篇(30 天) 数组,队列,栈 链表 树与递归 哈希表 双指针 进阶篇(30 天) 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 … 专题篇(31 天) 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 … 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定私有仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 本来计划做一个网站, 后面有一些意外情况, 暂时还是用 Github 私有仓库好了。 第二天会对前一天的题目进行讲解。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书,科学上网兑换码等 连续打卡七天可以获得补签卡一张哦 冲鸭报名开始时间待定。 采用 微信群的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 收费标准: 前 50 人免费 51 - 100 收费 5 元 101 - 500 收费 10 元 想要参与的小伙伴加我,发红包拉你进群。 微信号:DevelopeEngineer 需要注意的是,不管你是第几个进群,都需要先发红包才可以进群。只不过你进群之后发现不到 50 人, 可以联系我返现 10 元。大于 50 小于 100 可以找我返现 5 元。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"TypeScript 练习题(第二弹)","slug":"ts-exercises-2","date":"2020-10-12T16:00:00.000Z","updated":"2023-01-07T12:34:34.463Z","comments":true,"path":"2020/10/13/ts-exercises-2/","link":"","permalink":"https://lucifer.ren/blog/2020/10/13/ts-exercises-2/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(第一弹) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(第一弹) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言本文涉及的题目一共十六道,全部都可以在 typescript-exercises 上在线提交。 可以和标准答案进行对比。 并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 想重置进度,清空缓存,无痕模式或者换浏览器都可以。 题目中涉及到的知识点我基本也都在之前的文章中提到了,如果你没有看过,强烈建议先完成前面的教程,然后将上面的题目自己做一遍之后再看本文。另外一定要按照顺序读, 因此前面的题目都是后面的铺垫。 为了不让文章太过于冗长, 本篇文章分两次发布, 一次 8 道题,一共十五道。每道题都有思路,前置知识以及代码。 这次给大家带来的是后 6 道 其中有一道题需要大家有函数式编程的知识, 如果大家不知道会比较难以解释。 为了避免内容太过分散,将这道题从我的题解中移除,故只有 6 道。 题目九题目描述123456789101112131415161718192021222324Intro: PowerUsers idea was bad. Once those users got extended permissions, they started bullying others and we lost a lot of great users. As a response we spent all the remaining money on the marketing and got even more users. We need to start preparing to move everything to a real database. For now we just do some mocks. The server API format was decided to be the following: In case of success: { status: 'success', data: RESPONSE_DATA } In case of error: { status: 'error', error: ERROR_MESSAGE } The API engineer started creating types for this API and quickly figured out that the amount of types needed to be created is too big.Exercise: Remove UsersApiResponse and AdminsApiResponse types and use generic type ApiResponse in order to specify API response formats for each of the functions. 题目的大概意思是:之前都是写死的数据, 现在数据需要从接口拿,请你定义这个接口的类型。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" },];export type ApiResponse<T> = unknown;type AdminsApiResponse = | { status: \"success\"; data: Admin[]; } | { status: \"error\"; error: string; };export function requestAdmins(callback: (response: AdminsApiResponse) => void) { callback({ status: \"success\", data: admins, });}type UsersApiResponse = | { status: \"success\"; data: User[]; } | { status: \"error\"; error: string; };export function requestUsers(callback: (response: UsersApiResponse) => void) { callback({ status: \"success\", data: users, });}export function requestCurrentServerTime( callback: (response: unknown) => void) { callback({ status: \"success\", data: Date.now(), });}export function requestCoffeeMachineQueueLength( callback: (response: unknown) => void) { callback({ status: \"error\", error: \"Numeric value has exceeded Number.MAX_SAFE_INTEGER.\", });}function logPerson(person: Person) { console.log( ` - ${person.name}, ${person.age}, ${ person.type === \"admin\" ? person.role : person.occupation }` );}function startTheApp(callback: (error: Error | null) => void) { requestAdmins((adminsResponse) => { console.log(\"Admins:\"); if (adminsResponse.status === \"success\") { adminsResponse.data.forEach(logPerson); } else { return callback(new Error(adminsResponse.error)); } console.log(); requestUsers((usersResponse) => { console.log(\"Users:\"); if (usersResponse.status === \"success\") { usersResponse.data.forEach(logPerson); } else { return callback(new Error(usersResponse.error)); } console.log(); requestCurrentServerTime((serverTimeResponse) => { console.log(\"Server time:\"); if (serverTimeResponse.status === \"success\") { console.log( ` ${new Date(serverTimeResponse.data).toLocaleString()}` ); } else { return callback(new Error(serverTimeResponse.error)); } console.log(); requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse) => { console.log(\"Coffee machine queue length:\"); if (coffeeMachineQueueLengthResponse.status === \"success\") { console.log(` ${coffeeMachineQueueLengthResponse.data}`); } else { return callback(new Error(coffeeMachineQueueLengthResponse.error)); } callback(null); }); }); }); });}startTheApp((e: Error | null) => { console.log(); if (e) { console.log( `Error: \"${e.message}\", but it's fine, sometimes errors are inevitable.` ); } else { console.log(\"Success!\"); }}); 前置知识 泛型 回调函数 思路我们还是直接看报错。 很明显这个报错的原因是类型是 unknown, 因此我们只有将 unknown 改成正确的类型即可。 换句话说, 就是把这种地方改成正确类型即可。 题目描述说了, 这个 response 其实是从后端返回的。 而后端返回的数据有固定的格式。比如获取用户列表接口: 123456789type UsersApiResponse = | { status: \"success\"; data: User[]; } | { status: \"error\"; error: string; }; 其他接口也是类似, 不同的是 data 的类型。因此我们考虑使用泛型封装,将 data 的类型作为参数即可。 从本质上来说, 就是从后端取的数据有两种大的可能, 一种是错误, 一种是成功。两者在同一接口同一时刻只会出现一个,且必须出现一个。 而成功的情况又会随着接口不同从而可能产生不同的类型。 这是明显的使用 或逻辑关系 和泛型进行类型定义的强烈信号。我们可以使用泛型做如下改造: 123456789export type ApiResponse<T> = | { status: \"success\"; data: T; } | { status: \"error\"; error: string; }; 那么上面的 UsersApiResponse 就可以变成: 1type UsersApiResponse = ApiResponse<User[]>; 不懂的同学建议看下我之前的文章:- 你不知道的 TypeScript 泛型(万字长文,建议收藏) 用同样的套路把其他后端返回加上类型即可。 代码核心代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445export type ApiResponse<T> = | { status: \"success\"; data: T; } | { status: \"error\"; error: string; };export function requestAdmins( callback: (response: ApiResponse<Admin[]>) => void) { callback({ status: \"success\", data: admins, });}export function requestUsers( callback: (response: ApiResponse<User[]>) => void) { callback({ status: \"success\", data: users, });}export function requestCurrentServerTime( callback: (response: ApiResponse<number>) => void) { callback({ status: \"success\", data: Date.now(), });}export function requestCoffeeMachineQueueLength( callback: (response: ApiResponse<number>) => void) { callback({ status: \"error\", error: \"Numeric value has exceeded Number.MAX_SAFE_INTEGER.\", });} 题目十题目描述1234567891011121314151617181920212223242526272829303132Intro: We have asynchronous functions now, advanced technology. This makes us a tech startup officially now. But one of the consultants spoiled our dreams about inevitable future IT leadership. He said that callback-based asynchronicity is not popular anymore and everyone should use Promises. He promised that if we switch to Promises, this would bring promising results.Exercise: We don't want to reimplement all the data-requesting functions. Let's decorate the old callback-based functions with the new Promise-compatible result. The final function should return a Promise which would resolve with the final data directly (i.e. users or admins) or would reject with an error (or type Error). The function should be named promisify.Higher difficulty bonus exercise: Create a function promisifyAll which accepts an object with functions and returns a new object where each of the function is promisified. Rewrite api creation accordingly: const api = promisifyAll(oldApi); 题目大意是:前面用的是基于 callback 形式的代码, 他们对代码进行了重构,改造成了 Promise,让你对基于 Promise 的接口进行类型定义。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" },];export type ApiResponse<T> = | { status: \"success\"; data: T; } | { status: \"error\"; error: string; };export function promisify(arg: unknown): unknown { return null;}const oldApi = { requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) { callback({ status: \"success\", data: admins, }); }, requestUsers(callback: (response: ApiResponse<User[]>) => void) { callback({ status: \"success\", data: users, }); }, requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) { callback({ status: \"success\", data: Date.now(), }); }, requestCoffeeMachineQueueLength( callback: (response: ApiResponse<number>) => void ) { callback({ status: \"error\", error: \"Numeric value has exceeded Number.MAX_SAFE_INTEGER.\", }); },};export const api = { requestAdmins: promisify(oldApi.requestAdmins), requestUsers: promisify(oldApi.requestUsers), requestCurrentServerTime: promisify(oldApi.requestCurrentServerTime), requestCoffeeMachineQueueLength: promisify( oldApi.requestCoffeeMachineQueueLength ),};function logPerson(person: Person) { console.log( ` - ${person.name}, ${person.age}, ${ person.type === \"admin\" ? person.role : person.occupation }` );}async function startTheApp() { console.log(\"Admins:\"); (await api.requestAdmins()).forEach(logPerson); console.log(); console.log(\"Users:\"); (await api.requestUsers()).forEach(logPerson); console.log(); console.log(\"Server time:\"); console.log( ` ${new Date(await api.requestCurrentServerTime()).toLocaleString()}` ); console.log(); console.log(\"Coffee machine queue length:\"); console.log(` ${await api.requestCoffeeMachineQueueLength()}`);}startTheApp().then( () => { console.log(\"Success!\"); }, (e: Error) => { console.log( `Error: \"${e.message}\", but it's fine, sometimes errors are inevitable.` ); }); 前置知识 Promise promisify 泛型 高阶函数 思路题目给了一个 promisefy, 并且类型都是 unknown,不难看出, 它就是想让我们改造 promisefy 使其不报错, 并能正确推导类型。 123export function promisify(arg: unknown): unknown { return null;} 我们先不考虑这个类型怎么写,先把 promiify 实现一下再说。这需要你有一点高阶函数和 promise 的知识。由于这不是本文的重点,因此不赘述。 123456789export function promisify(fn) { return () => new Promise((resolve, reject) => { fn((response) => { if (response.status === \"success\") resolve(response.data); else reject(response.error); }); });} 接下来,我们需要给其增加类型签名。 这个 fn 实际上是一个函数,并且又接受一个 callback 作为参数。 因此大概是这个样子: 1((something) = void) => void 这里的 something 实际上我们在上一节已经解决了,直接套用即可。代码: 1(callback: (response: ApiResponse<T>) => void) => void 整体代码大概是: 12345export function promisify<T>( fn: (callback: (response: ApiResponse<T>) => void) => void): () => Promise<T> { // 上面的实现} 代码核心代码: 1234567891011export function promisify<T>( fn: (callback: (response: ApiResponse<T>) => void) => void): () => Promise<T> { return () => new Promise((resolve, reject) => { fn((response) => { if (response.status === \"success\") resolve(response.data); else reject(response.error); }); });} 第十一题题目描述12345678910111213141516171819Intro: In order to engage users in the communication with each other we have decided to decorate usernames in various ways. A brief search led us to a library called "str-utils". Bad thing is that it lacks TypeScript declarations.Exercise: Check str-utils module implementation at: node_modules/str-utils/index.js node_modules/str-utils/README.md Provide type declaration for that module in: declarations/str-utils/index.d.ts Try to avoid duplicates of type declarations, use type aliases. 题目的意思是他们用到了一个库 str-utils,这个库的人又没给我们写类型定义,于是我们不得不去自己写(好真实的例子啊)。 其实就是让我们实现以下函数的类型签名: 1234567import { strReverse, strToLower, strToUpper, strRandomize, strInvertCase,} from \"str-utils\"; 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081// declarations/str-utils/index.d.jsdeclare module \"str-utils\" { // export const ... // export function ...}// index.tsimport { strReverse, strToLower, strToUpper, strRandomize, strInvertCase,} from \"str-utils\";interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\" }, { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\" }, { type: \"admin\", name: \"Superwoman\", age: 28, role: \"Customer support\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\" }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\" }, { type: \"user\", name: \"Inspector Gadget\", age: 31, occupation: \"Undercover\" },];const isAdmin = (person: Person): person is Admin => person.type === \"admin\";const isUser = (person: Person): person is User => person.type === \"user\";export const nameDecorators = [ strReverse, strToLower, strToUpper, strRandomize, strInvertCase,];function logPerson(person: Person) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } const randomNameDecorator = nameDecorators[Math.round(Math.random() * (nameDecorators.length - 1))]; const name = randomNameDecorator(person.name); console.log(` - ${name}, ${person.age}, ${additionalInformation}`);}([] as Person[]).concat(users, admins).forEach(logPerson);// In case if you are stuck:// https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules 前置知识 如何给缺乏类型定义的第三方库定义类型 思路这个题目的考点就是如何给缺乏类型定义的第三方库定义类型。 这个时候我们只要新建一个文件然后加入以下代码即可。 12345declare module \"str-utils\" { // 在这里定义类型 // export const ... // export function ...} 其中 str-utils 是那个可恶的没有类型定义的库的名字。 有了这个知识,我们的代码就简单了。 代码123456789declare module \"str-utils\" { // export const ... // export function ... export function strReverse(s: string): string; export function strToLower(s: string): string; export function strToUpper(s: string): string; export function strRandomize(s: string): string; export function strInvertCase(s: string): string;} 第十二题题目描述12345678910111213141516171819202122232425262728293031323334Intro: We have so many users and admins in the database! CEO's father Jeff says that we are a BigData startup now. We have no idea what it means, but Jeff says that we need to do some statistics and analytics. We've ran a questionnaire within the team to figure out what do we know about statistics. The only person who filled it was our coffee machine maintainer. The answers were: * Maximums * Minumums * Medians * Averages We found a piece of code on stackoverflow and compiled it into a module `stats`. The bad thing is that it lacks type declarations.Exercise: Check stats module implementation at: node_modules/stats/index.js node_modules/stats/README.md Provide type declaration for that module in: declarations/stats/index.d.tsHigher difficulty bonus exercise: Avoid duplicates of type declarations. 题目大概意思是又来了一个库,这个库又没有写定义,我们又要自己写。 (真实++) 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142// declartions/stats/index.d.tsdeclare module \"stats\" { export function getMaxIndex(input: unknown, comparator: unknown): unknown;}// index.tsimport { getMaxIndex, getMaxElement, getMinIndex, getMinElement, getMedianIndex, getMedianElement, getAverageValue,} from \"stats\";interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\" }, { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\" }, { type: \"admin\", name: \"Superwoman\", age: 28, role: \"Customer support\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\" }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\" }, { type: \"user\", name: \"Inspector Gadget\", age: 31, occupation: \"Undercover\" },];function logUser(user: User | null) { if (!user) { console.log(\" - none\"); return; } const pos = users.indexOf(user) + 1; console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`);}function logAdmin(admin: Admin | null) { if (!admin) { console.log(\" - none\"); return; } const pos = admins.indexOf(admin) + 1; console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`);}const compareUsers = (a: User, b: User) => a.age - b.age;const compareAdmins = (a: Admin, b: Admin) => a.age - b.age;const colorizeIndex = (value: number) => String(value + 1);export { getMaxIndex, getMaxElement, getMinIndex, getMinElement, getMedianIndex, getMedianElement, getAverageValue,};console.log(\"Youngest user:\");logUser(getMinElement(users, compareUsers));console.log( ` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register`);console.log();console.log(\"Median user:\");logUser(getMedianElement(users, compareUsers));console.log( ` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register`);console.log();console.log(\"Oldest user:\");logUser(getMaxElement(users, compareUsers));console.log( ` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register`);console.log();console.log(\"Average user age:\");console.log( ` - ${String(getAverageValue(users, ({ age }: User) => age))} years`);console.log();console.log(\"Youngest admin:\");logAdmin(getMinElement(admins, compareAdmins));console.log( ` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register`);console.log();console.log(\"Median admin:\");logAdmin(getMedianElement(admins, compareAdmins));console.log( ` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register`);console.log();console.log(\"Oldest admin:\");logAdmin(getMaxElement(admins, compareAdmins));console.log( ` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register`);console.log();console.log(\"Average admin age:\");console.log( ` - ${String(getAverageValue(admins, ({ age }: Admin) => age))} years`); 前置知识 泛型 高阶函数 如何给缺乏类型定义的第三方库定义类型 思路和上面的思路类似。 唯一的不同的是这道题的需要实现的几个方法支持不同的入参类型。 123456789import { getMaxIndex, getMaxElement, getMinIndex, getMinElement, getMedianIndex, getMedianElement, getAverageValue,} from \"stats\"; 因此,我们考虑使用泛型来定义。 知道了这个, 代码就不难写。 这是最最基本的泛型, 比我们前面写的还简单。 代码123456789101112131415161718192021222324252627282930declare module \"stats\" { export function getMaxIndex<T>( input: T[], comparator: (a: T, b: T) => number ): number; export function getMaxElement<T>( input: T[], comparator: (a: T, b: T) => number ): T; export function getMinElement<T>( input: T[], comparator: (a: T, b: T) => number ): T; export function getMedianIndex<T>( input: T[], comparator: (a: T, b: T) => number ): number; export function getMedianElement<T>( input: T[], comparator: (a: T, b: T) => number ): T; export function getAverageValue<T>( input: T[], getValue: (a: T) => number ): number; export function getMinIndex<T>( input: T[], comparator: (a: T, b: T) => number ): number;} 第十三题题目描述123456789101112131415161718192021222324Intro: The next logical step for us is to provide more precise registration date for our users and admins. We've approximately made up dates for each user and admin and used a library called "date-wizard" in order to pretty-format the dates. Unfortunately, type declarations which came with "date-wizard" library were incomplete. 1. DateDetails interface is missing time related fields such as hours, minutes and seconds. 2. Function "pad" is exported but not declared.Exercise: Check date-wizard module implementation at: node_modules/date-wizard/index.js node_modules/date-wizard/index.d.ts Extend type declaration of that module in: module-augmentations/date-wizard/index.ts 题目大概意思是又来了一个库,这个库又没有写定义,我们又要自己写。 (真实+++++++++++++) 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146// module-augmentations/data-wizard/index.d.ts// This enables module augmentation mode.import \"date-wizard\";declare module \"date-wizard\" { // Add your module extensions here.}// index.tsimport * as dateWizard from \"date-wizard\";import \"./module-augmentations/date-wizard\";interface User { type: \"user\"; name: string; age: number; occupation: string; registered: Date;}interface Admin { type: \"admin\"; name: string; age: number; role: string; registered: Date;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\", registered: new Date(\"2016-06-01T16:23:13\"), }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\", registered: new Date(\"2017-02-11T12:12:11\"), }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\", registered: new Date(\"2018-01-05T11:02:30\"), }, { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\", registered: new Date(\"2018-08-12T10:01:24\"), }, { type: \"admin\", name: \"Superwoman\", age: 28, role: \"Customer support\", registered: new Date(\"2019-03-25T07:51:05\"), },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", registered: new Date(\"2016-02-15T09:25:13\"), }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", registered: new Date(\"2016-03-23T12:47:03\"), }, { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\", registered: new Date(\"2017-02-19T17:22:56\"), }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\", registered: new Date(\"2018-02-25T19:44:28\"), }, { type: \"user\", name: \"Inspector Gadget\", age: 31, occupation: \"Undercover\", registered: new Date(\"2019-03-25T09:29:12\"), },];const isAdmin = (person: Person): person is Admin => person.type === \"admin\";const isUser = (person: Person): person is User => person.type === \"user\";function logPerson(person: Person, index: number) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } let registeredAt = dateWizard( person.registered, \"{date}.{month}.{year} {hours}:{minutes}\" ); let num = `#${dateWizard.pad(index + 1)}`; console.log( ` - ${num}: ${person.name}, ${person.age}, ${additionalInformation}, ${registeredAt}` );}export { dateWizard };console.log(\"All users:\");([] as Person[]).concat(users, admins).forEach(logPerson);console.log();console.log(\"Early birds:\");([] as Person[]) .concat(users, admins) .filter((person) => dateWizard.dateDetails(person.registered).hours < 10) .forEach(logPerson);// In case if you are stuck:// https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules// https://www.typescriptlang.org/docs/handbook/declaration-merging.html 前置知识 interface 或 type 声明自定义类型 如何给缺乏类型定义的第三方库定义类型 思路和上面两道题思路一样, 不用多说了吧? 代码1234567891011121314151617// This enables module augmentation mode.import \"date-wizard\";declare module \"date-wizard\" { // Add your module extensions here. function dateWizard(date: string, format: string): string; function pad(s: number): string; interface DateDetails { year: number; month: number; date: number; hours: number; minutes: number; seconds: number; } function dateDetails(date: Date): DateDetails;} 第十四题需要大家有函数式编程的知识, 如果大家不知道会比较难以解释。 为了避免内容太过分散,将这道题从我的题解中移除。 对函数式编程感兴趣的,也可是看下我之前写的文章 函数式编程系列教程。 第十五题题目描述123456789101112131415161718192021222324Intro: Our attempt to Open Source didn't work quite as expected. It turned out there were already many existing functional JS libraries. All the remaining developers left the company as well. It seems that they are joining a very ambitious startup which re-invented a juicer and raised millions of dollars. Too bad we cannot compete with this kind of financing even though we believe our idea is great. It's time to shine for the last time and publish our new invention: object-constructor as our CTO named it. A small library which helps manipulating an object.Exercise: Here is a library which helps manipulating objects. We tried to write type annotations and we failed. Please help! 题目大概意思是函数式编程他们 hold 不住,于是又准备切换到面向对象编程。 于是你需要补充类型定义使得代码不报错。 题目内置代码123456789101112131415161718192021export class ObjectManipulator { constructor(protected obj) {} public set(key, value) { return new ObjectManipulator({ ...this.obj, [key]: value }); } public get(key) { return this.obj[key]; } public delete(key) { const newObj = { ...this.obj }; delete newObj[key]; return new ObjectManipulator(newObj); } public getObject() { return this.obj; }} 前置知识 泛型 Omit 泛型 ES6 class keyof 使用 extends 进行泛型约束 联合类型 思路这道题难度颇高,比前面的泛型题目都要难。 也是本系列的压轴题,我们重点讲一下。 首先题目有五个报错位置, 报错信息都是隐式使用了 any , 因此我们的思路就是将五个地方显式声明类型即可。 从它的名字 ObjectManipulator 以及 api 可以看出, 它应该可以存储任何对象,因此使用泛型定义就不难想到。 你也可是把这个 ObjectManipulator 想象成抽象包包。 你的期望是限量款包包拍照的时候用,普通包包和闺蜜逛街的时候用,优衣库送的包包逛超市的时候用等等。 ObjectManipulator 是一个抽象的包包概念,不是具体的包, 比如当你买一个 LV 的包包的时候就是 ObjectManipulator<LVBag>。这样当你往 LV 里放超市买的水果的时候就可以报错:你怎么可以用 LV 包包装这样东西呢?你应该用 ta 装*。 当然这个例子很不严谨, 这个只是帮助大家快速理解而已,切莫较真。 理解了题意,我们就可以开始写了。 我们先改第一个错 - 构造函数 constructor, 这个错比较简单。 123456export class ObjectManipulator<T> { constructor(protected obj: T) { this.obj = obj; } ...} 这个时候经过 ObjectManipulator 实例化产生的对象的 this.obj 都是 T 类型,其中 T 是泛型。因此 getObject 的错也不难改,返回值写 T 就行。 123456export class ObjectManipulator<T> { ... public getObject(): T { return this.obj; }} 剩下的 get,set 和 delete 思路有点类似。 先拿 get 来说: 1234567export class ObjectManipulator<T> { ... public get(key) { return this.obj[key]; } ...} 这个怎么写类型呢? key 理论上可是是任何值,返回值理论上也可以是任何值。但是一旦类型 T 确定了, 那么实际上 key 和返回值就不是任意值了。 比如: 12type A = ObjectManipulator<{ name: string; age: number }>;const a: A = new ObjectManipulator({ name: \"\", age: 17 }); 如上代码中的 A 是 ObjectManipulator 传入具体类型 { name: string; age: number } 产生的新的类型。 我这里用的是行内类型, 实际项目建议使用 interface 或者 type 定义类型。 之后我们模拟一些操作: 123456a.set(\"name\", \"脑洞前端\");a.get(\"name\");a.get(\"name123\"); // 期望报错a.set(\"name123\", \"脑洞\");a.delete(\"name123\"); // 期望报错a.delete(\"name\"); 实际上,我可能期望的是其中一些行为可以借助 TypeScript 的类型分析直接报错。 简单来说,我的期望是 get 和 delete 不在 T 中的 key 都报错。 当然你的真实项目也可以不认同我的观点, 比如 get 一个不在 T 中定义的 key 也可以,但是我还是推荐你这么做。 知道了这个, 再结合我之前有关泛型的文章就不难写出来。 其中 get 和 delete 的代码: 1234567891011export class ObjectManipulator<T> { public get<K extends keyof T>(key: K): T[K] { return this.obj[key]; } public delete<K extends keyof T>(key: K): ObjectManipulator<Omit<T, K>> { const newObj = { ...this.obj }; delete newObj[key]; return new ObjectManipulator(newObj); }} 最后是 set,其实一开始我的 set 是这么写的。 12345678export class ObjectManipulator<T> { public set<K extends keyof T, V>(key: K, value: V): ObjectManipulator<T> { return new ObjectManipulator({ ...this.obj, [key]: value, }) as ObjectManipulator<T & { [k in K]: V }>; }} 但是无奈没有通过官方的测试用例。 实际项目我其实更推荐我上面的这种写法。下面是我为了通过所有的测试用例写的方法。 经过分析, 我发现它期望的是 set 中的 key 可以不是 T 中的。这一点从官方给的测试用例就可以看出来。 因此我将代码改成 K 放宽到任意 string,返回值做了一个联合类型。代码: 12345678910111213export class ObjectManipulator<T> { ... public set<K extends string, V>( key: K, value: V ): ObjectManipulator<T & { [k in K]: V }> { return new ObjectManipulator({ ...this.obj, [key]: value, }) as ObjectManipulator<T & { [k in K]: V }>; } ...} 终于通过了所有的测试用例。 代码12345678910111213141516171819202122232425262728export class ObjectManipulator<T> { constructor(protected obj: T) { this.obj = obj; } public set<K extends string, V>( key: K, value: V ): ObjectManipulator<T & { [k in K]: V }> { return new ObjectManipulator({ ...this.obj, [key]: value, }) as ObjectManipulator<T & { [k in K]: V }>; } public get<K extends keyof T>(key: K): T[K] { return this.obj[key]; } public delete<K extends keyof T>(key: K): ObjectManipulator<Omit<T, K>> { const newObj = { ...this.obj }; delete newObj[key]; return new ObjectManipulator(newObj); } public getObject(): T { return this.obj; }} 总结以上就是给大家带来的题目解析。 这六道题的考点有,按照我个人理解的重要程度划分为: type 和 interface 的基本操作(必须掌握) 如何给缺乏类型定义的第三方库定义类型(必须掌握) 联合类型 和 交叉类型(强烈建议掌握) 类型断言和类型收缩(强烈建议掌握) 泛型和常见内置泛型(强烈建议掌握) 高阶函数的类型定义(强烈建议掌握) 最后祝愿大家告别 anyscript,成为 TypeScript 魔法师。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"西法喊你参加模拟面试~","slug":"mock-interview","date":"2020-10-06T16:00:00.000Z","updated":"2023-01-05T12:24:50.072Z","comments":true,"path":"2020/10/07/mock-interview/","link":"","permalink":"https://lucifer.ren/blog/2020/10/07/mock-interview/","excerpt":"很多粉丝向西法我反应:做了很多题。看到新的题目还是不会, 看了题解之后又觉得自己会,但是自己写又写不出来。 这个现象实际上很常见, 破局的最有效方法就是多做题。 是真正地做出来,而不是看了会了,要自己从零 coding。 我之前讲过对于新手的建议是按 tag 刷, 对于老手或者马上要面试的我建议随机刷。 今天我在补充一句,那就是不管大家用哪种方式刷,建议大家都通过模拟面试或者竞赛的形式刷。 时间条件允许的可以参加周赛,不允许的则可以模拟面试。","text":"很多粉丝向西法我反应:做了很多题。看到新的题目还是不会, 看了题解之后又觉得自己会,但是自己写又写不出来。 这个现象实际上很常见, 破局的最有效方法就是多做题。 是真正地做出来,而不是看了会了,要自己从零 coding。 我之前讲过对于新手的建议是按 tag 刷, 对于老手或者马上要面试的我建议随机刷。 今天我在补充一句,那就是不管大家用哪种方式刷,建议大家都通过模拟面试或者竞赛的形式刷。 时间条件允许的可以参加周赛,不允许的则可以模拟面试。 如果你是新手, 可以按照 tag 自定义模拟面试, 否则可以随机或者针对公司模拟面试。 今天做了力扣几套模拟面试的题目,入口在力扣主页上方的面试导航。如果没有 plus 会员的话,可以随机模拟面试,题目随机抽取。如果你有会员则可以选择特定公司的真题进行模拟。由于有时间限制, 如果你在准备面试的话, 提前一个月每天做一套题,锻炼自己的解题能力。 如果你没有 plus ,还想白嫖真题模拟面试也不是不可以。 你可以找个知道哪些题是哪个公司的人,然后自定义模拟面试。 知道哪些题是哪个公司的人,网上有一些好心人贡献,将某一个公司的题目做成一个集合供大家免费查看。我的力扣刷题插件也做了一部分, 目前还不完全,不嫌弃的也可以用我的刷题插件看。 插件获取方式: 关注公众号力扣加加,回复插件即可。 另外国际版的力扣有一个讨论专区,里面的题目质量和可信度都很高。 地址:https://leetcode.com/discuss/interview-question?currentPage=1&orderBy=hot&query= 要求你在限定时间解决三道题目,难度是不确定的,有可能两道简单,也可能两道困难。 模拟面试中, 大家也可以练习一些可耻但是管用的小技巧。 比如打表: 如上是一个经典的打表解法,如果没有人工判卷,算是一个可耻但有用的技巧。 其他鸡贼技巧,西法有时间再给大家整理。 最后,之后的模拟面试考虑使用力扣的模拟面试形式进行。如果想参加我的模拟面试的,可以加我的模拟面试群,然后群里预约即可。加群方式,关注公众号力扣加加,回复模拟面试。","categories":[{"name":"模拟面试","slug":"模拟面试","permalink":"https://lucifer.ren/blog/categories/模拟面试/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"模拟面试","slug":"模拟面试","permalink":"https://lucifer.ren/blog/tags/模拟面试/"}]},{"title":"你不知道的前端异常处理(万字长文,建议收藏)","slug":"error-catch","date":"2020-10-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.726Z","comments":true,"path":"2020/10/02/error-catch/","link":"","permalink":"https://lucifer.ren/blog/2020/10/02/error-catch/","excerpt":"除了调试,处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道,就好像我们天天和 Bug 打交道一样。因此正确认识异常,并作出合适的异常处理就显得很重要了。 我们先尝试抛开前端这个限定条件,来看下更广泛意义上程序的报错以及异常处理。不管是什么语言,都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常,并针对其做相应的异常处理。 然而,很多人对异常的处理方式是事后修补,即某个异常发生的时候,增加对应的条件判断,这真的是一种非常低效的开发方式,非常不推荐大家这么做。那么究竟如何正确处理异常呢?由于不同语言有不同的特性,因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就前端异常进行详细阐述,但是读者也可以稍加修改延伸到其他各个领域。 本文讨论的异常指的是软件异常,而非硬件异常。","text":"除了调试,处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道,就好像我们天天和 Bug 打交道一样。因此正确认识异常,并作出合适的异常处理就显得很重要了。 我们先尝试抛开前端这个限定条件,来看下更广泛意义上程序的报错以及异常处理。不管是什么语言,都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常,并针对其做相应的异常处理。 然而,很多人对异常的处理方式是事后修补,即某个异常发生的时候,增加对应的条件判断,这真的是一种非常低效的开发方式,非常不推荐大家这么做。那么究竟如何正确处理异常呢?由于不同语言有不同的特性,因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就前端异常进行详细阐述,但是读者也可以稍加修改延伸到其他各个领域。 本文讨论的异常指的是软件异常,而非硬件异常。 什么是异常用直白的话来解释异常的话,就是程序发生了意想不到的情况,这种情况影响到了程序的正确运行。 从根本上来说,异常就是一个数据结构,其保存了异常发生的相关信息,比如错误码,错误信息等。以 JS 中的标准内置对象 Error 为例,其标准属性有 name 和 message。然而不同的浏览器厂商有自己的自定义属性,这些属性并不通用。比如 Mozilla 浏览器就增加了 filename 和 stack 等属性。 值得注意的是错误只有被抛出,才会产生异常,不被抛出的错误不会产生异常。比如: 123456function t() { console.log(\"start\"); new Error(); console.log(\"end\");}t(); (动画演示) 这段代码不会产生任何的异常,控制台也不会有任何错误输出。 异常的分类按照产生异常时程序是否正在运行,我们可以将错误分为编译时异常和运行时异常。 编译时异常指的是源代码在编译成可执行代码之前产生的异常。而运行时异常指的是可执行代码被装载到内存中执行之后产生的异常。 编译时异常我们知道 TS 最终会被编译成 JS,从而在 JS Runtime中执行。既然存在编译,就有可能编译失败,就会有编译时异常。 比如我使用 TS 写出了如下代码: 1const s: string = 123; 这很明显是错误的代码, 我给 s 声明了 string 类型,但是却给它赋值 number。 当我使用 tsc(typescript 编译工具,全称是 typescript compiler)尝试编译这个文件的时候会有异常抛出: 12345678tsc a.tsa.ts:1:7 - error TS2322: Type '123' is not assignable to type 'string'.1 const s: string = 123; ~Found 1 error. 这个异常就是编译时异常,因为我的代码还没有执行。 然而并不是你用了 TS 才存在编译时异常,JS 同样有编译时异常。有的人可能会问 JS 不是解释性语言么?是边解释边执行,没有编译环节,怎么会有编译时异常? 别急,我举个例子你就明白了。如下代码: 123456function t() { console.log('start') await sa console.log('end')}t() 上面的代码由于存在语法错误,不会编译通过,因此并不会打印start,侧面证明了这是一个编译时异常。尽管 JS 是解释语言,也依然存在编译阶段,这是必然的,因此自然也会有编译异常。 总的来说,编译异常可以在代码被编译成最终代码前被发现,因此对我们的伤害更小。接下来,看一下令人心生畏惧的运行时异常。 运行时异常相信大家对运行时异常非常熟悉。这恐怕是广大前端碰到最多的异常类型了。众所周知的 NPE(Null Pointer Exception) 就是运行时异常。 将上面的例子稍加改造,得到下面代码: 123456function t() { console.log(\"start\"); throw 1; console.log(\"end\");}t(); (动画演示) 注意 end 没有打印,并且 t 没有弹出栈。实际上 t 最终还是会被弹出的,只不过和普通的返回不一样。 如上,则会打印出start。由于异常是在代码运行过程中抛出的,因此这个异常属于运行时异常。相对于编译时异常,这种异常更加难以发现。上面的例子可能比较简单,但是如果我的异常是隐藏在某一个流程控制语句(比如 if else)里面呢?程序就可能在客户的电脑走入那个抛出异常的 if 语句,而在你的电脑走入另一条。这就是著名的 《在我电脑上好好的》 事件。 异常的传播异常的传播和我之前写的浏览器事件模型有很大的相似性。只不过那个是作用在 DOM 这样的数据结构,这个则是作用在函数调用栈这种数据结构,并且事件传播存在捕获阶段,异常传播是没有的。不同 C 语言,JS 中异常传播是自动的,不需要程序员手动地一层层传递。如果一个异常没有被 catch,它会沿着函数调用栈一层层传播直到栈空。 异常处理中有两个关键词,它们是throw(抛出异常) 和 catch(处理异常)。 当一个异常被抛出的时候,异常的传播就开始了。异常会不断传播直到遇到第一个 catch。 如果程序员没有手动 catch,那么一般而言程序会抛出类似unCaughtError,表示发生了一个异常,并且这个异常没有被程序中的任何 catch 语言处理。未被捕获的异常通常会被打印在控制台上,里面有详细的堆栈信息,从而帮助程序员快速排查问题。实际上我们的程序的目标是避免 unCaughtError这种异常,而不是一般性的异常。 一点小前提由于 JS 的 Error 对象没有 code 属性,只能根据 message 来呈现,不是很方便。我这里进行了简单的扩展,后面很多地方我用的都是自己扩展的 Error ,而不是原生 JS Error ,不再赘述。 123456oldError = Error;Error = function ({ code, message, fileName, lineNumber }) { error = new oldError(message, fileName, lineNumber); error.code = code; return error;}; 手动抛出 or 自动抛出异常既可以由程序员自己手动抛出,也可以由程序自动抛出。 1throw new Error(`I'm Exception`); (手动抛出的例子) 12a = null;a.toString(); // Thrown: TypeError: Cannot read property 'toString' of null (程序自动抛出的例子) 自动抛出异常很好理解,毕竟我们哪个程序员没有看到过程序自动抛出的异常呢? “这个异常突然就跳出来!吓我一跳!”,某不知名程序员如是说。 那什么时候应该手动抛出异常呢? 一个指导原则就是你已经预知到程序不能正确进行下去了。比如我们要实现除法,首先我们要考虑的是被除数为 0 的情况。当被除数为 0 的时候,我们应该怎么办呢?是抛出异常,还是 return 一个特殊值?答案是都可以,你自己能区分就行,这没有一个严格的参考标准。 我们先来看下抛出异常,告诉调用者你的输入,我处理不了这种情况。 12345678910111213141516171819function divide(a, b) { a = +a; b = +b; // 转化成数字 if (!b) { // 匹配 +0, -0, NaN throw new Error({ code: 1, message: \"Invalid dividend \" + b, }); } if (Number.isNaN(a)) { // 匹配 NaN throw new Error({ code: 2, message: \"Invalid divisor \" + a, }); } return a / b;} 上面代码会在两种情况下抛出异常,告诉调用者你的输入我处理不了。由于这两个异常都是程序员自动手动抛出的,因此是可预知的异常。 刚才说了,我们也可以通过返回值来区分异常输入。我们来看下返回值输入是什么,以及和异常有什么关系。 异常 or 返回如果是基于异常形式(遇到不能处理的输入就抛出异常)。当别的代码调用divide的时候,需要自己 catch。 12345678910111213function t() { try { divide(\"foo\", \"bar\"); } catch (err) { if (err.code === 1) { return console.log(\"被除数必须是除0之外的数\"); } if (err.code === 2) { return console.log(\"除数必须是数字\"); } throw new Error(\"不可预知的错误\"); }} 然而就像上面我说的那样,divide 函数设计的时候,也完全可以不用异常,而是使用返回值来区分。 12345678910111213141516171819function divide(a, b) { a = +a; b = +b; // 转化成数字 if (!b) { // 匹配 +0, -0, NaN return new Error({ code: 1, message: \"Invalid dividend \" + b, }); } if (Number.isNaN(a)) { // 匹配 NaN return new Error({ code: 2, message: \"Invalid divisor \" + a, }); } return a / b;} 当然,我们使用方式也要作出相应改变。 1234567891011function t() { const res = divide(\"foo\", \"bar\"); if (res.code === 1) { return console.log(\"被除数必须是除0之外的数\"); } if (res.code === 2) { return console.log(\"除数必须是数字\"); } return new Error(\"不可预知的错误\");} 这种函数设计方式和抛出异常的设计方式从功能上说都是一样的,只是告诉调用方的方式不同。如果你选择第二种方式,而不是抛出异常,那么实际上需要调用方书写额外的代码,用来区分正常情况和异常情况,这并不是一种良好的编程习惯。 然而在 Go 等返回值可以为复数的语言中,我们无需使用上面蹩脚的方式,而是可以: 1234res, err := divide(\"foo\", \"bar\");if err != nil { log.Fatal(err)} 这是和 Java 和 JS 等语言使用的 try catch 不一样的的地方,Go 是通过 panic recover defer 机制来进行异常处理的。感兴趣的可以去看看 Go 源码关于错误测试部分 可能大家对 Go 不太熟悉。没关系,我们来继续看下 shell。实际上 shell 也是通过返回值来处理异常的,我们可以通过 $? 拿到上一个命令的返回值,这本质上也是一种调用栈的传播行为,而且是通过返回值而不是捕获来处理异常的。 作为函数返回值处理和 try catch 一样,这是语言的设计者和开发者共同决定的一件事情。 上面提到了异常传播是作用在函数调用栈上的。当一个异常发生的时候,其会沿着函数调用栈逐层返回,直到第一个 catch 语句。当然 catch 语句内部仍然可以触发异常(自动或者手动)。如果 catch 语句内部发生了异常,也一样会沿着其函数调用栈继续执行上述逻辑,专业术语是 stack unwinding。 实际上并不是所有的语言都会进行 stack unwinding,这个我们会在接下来的《运行时异常可以恢复么?》部分讲解。 伪代码来描述一下: 12345678function bubble(error, fn) { if (fn.hasCatchBlock()) { runCatchCode(error); } if (callstack.isNotEmpty()) { bubble(error, callstack.pop()); }} 从我的伪代码可以看出所谓的 stack unwinding 其实就是 callstack.pop() 这就是异常传播的一切!仅此而已。 异常的处理我们已经了解来异常的传播方式了。那么接下来的问题是,我们应该如何在这个传播过程中处理异常呢? 我们来看一个简单的例子: 12345678910function a() { b();}function b() { c();}function c() { throw new Error(\"an error occured\");}a(); 我们将上面的代码放到 chrome 中执行, 会在控制台显示如下输出: 我们可以清楚地看出函数的调用关系。即错误是在 c 中发生的,而 c 是 b 调用的,b 是 a 调用的。这个函数调用栈是为了方便开发者定位问题而存在的。 上面的代码,我们并没有 catch 错误,因此上面才会有uncaught Error。 那么如果我们 catch ,会发生什么样的变化呢?catch 的位置会对结果产生什么样的影响?在 a ,b,c 中 catch 的效果是一样的么? 我们来分别看下: 1234567891011121314function a() { b();}function b() { c();}function c() { try { throw new Error(\"an error occured\"); } catch (err) { console.log(err); }}a(); (在 c 中 catch) 我们将上面的代码放到 chrome 中执行, 会在控制台显示如下输出: 可以看出,此时已经没有uncaught Error啦,仅仅在控制台显示了标准输出,而非错误输出(因为我用的是 console.log,而不是 console.error)。然而更重要是的是,如果我们没有 catch,那么后面的同步代码将不会执行。 比如在 c 的 throw 下面增加一行代码,这行代码是无法被执行的,无论这个错误有没有被捕获。 12345678function c() { try { throw new Error(\"an error occured\"); console.log(\"will never run\"); } catch (err) { console.log(err); }} 我们将 catch 移动到 b 中试试看。 123456789101112131415function a() { b();}function b() { try { c(); } catch (err) { console.log(err); }}function c() { throw new Error(\"an error occured\");}a(); (在 b 中 catch) 在这个例子中,和上面在 c 中捕获没有什么本质不同。其实放到 a 中捕获也是一样,这里不再贴代码了,感兴趣的自己试下。 既然处于函数调用栈顶部的函数报错, 其函数调用栈下方的任意函数都可以进行捕获,并且效果没有本质不同。那么问题来了,我到底应该在哪里进行错误处理呢? 答案是责任链模式。我们先来简单介绍一下责任链模式,不过细节不会在这里展开。 假如 lucifer 要请假。 如果请假天数小于等于 1 天,则主管同意即可 如果请假大于 1 天,但是小于等于三天,则需要 CTO 同意。 如果请假天数大于三天,则需要老板同意。 这就是一个典型的责任链模式。谁有责任干什么事情是确定的,不要做自己能力范围之外的事情。比如主管不要去同意大于 1 天的审批。 举个例子,假设我们的应用有三个异常处理类,它们分别是:用户输入错误,网络错误 和 类型错误。如下代码,当代码执行的时候会报错一个用户输入异常。这个异常没有被 C 捕获,会 unwind stack 到 b,而 b 中 catch 到这个错误之后,通过查看 code 值判断其可以被处理,于是打印I can handle this。 123456789101112131415161718192021222324252627282930function a() { try { b(); } catch (err) { if (err.code === \"NETWORK_ERROR\") { return console.log(\"I can handle this\"); } // can't handle, pass it down throw err; }}function b() { try { c(); } catch (err) { if (err.code === \"INPUT_ERROR\") { return console.log(\"I can handle this\"); } // can't handle, pass it down throw err; }}function c() { throw new Error({ code: \"INPUT_ERROR\", message: \"an error occured\", });}a(); 而如果 c 中抛出的是别的异常,比如网络异常,那么 b 是无法处理的,虽然 b catch 住了,但是由于你无法处理,因此一个好的做法是继续抛出异常,而不是吞没异常。不要畏惧错误,抛出它。只有没有被捕获的异常才是可怕的,如果一个错误可以被捕获并得到正确处理,它就不可怕。 举个例子: 12345678910111213141516171819202122232425262728function a() { try { b(); } catch (err) { if (err.code === \"NETWORK_ERROR\") { return console.log(\"I can handle this\"); } // can't handle, pass it down throw err; }}function b() { try { c(); } catch (err) { if (err.code === \"INPUT_ERROR\") { return console.log(\"I can handle this\"); } }}function c() { throw new Error({ code: \"NETWORK_ERROR\", message: \"an error occured\", });}a(); 如上代码不会有任何异常被抛出,它被完全吞没了,这对我们调试问题简直是灾难。因此切记不要吞没你不能处理的异常。正确的做法应该是上面讲的那种只 catch 你可以处理的异常,而将你不能处理的异常 throw 出来,这就是责任链模式的典型应用。 这只是一个简单的例子,就足以绕半天。实际业务肯定比这个复杂多得多。因此异常处理绝对不是一件容易的事情。 如果说谁来处理是一件困难的事情,那么在异步中决定谁来处理异常就是难上加难,我们来看下。 同步与异步同步异步一直是前端难以跨越的坎,对于异常处理也是一样。以 NodeJS 中用的比较多的读取文件 API 为例。它有两个版本,一个是异步,一个是同步。同步读取仅仅应该被用在没了这个文件无法进行下去的时候。比如读取一个配置文件。而不应该在比如浏览器中读取用户磁盘上的一个图片等,这样会造成主线程阻塞,导致浏览器卡死。 1234// 同步读取文件fs.readFileSync();// 异步读取文件fs.readFile(); 当我们试图同步读取一个不存在的文件的时候,会抛出以下异常: 1234567891011fs.readFileSync('something-not-exist.lucifer');console.log('脑洞前端');Thrown:Error: ENOENT: no such file or directory, open 'something-not-exist.lucifer' at Object.openSync (fs.js:446:3) at Object.readFileSync (fs.js:348:35) { errno: -2, syscall: 'open', code: 'ENOENT', path: 'something-not-exist.lucifer'} 并且脑洞前端是不会被打印出来的。这个比较好理解,我们上面已经解释过了。 而如果以异步方式的话: 123456789101112fs.readFile('something-not-exist.lucifer', (err, data) => {if(err) {throw err}});console.log('lucifer')luciferundefinedThrown:[Error: ENOENT: no such file or directory, open 'something-not-exist.lucifer'] { errno: -2, code: 'ENOENT', syscall: 'open', path: 'something-not-exist.lucifer'}> 脑洞前端是会被打印出来的。 其本质在于 fs.readFile 的函数调用已经成功,并从调用栈返回并执行到下一行的console.log('lucifer')。因此错误发生的时候,调用栈是空的,这一点可以从上面的错误堆栈信息中看出来。 不明白为什么调用栈是空的同学可以看下我之前写的《一文看懂浏览器事件循环》 而 try catch 的作用仅仅是捕获当前调用栈的错误(上面异常传播部分已经讲过了)。因此异步的错误是无法捕获的,比如; 123456789try { fs.readFile(\"something-not-exist.lucifer\", (err, data) => { if (err) { throw err; } });} catch (err) { console.log(\"catching an error\");} 上面的 catching an error 不会被打印。因为错误抛出的时候, 调用栈中不包含这个 catch 语句,而仅仅在执行fs.readFile的时候才会。 如果我们换成同步读取文件的例子看看: 12345try { fs.readFileSync(\"something-not-exist.lucifer\");} catch (err) { console.log(\"catching an error\");} 上面的代码会打印 catching an error。因为读取文件被同步发起,文件返回之前线程会被挂起,当线程恢复执行的时候, fs.readFileSync 仍然在函数调用栈中,因此 fs.readFileSync 产生的异常会冒泡到 catch 语句。 简单来说就是异步产生的错误不能用 try catch 捕获,而要使用回调捕获。 可能有人会问了,我见过用 try catch 捕获异步异常啊。 比如: 123456789101112131415rejectIn = (ms) => new Promise((_, r) => { setTimeout(() => { r(1); }, ms); });async function t() { try { await rejectIn(0); } catch (err) { console.log(\"catching an error\", err); }}t(); 本质上这只是一个语法糖,是 Promise.prototype.catch 的一个语法糖而已。而这一语法糖能够成立的原因在于其用了 Promise 这种包装类型。如果你不用包装类型,比如上面的 fs.readFile 不用 Promise 等包装类型包装,打死都不能用 try catch 捕获。 而如果我们使用 babel 转义下,会发现 try catch 不见了,变成了 switch case 语句。这就是 try catch “可以捕获异步异常”的原因,仅此而已,没有更多。 (babel 转义结果) 我使用的 babel 转义环境都记录在这里,大家可以直接点开链接查看. 虽然浏览器并不像 babel 转义这般实现,但是至少我们明白了一点。目前的 try catch 的作用机制是无法捕获异步异常的。 异步的错误处理推荐使用容器包装,比如 Promise。然后使用 catch 进行处理。实际上 Promise 的 catch 和 try catch 的 catch 有很多相似的地方,大家可以类比过去。 和同步处理一样,很多原则都是通用的。比如异步也不要去吞没异常。下面的代码是不好的,因为它吞没了它不能处理的异常。 12p = Promise.reject(1);p.catch(() => {}); 更合适的做法的应该是类似这种: 1234567p = Promise.reject(1);p.catch((err) => { if (err == 1) { return console.log(\"I can handle this\"); } throw err;}); 彻底消除运行时异常可能么?我个人对目前前端现状最为头疼的一点是:大家过分依赖运行时,而严重忽略编译时。我见过很多程序,你如果不运行,根本不知道程序是怎么走的,每个变量的 shape 是什么。怪不得处处都可以看到 console.log。我相信你一定对此感同身受。也许你就是那个写出这种代码的人,也许你是给别人擦屁股的人。为什么会这样? 就是因为大家太依赖运行时。TS 的出现很大程度上改善了这一点,前提是你用的是 typescript,而不是 anyscript。其实 eslint 以及 stylint 对此也有贡献,毕竟它们都是静态分析工具。 我强烈建议将异常保留在编译时,而不是运行时。不妨极端一点来看:假如所有的异常都在编译时发生,而一定不会在运行时发生。那么我们是不是就可以信心满满地对应用进行重构啦? 幸运的是,我们能够做到。只不过如果当前语言做不到的话,则需要对现有的语言体系进行改造。这种改造成本真的很大。不仅仅是 API,编程模型也发生了翻天覆地的变化,不然函数式也不会这么多年没有得到普及了。 不熟悉函数编程的可以看看我之前写的函数式编程入门篇。 如果才能彻底消除异常呢?在回答这个问题之前,我们先来看下一门号称没有运行时异常的语言 elm。elm 是一门可以编译为 JS 的函数式编程语言,其封装了诸如网络 IO 等副作用,是一种声明式可推导的语言。 有趣的是,elm 也有异常处理。 elm 中关于异常处理(Error Handling)部分有两个小节的内容,分别是:Maybe 和 Result。elm 之所以没有运行时异常的一个原因就是它们。 一句话概括“为什么 elm 没有异常”的话,那就是elm 把异常看作数据(data)。 举个简单的例子: 12345678maybeResolveOrNot = (ms) => setTimeout(() => { if (Math.random() > 0.5) { console.log(\"ok\"); } else { throw new Error(\"error\"); } }); 上面的代码有一半的可能报错。那么在 elm 中就不允许这样的情况发生。所有的可能发生异常的代码都会被强制包装一层容器,这个容器在这里是 Maybe。 在其他函数式编程语言名字可能有所不同,但是意义相同。实际上,不仅仅是异常,正常的数据也会被包装到容器中,你需要通过容器的接口来获取数据。如果难以理解的话,你可以将其简单理解为 Promsie(但并不完全等价)。 Maybe 可能返回正常的数据 data,也可能会生成一个错误 error。某一个时刻只能是其中一个,并且只有运行的时候,我们才真正知道它是什么。从这一点来看,有点像薛定谔的猫。 不过 Maybe 已经完全考虑到异常的存在,一切都在它的掌握之中。所有的异常都能够在编译时推导出来。当然要想推导出这些东西,你需要对整个编程模型做一定的封装会抽象,比如 DOM 就不能直接用了,而是需要一个中间层。 再来看下一个更普遍的例子 NPE: 1null.toString(); elm 也不会发生。原因也很简单,因为 null 也会被包装起来,当你通过这个包装类型就行访问的时候,容器有能力避免这种情况,因此就可以不会发生异常。当然这里有一个很重要的前提就是可推导,而这正是函数式编程语言的特性。这部分内容超出了本文的讨论范围,不再这里说了。 运行时异常可以恢复么?最后要讨论的一个主题是运行时异常是否可以恢复。先来解释一下,什么是运行时异常的恢复。 还是用上面的例子: 123456function t() { console.log(\"start\"); throw 1; console.log(\"end\");}t(); 这个我们已经知道了, end 是不会打印的。 尽管你这么写也是无济于事: 12345678910function t() { try { console.log(\"start\"); throw 1; console.log(\"end\"); } catch (err) { console.log(\"relax, I can handle this\"); }}t(); 如果我想让它打印呢?我想让程序面对异常可以自己 recover 怎么办?我已经捕获这个错误, 并且我确信我可以处理,让流程继续走下去吧!如果有能力做到这个,这个就是运行时异常恢复。 遗憾地告诉你,据我所知,目前没有任何一个引擎能够做到这一点。 这个例子过于简单, 只能帮助我们理解什么是运行时异常恢复,但是不足以让我们看出这有什么用? 我们来看一个更加复杂的例子,我们这里直接使用上面实现过的函数divide。 1234567891011121314function t() { try { const res = divide(\"foo\", \"bar\"); alert(`you got ${res}`); } catch (err) { if (err.code === 1) { return console.log(\"被除数必须是除0之外的数\"); } if (err.code === 2) { return console.log(\"除数必须是数字\"); } throw new Error(\"不可预知的错误\"); }} 如上代码,会进入 catch ,而不会 alert。因此对于用户来说, 应用程序是没有任何响应的。这是不可接受的。 要吐槽一点的是这种事情真的是挺常见的,只不过大家用的不是 alert 罢了。 如果我们的代码在进入 catch 之后还能够继续返回出错位置继续执行就好了。 如何实现异常中断的恢复呢?我刚刚说了:据我所知,目前没有任何一个引擎能够做到异常恢复。那么我就来发明一个新的语法解决这个问题。 12345678910function t() { try { const res = divide(\"foo\", \"bar\"); alert(`you got ${res}`); } catch (err) { console.log(\"releax, I can handle this\"); resume - 1; }}t(); 上面的 resume 是我定义的一个关键字,功能是如果遇到异常,则返回到异常发生的地方,然后给当前发生异常的函数一个返回值 -1,并使得后续代码能够正常运行,不受影响。这其实是一种 fallback。 这绝对是一个超前的理念。当然挑战也非常大,对现有的体系冲击很大,很多东西都要改。我希望社区可以考虑把这个东西加到标准。 最佳实践通过前面的学习,你已经知道了异常是什么,异常是怎么产生的,以及如何正确处理异常(同步和异步)。接下来,我们谈一下异常处理的最佳实践。 我们平时开发一个应用。 如果站在生产者和消费者的角度来看的话。当我们使用别人封装的框架,库,模块,甚至是函数的时候,我们就是消费者。而当我们写的东西被他人使用的时候,我们就是生产者。 实际上,就算是生产者内部也会有多个模块构成,多个模块之间也会有生产者和消费者的再次身份转化。不过为了简单起见,本文不考虑这种关系。这里的生产者指的就是给他人使用的功能,是纯粹的生产者。 从这个角度出发,来看下异常处理的最佳实践。 作为消费者当作为消费者的时候,我们关心的是使用的功能是否会抛出异常,如果是,他们有哪些异常。比如: 123456import foo from \"lucifer\";try { foo.bar();} catch (err) { // 有哪些异常?} 当然,理论上 foo.bar 可能产生任何异常,而不管它的 API 是这么写的。但是我们关心的是可预期的异常。因此你一定希望这个时候有一个 API 文档,详细列举了这个 API 可能产生的异常有哪些。 比如这个 foo.bar 4 种可能的异常 分别是 A,B,C 和 D。其中 A 和 B 是我可以处理的,而 C 和 D 是我不能处理的。那么我应该: 123456789101112import foo from \"lucifer\";try { foo.bar();} catch (err) { if (err.code === \"A\") { return console.log(\"A happened\"); } if (err.code === \"B\") { return console.log(\"B happened\"); } throw err;} 可以看出,不管是 C 和 D,还是 API 中没有列举的各种可能异常,我们的做法都是直接抛出。 作为生产者如果你作为生产者,你要做的就是提供上面提到的详细的 API,告诉消费者你的可能错误有哪些。这样消费者就可以在 catch 中进行相应判断,处理异常情况。 你可以提供类似上图的错误表,让大家可以很快知道可能存在的可预知异常有哪些。不得不吐槽一句,在这一方面很多框架,库做的都很差。希望大家可以重视起来,努力维护良好的前端开发大环境。 总结本文很长,如果你能耐心看完,你真得给可以给自己鼓个掌 👏👏👏。 我从什么是异常,以及异常的分类,让大家正确认识异常,简单来说异常就是一种数据结构而已。 接着,我又讲到了异常的传播和处理。这两个部分是紧密联系的。异常的传播和事件传播没有本质不同,主要不同是数据结构不同,思想是类似的。具体来说异常会从发生错误的调用处,沿着调用栈回退,直到第一个 catch 语句或者栈为空。如果栈为空都没有碰到一个 catch,则会抛出uncaught Error。 需要特别注意的是异步的异常处理,不过你如果对我讲的原理了解了,这都不是事。 然后,我提出了两个脑洞问题: 彻底消除运行时异常可能么? 运行时异常可以恢复么? 这两个问题非常值得研究,但由于篇幅原因,我这里只是给你讲个轮廓而已。如果你对这两个话题感兴趣,可以和我交流。 最后,我提到了前端异常处理的最佳实践。大家通过两种角色(生产者和消费者)的转换,认识一下不同决定关注点以及承担责任的不同。具体来说提到了 明确声明可能的异常以及 处理你应该处理的,不要吞没你不能处理的异常。当然这个最佳实践仍然是轮廓性的。如果大家想要一份 前端最佳实践 checklist,可以给我留言。留言人数较多的话,我考虑专门写一个前端最佳实践 checklist 类型的文章。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"异常处理","slug":"异常处理","permalink":"https://lucifer.ren/blog/tags/异常处理/"}]},{"title":"【西法带你学算法】一次搞定前缀和","slug":"atMostK","date":"2020-09-26T16:00:00.000Z","updated":"2023-01-07T12:29:14.687Z","comments":true,"path":"2020/09/27/atMostK/","link":"","permalink":"https://lucifer.ren/blog/2020/09/27/atMostK/","excerpt":"本文是前缀和专题第一篇。系列目录如下: 一维前缀和(本文) 二维前缀和 我花了几天时间,从力扣中精选了五道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 467. 环绕字符串中唯一的子字符串(中等) 795. 区间子数组个数(中等) 904. 水果成篮(中等) 992. K 个不同整数的子数组(困难) 1109. 航班预订统计(中等) 前四道题都是滑动窗口的子类型,我们知道滑动窗口适合在题目要求连续的情况下使用, 而前缀和也是如此。二者在连续问题中,对于优化时间复杂度有着很重要的意义。 因此如果一道题你可以用暴力解决出来,而且题目恰好有连续的限制, 那么滑动窗口和前缀和等技巧就应该被想到。 除了这几道题, 还有很多题目都是类似的套路, 大家可以在学习过程中进行体会。今天我们就来一起学习一下。","text":"本文是前缀和专题第一篇。系列目录如下: 一维前缀和(本文) 二维前缀和 我花了几天时间,从力扣中精选了五道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 467. 环绕字符串中唯一的子字符串(中等) 795. 区间子数组个数(中等) 904. 水果成篮(中等) 992. K 个不同整数的子数组(困难) 1109. 航班预订统计(中等) 前四道题都是滑动窗口的子类型,我们知道滑动窗口适合在题目要求连续的情况下使用, 而前缀和也是如此。二者在连续问题中,对于优化时间复杂度有着很重要的意义。 因此如果一道题你可以用暴力解决出来,而且题目恰好有连续的限制, 那么滑动窗口和前缀和等技巧就应该被想到。 除了这几道题, 还有很多题目都是类似的套路, 大家可以在学习过程中进行体会。今天我们就来一起学习一下。 前菜我们从一个简单的问题入手,识别一下这种题的基本形式和套路,为之后的四道题打基础。当你了解了这个套路之后, 之后做这种题就可以直接套。 需要注意的是这四道题的前置知识都是 滑动窗口, 不熟悉的同学可以先看下我之前写的 滑动窗口专题(思路 + 模板) 母题 0有 N 个的正整数放到数组 A 里,现在要求一个新的数组 B,新数组的第 i 个数 B[i]是原数组 A 第 0 到第 i 个数的和。 这道题可以使用前缀和来解决。 前缀和是一种重要的预处理,能大大降低查询的时间复杂度。我们可以简单理解前缀和为“数列的前 n 项的和”。 这个概念其实很容易理解,即一个数组中,第 n 位存储的是数组前 n 个数字的和。 例如,对 [1,2,3,4,5,6] 来说,其前缀和就是 pre=[1,3,6,10,15,21]。 我们可以使用公式 pre[𝑖]=pre[𝑖−1]+nums[𝑖]得到每一位前缀和的值,从而通过前缀和进行相应的计算和解题。如果想得到某一个连续区间[l,r]的和则可以通过 pre[r] - pre[l-1] 取得,不过这里 l 需要 > 0。我们可以加一个特殊判断,如果 l = 0,区间 [l,r] 的和就是 pre[r]。 我们也可以在前缀和首项前面添加一个 0 省去这种特殊判断,算是一个小技巧吧。 其实前缀和的概念很简单,但困难的是如何在题目中使用前缀和以及如何使用前缀和的关系来进行解题。 母题 1如果让你求一个数组的连续子数组总个数,你会如何求?其中连续指的是数组的索引连续。 比如 [1,3,4],其连续子数组有:[1], [3], [4], [1,3], [3,4] , [1,3,4],你需要返回 6。 一种思路是总的连续子数组个数等于:以索引为 0 结尾的子数组个数 + 以索引为 1 结尾的子数组个数 + … + 以索引为 n - 1 结尾的子数组个数,这无疑是完备的。 同时利用母题 0 的前缀和思路, 边遍历边求和。 参考代码(JS): 123456789function countSubArray(nums) { let ans = 0; let pre = 0; for (_ in nums) { pre += 1; ans += pre; } return ans;} 复杂度分析 时间复杂度:$O(N)$,其中 N 为数组长度。 空间复杂度:$O(1)$ 而由于以索引为 i 结尾的子数组个数就是 i + 1,因此这道题可以直接用等差数列求和公式 (1 + n) * n / 2,其中 n 数组长度。 母题 2我继续修改下题目, 如果让你求一个数组相邻差为 1 连续子数组的总个数呢?(如果只有一个数,那么我们也认为其实一个相邻差为 1 连续子数组)其实就是索引差 1 的同时,值也差 1。 和上面思路类似,无非就是增加差值的判断。 参考代码(JS): 1234567891011121314function countSubArray(nums) { let ans = 1; let pre = 1; for (let i = 1; i < nums.length; i++) { if (nums[i] - nums[i - 1] == 1) { pre += 1; } else { pre = 1; } ans += pre; } return ans;} 复杂度分析 时间复杂度:$O(N)$,其中 N 为数组长度。 空间复杂度:$O(1)$ 如果我值差只要大于 1 就行呢?其实改下符号就行了,这不就是求上升子序列个数么?这里不再继续赘述, 大家可以自己试试。 母题 3我们继续扩展。 如果我让你求出不大于 k 的子数组的个数呢?不大于 k 指的是子数组的全部元素都不大于 k。 比如 [1,3,4] 子数组有 [1], [3], [4], [1,3], [3,4] , [1,3,4],不大于 3 的子数组有 [1], [3], [1,3] ,那么 [1,3,4] 不大于 3 的子数组个数就是 3。 实现函数 atMostK(k, nums)。 参考代码(JS): 1234567891011121314function countSubArray(k, nums) { let ans = 0; let pre = 0; for (let i = 0; i < nums.length; i++) { if (nums[i] <= k) { pre += 1; } else { pre = 0; } ans += pre; } return ans;} 复杂度分析 时间复杂度:$O(N)$,其中 N 为数组长度。 空间复杂度:$O(1)$ 母题 4如果我让你求出子数组最大值刚好是 k 的子数组的个数呢? 比如 [1,3,4] 子数组有 [1], [3], [4], [1,3], [3,4] , [1,3,4],子数组最大值刚好是 3 的子数组有 [3], [1,3] ,那么 [1,3,4] 子数组最大值刚好是 3 的子数组个数就是 2。实现函数 exactK(k, nums)。 实际上是 exactK 可以直接利用 atMostK,即 atMostK(k) - atMostK(k - 1),原因见下方母题 5 部分。 母题 5如果我让你求出子数组最大值刚好是 介于 k1 和 k2 的子数组的个数呢?实现函数 betweenK(k1, k2, nums)。 实际上是 betweenK 可以直接利用 atMostK,即 atMostK(k1, nums) - atMostK(k2 - 1, nums),其中 k1 > k2。前提是值是离散的, 比如上面我出的题都是整数。 因此我可以直接 减 1,因为 1 是两个整数最小的间隔。 如上,小于等于 10 的区域减去 小于 5 的区域就是 大于等于 5 且小于等于 10 的区域。 注意我说的是小于 5, 不是小于等于 5。 由于整数是离散的,最小间隔是 1。因此小于 5 在这里就等价于 小于等于 4。这就是 betweenK(k1, k2, nums) = atMostK(k1) - atMostK(k2 - 1) 的原因。 因此不难看出 exactK 其实就是 betweenK 的特殊形式。 当 k1 == k2 的时候, betweenK 等价于 exactK。 因此 atMostK 就是灵魂方法,一定要掌握,不明白建议多看几遍。 前缀和的使用场景上面是前缀和的基本概念以及简单使用。这里,我们总结一下前缀和的常见使用场景。大家碰到类似场景,不妨思考一下是否可通过前缀和以空间换时间的方式解决。 差分(后面我们要讲的 1109. 航班预订统计 就使用了这个技巧) 前缀和与二分。如果 nums 是一个正整数数组,那么其前缀和一定是单调递增的,有时候可以利用这个性质做二分。 求区间内的 1 的个数。比如给你一个数组 nums,让你求任意区间的 1 的个数。那么就可以先预处理,比如将 1 以外的数字预处理为 0,然后做前缀和,最后做差求区间和,这样区间和就是 1 的个数。(比如 1871. 跳跃游戏 VII 就利用了这个技巧) 区间值计数。上面是对 nums 区间本身进行统计。如果我想求 nums 的值(注意是值,不是索引)在 [lower,upper] 之间的数有多少,怎么求呢?我们可以开辟一个与 nums 值域大小等大的数组,并对值进行计数(即统计 nums 的值的出现频率),接下来就和普通前缀和一样了。(比如 1862. 向下取整数对和 就利用了这个技巧) 区间值计数扩展。上面的区间值计数没有对索引进行限制,那如果我加了索引的限制呢?比如我想求索引在 [l,r] 并且值在 [lower,upper]的值的个数如何求?我们可以先做一个二维前缀和 pre[i][j] 表示 前 i 项 j 的出现次数(显然 pre 的规模为数组长度乘以数组值域大小),接下来依次枚举 [lower,upper]的所有数 cur,并利用 pre[r][cur] - pre[l-1][cur],将结果进行累加即可。(比如1906. 查询差绝对值的最小值 就使用了这个技巧) 有了上面的铺垫, 我们来看下第一道题。 467. 环绕字符串中唯一的子字符串(中等)题目描述123456789101112131415161718192021222324252627把字符串 s 看作是“abcdefghijklmnopqrstuvwxyz”的无限环绕字符串,所以 s 看起来是这样的:"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....". 现在我们有了另一个字符串 p 。你需要的是找出 s 中有多少个唯一的 p 的非空子串,尤其是当你的输入是字符串 p ,你需要输出字符串 s 中 p 的不同的非空子串的数目。 注意: p 仅由小写的英文字母组成,p 的大小可能超过 10000。 示例 1:输入: "a"输出: 1解释: 字符串 S 中只有一个"a"子字符。 示例 2:输入: "cac"输出: 2解释: 字符串 S 中的字符串“cac”只有两个子串“a”、“c”。. 示例 3:输入: "zab"输出: 6解释: 在字符串 S 中有六个子串“z”、“a”、“b”、“za”、“ab”、“zab”。. 前置知识 滑动窗口 思路题目是让我们找 p 在 s 中出现的非空子串数目,而 s 是固定的一个无限循环字符串。由于 p 的数据范围是 10^5 ,因此暴力找出所有子串就需要 10^10 次操作了,应该会超时。而且题目很多信息都没用到,肯定不对。 仔细看下题目发现,这不就是母题 2 的变种么?话不多说, 直接上代码,看看有多像。 为了减少判断, 我这里用了一个黑科技, p 前面加了个 ^。 123456789101112class Solution: def findSubstringInWraproundString(self, p: str) -> int: p = '^' + p w = 1 ans = 0 for i in range(1,len(p)): if ord(p[i])-ord(p[i-1]) == 1 or ord(p[i])-ord(p[i-1]) == -25: w += 1 else: w = 1 ans += w return ans 如上代码是有问题。 比如 cac会被计算为 3,实际上应该是 2。根本原因在于 c 被错误地计算了两次。因此一个简单的思路就是用 set 记录一下访问过的子字符串即可。比如: 123456{ c, abc, ab, abcd} 而由于 set 中的元素一定是连续的,因此上面的数据也可以用 hashmap 存: 12345{ c: 3 d: 4 b: 1} 含义是: 以 b 结尾的子串最大长度为 1,也就是 b。 以 c 结尾的子串最大长度为 3,也就是 abc。 以 d 结尾的子串最大长度为 4,也就是 abcd。 至于 c ,是没有必要存的。我们可以通过母题 2 的方式算出来。 具体算法: 定义一个 len_mapper。key 是 字母, value 是 长度。 含义是以 key 结尾的最长连续子串的长度。 关键字是:最长 用一个变量 w 记录连续子串的长度,遍历过程根据 w 的值更新 len_mapper 返回 len_mapper 中所有 value 的和。 比如: abc,此时的 len_mapper 为: 12345{ c: 3 b: 2 a: 1} 再比如:abcab,此时的 len_mapper 依旧。 再比如: abcazabc,此时的 len_mapper: 123456{ c: 4 b: 3 a: 2 z: 1} 这就得到了去重的目的。这种算法是不重不漏的,因为最长的连续子串一定是包含了比它短的连续子串,这个思想和 1297. 子串的最大出现次数 剪枝的方法有异曲同工之妙。 代码(Python)123456789101112class Solution: def findSubstringInWraproundString(self, p: str) -> int: p = '^' + p len_mapper = collections.defaultdict(lambda: 0) w = 1 for i in range(1,len(p)): if ord(p[i])-ord(p[i-1]) == 1 or ord(p[i])-ord(p[i-1]) == -25: w += 1 else: w = 1 len_mapper[p[i]] = max(len_mapper[p[i]], w) return sum(len_mapper.values()) 复杂度分析 时间复杂度:$O(N)$,其中 $N$ 为字符串 p 的长度。 空间复杂度:由于最多存储 26 个字母, 因此空间实际上是常数,故空间复杂度为 $O(1)$。 795. 区间子数组个数(中等)题目描述12345678910111213141516给定一个元素都是正整数的数组 A ,正整数 L 以及 R (L <= R)。求连续、非空且其中最大元素满足大于等于 L 小于等于 R 的子数组个数。例如 :输入:A = [2, 1, 4, 3]L = 2R = 3输出: 3解释: 满足条件的子数组: [2], [2, 1], [3].注意:L, R 和 A[i] 都是整数,范围在 [0, 10^9]。数组 A 的长度范围在[1, 50000]。 前置知识 滑动窗口 思路由母题 5,我们知道 betweenK 可以直接利用 atMostK,即 atMostK(k1) - atMostK(k2 - 1),其中 k1 > k2。 由母题 2,我们知道如何求满足一定条件(这里是元素都小于等于 R)子数组的个数。 这两个结合一下, 就可以解决。 代码(Python) 代码是不是很像 1234567891011class Solution: def numSubarrayBoundedMax(self, A: List[int], L: int, R: int) -> int: def notGreater(R): ans = cnt = 0 for a in A: if a <= R: cnt += 1 else: cnt = 0 ans += cnt return ans return notGreater(R) - notGreater(L - 1) _复杂度分析_ 时间复杂度:$O(N)$,其中 $N$ 为数组长度。 空间复杂度:$O(1)$。 904. 水果成篮(中等)题目描述123456789101112131415161718192021222324252627282930313233343536373839404142在一排树中,第 i 棵树产生 tree[i] 型的水果。你可以从你选择的任何树开始,然后重复执行以下步骤:把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。移动到当前树右侧的下一棵树。如果右边没有树,就停下来。请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。用这个程序你能收集的水果树的最大总量是多少? 示例 1:输入:[1,2,1]输出:3解释:我们可以收集 [1,2,1]。示例 2:输入:[0,1,2,2]输出:3解释:我们可以收集 [1,2,2]如果我们从第一棵树开始,我们将只能收集到 [0, 1]。示例 3:输入:[1,2,3,2,2]输出:4解释:我们可以收集 [2,3,2,2]如果我们从第一棵树开始,我们将只能收集到 [1, 2]。示例 4:输入:[3,3,3,1,2,1,1,2,3,3,4]输出:5解释:我们可以收集 [1,2,1,1,2]如果我们从第一棵树或第八棵树开始,我们将只能收集到 4 棵水果树。 提示:1 <= tree.length <= 400000 <= tree[i] < tree.length 前置知识 滑动窗口 思路题目花里胡哨的。我们来抽象一下,就是给你一个数组, 让你选定一个子数组, 这个子数组最多只有两种数字,这个选定的子数组最大可以是多少。 这不就和母题 3 一样么?只不过 k 变成了固定值 2。另外由于题目要求整个窗口最多两种数字,我们用哈希表存一下不就好了吗? set 是不行了的。 因此我们不但需要知道几个数字在窗口, 我们还要知道每个数字出现的次数,这样才可以使用滑动窗口优化时间复杂度。 代码(Python)12345678910111213141516class Solution: def totalFruit(self, tree: List[int]) -> int: def atMostK(k, nums): i = ans = 0 win = defaultdict(lambda: 0) for j in range(len(nums)): if win[nums[j]] == 0: k -= 1 win[nums[j]] += 1 while k < 0: win[nums[i]] -= 1 if win[nums[i]] == 0: k += 1 i += 1 ans = max(ans, j - i + 1) return ans return atMostK(2, tree) 复杂度分析 时间复杂度:$O(N)$,其中 $N$ 为数组长度。 空间复杂度:$O(k)$。 992. K 个不同整数的子数组(困难)题目描述12345678910111213141516171819202122232425给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定独立的子数组为好子数组。(例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。)返回 A 中好子数组的数目。 示例 1:输入:A = [1,2,1,2,3], K = 2输出:7解释:恰好由 2 个不同整数组成的子数组:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].示例 2:输入:A = [1,2,1,3,4], K = 3输出:3解释:恰好由 3 个不同整数组成的子数组:[1,2,1,3], [2,1,3], [1,3,4]. 提示:1 <= A.length <= 200001 <= A[i] <= A.length1 <= K <= A.length 前置知识 滑动窗口 思路由母题 5,知:exactK = atMostK(k) - atMostK(k - 1), 因此答案便呼之欲出了。其他部分和上面的题目 904. 水果成篮 一样。 实际上和所有的滑动窗口题目都差不多。 代码(Python)123456789101112131415161718class Solution: def subarraysWithKDistinct(self, A, K): return self.atMostK(A, K) - self.atMostK(A, K - 1) def atMostK(self, A, K): counter = collections.Counter() res = i = 0 for j in range(len(A)): if counter[A[j]] == 0: K -= 1 counter[A[j]] += 1 while K < 0: counter[A[i]] -= 1 if counter[A[i]] == 0: K += 1 i += 1 res += j - i + 1 return res 复杂度分析 时间复杂度:$O(N)$,中 $N$ 为数组长度。 空间复杂度:$O(k)$。 1109. 航班预订统计(中等)题目描述123456789101112131415161718192021这里有 n 个航班,它们分别从 1 到 n 进行编号。我们这儿有一份航班预订表,表中第 i 条预订记录 bookings[i] = [i, j, k] 意味着我们在从 i 到 j 的每个航班上预订了 k 个座位。请你返回一个长度为 n 的数组 answer,按航班编号顺序返回每个航班上预订的座位数。示例:输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5输出:[10,55,45,25,25]提示:1 <= bookings.length <= 200001 <= bookings[i][0] <= bookings[i][1] <= n <= 200001 <= bookings[i][2] <= 10000 前置知识 前缀和 思路这道题的题目描述不是很清楚。我简单分析一下题目: [i, j, k] 其实代表的是 第 i 站上来了 k 个人, 一直到 第 j 站都在飞机上,到第 j + 1 就不在飞机上了。所以第 i 站到第 j 站的每一站都会因此多 k 个人。 理解了题目只会不难写出下面的代码。 123456789class Solution: def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]: counter = [0] * n for i, j, k in bookings: while i <= j: counter[i - 1] += k i += 1 return counter 如上的代码复杂度太高,无法通过全部的测试用例。 注意到里层的 while 循环是连续的数组全部加上一个数字,不难想到可以利用母题 0 的前缀和思路优化。 一种思路就是在 i 的位置 + k, 然后利用前缀和的技巧给 i 到 n 的元素都加上 k。但是题目需要加的是一个区间, j + 1 及其之后的元素会被多加一个 k。一个简单的技巧就是给 j + 1 的元素减去 k,这样正负就可以抵消。 拼车 是这道题的换皮题, 思路一模一样。 代码(Python)12345678910class Solution: def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]: counter = [0] * (n + 1) for i, j, k in bookings: counter[i - 1] += k if j < n: counter[j] -= k for i in range(n + 1): counter[i] += counter[i - 1] return counter[:-1] 复杂度分析 时间复杂度:$O(N)$,中 $N$ 为数组长度。 空间复杂度:$O(N)$。 总结这几道题都是滑动窗口和前缀和的思路。力扣类似的题目还真不少,大家只有多留心,就会发现这个套路。 前缀和的技巧以及滑动窗口的技巧都比较固定,且有模板可套。 难点就在于我怎么才能想到可以用这个技巧呢? 我这里总结了两点: 找关键字。比如题目中有连续,就应该条件反射想到滑动窗口和前缀和。比如题目求最大最小就想到动态规划和贪心等等。想到之后,就可以和题目信息对比快速排除错误的算法,找到可行解。这个思考的时间会随着你的题感增加而降低。 先写出暴力解,然后找暴力解的瓶颈, 根据瓶颈就很容易知道应该用什么数据结构和算法去优化。 最后推荐几道类似的题目, 供大家练习,一定要自己写出来才行哦。 303. 区域和检索 - 数组不可变 1171. 从链表中删去总和值为零的连续节点 1186.删除一次得到子数组最大和 1310. 子数组异或查询 1371. 每个元音包含偶数次的最长子字符串 1402. 做菜顺序 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。 更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K star 啦。 大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"},{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/tags/经验分享/"},{"name":"困难","slug":"困难","permalink":"https://lucifer.ren/blog/tags/困难/"},{"name":"中等","slug":"中等","permalink":"https://lucifer.ren/blog/tags/中等/"},{"name":"子数组","slug":"子数组","permalink":"https://lucifer.ren/blog/tags/子数组/"},{"name":"k 问题","slug":"k-问题","permalink":"https://lucifer.ren/blog/tags/k-问题/"}]},{"title":"TypeScript 练习题","slug":"ts-exercises","date":"2020-09-26T16:00:00.000Z","updated":"2023-01-07T12:34:34.505Z","comments":true,"path":"2020/09/27/ts-exercises/","link":"","permalink":"https://lucifer.ren/blog/2020/09/27/ts-exercises/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(就是本文) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(就是本文) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言本文涉及的题目一共十五道,全部都可以在 typescript-exercises 上在线提交。 可以和标准答案进行对比。 并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 想重置进度,清空缓存,无痕模式或者换浏览器都可以。 题目中涉及到的知识点我基本也都在之前的文章中提到了,如果你没有看过,强烈建议先完成前面的教程,然后将上面的题目自己做一遍之后再看本文。另外一定要按照顺序读, 因此前面的题目都是后面的铺垫。 为了不让文章太过于冗长, 本篇文章分两次发布, 这一次是 8 道题,一共十五道。每道题都有思路,前置知识以及代码。 题目一题目描述12345678910111213Intro: We are starting a small community of users. For performance reasons we have decided to store all users right in the code. This way we can provide our developers with more user-interaction opportunities. With user-related data, at least. All the GDPR-related issues we will solved some other day. This would be the base for our future experiments during these exercises.Exercise: Given the data, define the interface "User" and use it accordingly. 题目的大概意思是让你定义一个类型 User, 使得代码可以正常运行。 题目内置代码123456789101112131415161718192021export type User = unknown;export const users: unknown[] = [ { name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", },];export function logPerson(user: unknown) { console.log(` - ${user.name}, ${user.age}`);}console.log(\"Users:\");users.forEach(logPerson); 前置知识 interface 或 type 声明自定义类型 思路这道题比较简单, 我们只有定义一个 User 类即可。从 users 数组中不难看出, User 中有三个属性 name ,age 和 occupation,类型分别为 string, number 和 string。因此直接使用 type 或者 interface 定义自定义类型即可。 代码核心代码: 12345export type User = { name: string; age: number; occupation: string;}; 题目二题目描述123456789101112131415Intro: All 2 users liked the idea of the community. We should go forward and introduce some order. We are in Germany after all. Let's add a couple of admins. Initially we only had users in the in-memory database. After introducing Admins, we need to fix the types so that everything works well together.Exercise: Type "Person" is missing, please define it and use it in persons array and logPerson function in order to fix all the TS errors. 题目大意是补充 Person 类, 使得代码不报错。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142interface User { name: string; age: number; occupation: string;}interface Admin { name: string; age: number; role: string;}export type Person = unknown;export const persons: User[] /* <- Person[] */ = [ { name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { name: \"Jane Doe\", age: 32, role: \"Administrator\", }, { name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", }, { name: \"Bruce Willis\", age: 64, role: \"World saver\", },];export function logPerson(user: User) { console.log(` - ${user.name}, ${user.age}`);}persons.forEach(logPerson); 前置知识 联合类型 思路我们直接从报错入手。 不难发现 persons 数组既有 User 又有 Admin。 因此 person 的函数签名应该是两者的联合类型。而题目又让我们补充 Person,于是代码将 Person 定义为 Admin 和 User 的联合类型就不难想到。 代码核心代码: 1export type Person = User | Admin; 这个时候, persons 数组使用的过程只能用 User 和 Admin 的共有属性, 也就是 name 和 age,这点后面的题目也会提到。 因此如果你使用了 role 或者 occupation 就会报错。怎么解决呢? 我们继续看下一题。 第三题题目描述12345678910111213Intro: Since we already have some of the additional information about our users, it's a good idea to output it in a nice way.Exercise: Fix type errors in logPerson function. logPerson function should accept both User and Admin and should output relevant information according to the input: occupation for User and role for Admin. 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748interface User { name: string; age: number; occupation: string;}interface Admin { name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { name: \"Jane Doe\", age: 32, role: \"Administrator\", }, { name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", }, { name: \"Bruce Willis\", age: 64, role: \"World saver\", },];export function logPerson(person: Person) { let additionalInformation: string; if (person.role) { additionalInformation = person.role; } else { additionalInformation = person.occupation; } console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);}persons.forEach(logPerson); 前置知识 类型断言 类型收敛 in 操作符 思路关于类型收敛, 我在 TypeScript 类型系统 做了很详情的讨论。 上面代码报错的原因前面已经讲过了, 那么如何解决呢?由于 person 可能是 User ,也可能是 Admin 类型,而 TypeScript 没有足够的信息确定具体是哪一种。因此你使用 User 或者 Admin 特有的属性就会报错了。 因此解决方案的基本思想就是告诉 TypeScript person 当前是 Admin 还是 User 类型。有多种方式可以解决这个问题。 将 person 断言为准确的类型。 就是告诉 TypeScript ”交给我吧, person 就是 xxx 类型,有错就我的锅“。 代码: 12345if ((<Admin>person).role) { additionalInformation = (<Admin>person).role;} else { additionalInformation = (<User>person).occupation;} 另外一种方式是使用类型收缩,比如 is , in, typeof , instanceof 等。使得 Typescript 能够 Get 到当前的类型。”哦, person 上有 role 属性啊,那它就是 Admin 类型,有问题我 Typescript 的锅“ 这里我们使用 in 操作符,写起来也很简单。 推荐哪种不用我多说了吧 ? 代码1234567if (\"role\" in person) { // person 会被自动推导为 Admin additionalInformation = person.role;} else { // Person 会被自动推导为 User additionalInformation = person.occupation;} 第四题题目描述123456789101112Intro: As we introduced "type" to both User and Admin it's now easier to distinguish between them. Once object type checking logic was extracted into separate functions isUser and isAdmin - logPerson function got new type errors.Exercise: Figure out how to help TypeScript understand types in this situation and apply necessary fixes. 大概意思还是让你改代码, 使得 Typescript 能理解(不报错)。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" },];export function isAdmin(person: Person) { return person.type === \"admin\";}export function isUser(person: Person) { return person.type === \"user\";}export function logPerson(person: Person) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);}console.log(\"Admins:\");persons.filter(isAdmin).forEach(logPerson);console.log();console.log(\"Users:\");persons.filter(isUser).forEach(logPerson); 前置知识 类型收敛 is 操作符 思路我们仍然从报错入手。 实际上还是 person 的类型问题, 没有被收缩到正确的类型。看题目的代码,期望效果应该是如果进入 isAdmin 内部,那么 person 就是 Admin 类型,同理进入 isUser 内部,那么 person 就是 User 类型。 继续看下 isAdmin 和 isUser 的实现: 1234567export function isAdmin(person: Person) { return person.type === \"admin\";}export function isUser(person: Person) { return person.type === \"user\";} 这里我们期望的效果是如果 isAdmin 函数返回 true ,那么 person 就应该被收敛为 Admin,isUser 同理。 这里就需要用到 is 操作符。 上文提到了类型收敛常见的操作符是 is , in, typeof , instanceof 代码1234567export function isAdmin(person: Person): person is Admin { return person.type === \"admin\";}export function isUser(person: Person): person is User { return person.type === \"user\";} 这样当 isAdmin 返回 true, 那么 person 变量就会被推导成 Admin 类型,而不是联合类型, 也就是类型发生了收缩。 不难看出,这样的类型断言会直接影响到调用 isAdmin 或 isUser 的函数的入参的类型。 第五题题目描述123456789101112131415161718Intro: Time to filter the data! In order to be flexible we filter users using a number of criteria and return only those matching all of the criteria. We don't need Admins yet, we only filter Users.Exercise: Without duplicating type structures, modify filterUsers function definition so that we can pass only those criteria which are needed, and not the whole User information as it is required now according to typing.Higher difficulty bonus exercise: Exclude "type" from filter criterias. 大概意思是让你改 filterUsers, 但要注意 DRY(Don’t Repeat Yourself)。 题目内置代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\", }, { type: \"user\", name: \"Wilson\", age: 23, occupation: \"Ball\", }, { type: \"admin\", name: \"Agent Smith\", age: 23, role: \"Administrator\", },];export const isAdmin = (person: Person): person is Admin => person.type === \"admin\";export const isUser = (person: Person): person is User => person.type === \"user\";export function logPerson(person: Person) { let additionalInformation = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);}export function filterUsers(persons: Person[], criteria: User): User[] { return persons.filter(isUser).filter((user) => { const criteriaKeys = Object.keys(criteria) as (keyof User)[]; return criteriaKeys.every((fieldName) => { return user[fieldName] === criteria[fieldName]; }); });}console.log(\"Users of age 23:\");filterUsers(persons, { age: 23,}).forEach(logPerson); 前置知识 泛型 Partial 泛型 思路老规矩, 从报错入手。 大概意思是 { age: 23 } 不完整,缺失了部分 key。而题目实际上的想法应该是想根据部分内容对人员进行检错。比如可以根据 age 查, 也可以根据 name 查,也可以同时根据 age 和 name 查等,这和我们平时的搜索逻辑是一致的。 直接用 Partial 泛型即可解决, 不懂的可以看下我的文章你不知道的 TypeScript 泛型(万字长文,建议收藏)。 代码123export function filterUsers(persons: Person[], criteria: Partial<User>): User[] { ...} 第六题题目描述123456789101112131415161718192021222324Intro: Filtering requirements have grown. We need to be able to filter any kind of Persons.Exercise: Fix typing for the filterPersons so that it can filter users and return User[] when personType='user' and return Admin[] when personType='admin'. Also filterPersons should accept partial User/Admin type according to the personType. `criteria` argument should behave according to the `personType` argument value. `type` field is not allowed in the `criteria` field.Higher difficulty bonus exercise: Implement a function `getObjectKeys()` which returns more convenient result for any argument given, so that you don't need to cast it. let criteriaKeys = Object.keys(criteria) as (keyof User)[]; --> let criteriaKeys = getObjectKeys(criteria); 大概意思是让你改 filterUsers, 但要注意 DRY(Don’t Repeat Yourself)。并且可以根据 personType 的不同,返回不同的类型。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"user\", name: \"Wilson\", age: 23, occupation: \"Ball\" }, { type: \"admin\", name: \"Agent Smith\", age: 23, role: \"Anti-virus engineer\" },];export function logPerson(person: Person) { console.log( ` - ${person.name}, ${person.age}, ${ person.type === \"admin\" ? person.role : person.occupation }` );}export function filterPersons( persons: Person[], personType: string, criteria: unknown): unknown[] { return persons .filter((person) => person.type === personType) .filter((person) => { let criteriaKeys = Object.keys(criteria) as (keyof Person)[]; return criteriaKeys.every((fieldName) => { return person[fieldName] === criteria[fieldName]; }); });}export const usersOfAge23 = filterPersons(persons, \"user\", { age: 23 });export const adminsOfAge23 = filterPersons(persons, \"admin\", { age: 23 });console.log(\"Users of age 23:\");usersOfAge23.forEach(logPerson);console.log();console.log(\"Admins of age 23:\");adminsOfAge23.forEach(logPerson); 前置知识 泛型 Partial 泛型 函数重载 思路题目描述也懒得看了, 直接看报错。 报错信息提示我们没有找到合适的函数重载。 因此我的思路就是补上合适的重载即可。关于函数重载,我的系列教程不涉及,大家可以看下官网资料。 重载之后,不同的情况调用返回值就可以对应不同的类型。本题中就是: 如果 personType 是 admin,就会返回 Admin 数组。 如果 personType 是 user,就会返回 User 数组。 如果 personType 是其他 string,就会返回 Person 数组。 代码12345export function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Person>): Admin[]export function filterPersons(persons: Person[], personType: 'user', criteria: Partial<Person>): User[]export function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): Person[] { ...} 第七题题目描述123456789101112131415161718192021Intro: Filtering was completely removed from the project. It turned out that this feature was just not needed for the end-user and we spent a lot of time just because our office manager told us to do so. Next time we should instead listen to the product management. Anyway we have a new plan. CEO's friend Nick told us that if we randomly swap user names from time to time in the community, it would be very funny and the project would definitely succeed!Exercise: Implement swap which receives 2 persons and returns them in the reverse order. The function itself is already there, actually. We just need to provide it with proper types. Also this function shouldn't necessarily be limited to just Person types, lets type it so that it works with any two types specified. 题目大概意思是让你修改 swap 函数,使得不报错。 并且,我希望这个函数可以适用于任意两个变量,不管其类型一样不一样, 也不管二者类型是什么。 题目内置代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}function logUser(user: User) { const pos = users.indexOf(user) + 1; console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`);}function logAdmin(admin: Admin) { const pos = admins.indexOf(admin) + 1; console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`);}const admins: Admin[] = [ { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\", }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\", },];const users: User[] = [ { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\", }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\", },];export function swap(v1, v2) { return [v2, v1];}function test1() { console.log(\"test1:\"); const [secondUser, firstAdmin] = swap(admins[0], users[1]); logUser(secondUser); logAdmin(firstAdmin);}function test2() { console.log(\"test2:\"); const [secondAdmin, firstUser] = swap(users[0], admins[1]); logAdmin(secondAdmin); logUser(firstUser);}function test3() { console.log(\"test3:\"); const [secondUser, firstUser] = swap(users[0], users[1]); logUser(secondUser); logUser(firstUser);}function test4() { console.log(\"test4:\"); const [firstAdmin, secondAdmin] = swap(admins[1], admins[0]); logAdmin(firstAdmin); logAdmin(secondAdmin);}function test5() { console.log(\"test5:\"); const [stringValue, numericValue] = swap(123, \"Hello World\"); console.log(` - String: ${stringValue}`); console.log(` - Numeric: ${numericValue}`);}[test1, test2, test3, test4, test5].forEach((test) => test()); 前置知识 泛型 思路题目废话很多, 直接忽略看报错。 这个其实我在 你不知道的 TypeScript 泛型(万字长文,建议收藏) 里也讲过了,直接看代码。 代码123export function swap<U, T>(v1: T, v2: U): [U, T] { return [v2, v1];} 第八题题目描述1234567891011121314Intro: Project grew and we ended up in a situation with some users starting to have more influence. Therefore, we decided to create a new person type called PowerUser which is supposed to combine everything User and Admin have.Exercise: Define type PowerUser which should have all fields from both User and Admin (except for type), and also have type 'powerUser' without duplicating all the fields in the code. 题目大概意思是定义一个类型 PowerUser, 里面包含 User 和 Admin 的所有属性, 并且有一个字段是固定的 type: ‘powerUser’。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type PowerUser = unknown;export type Person = User | Admin | PowerUser;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"powerUser\", name: \"Nikki Stone\", age: 45, role: \"Moderator\", occupation: \"Cat groomer\", },];function isAdmin(person: Person): person is Admin { return person.type === \"admin\";}function isUser(person: Person): person is User { return person.type === \"user\";}function isPowerUser(person: Person): person is PowerUser { return person.type === \"powerUser\";}export function logPerson(person: Person) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } if (isPowerUser(person)) { additionalInformation = `${person.role}, ${person.occupation}`; } console.log(`${person.name}, ${person.age}, ${additionalInformation}`);}console.log(\"Admins:\");persons.filter(isAdmin).forEach(logPerson);console.log();console.log(\"Users:\");persons.filter(isUser).forEach(logPerson);console.log();console.log(\"Power users:\");persons.filter(isPowerUser).forEach(logPerson); 前置知识 集合操作(交叉类型) & 操作符 泛型 Omit 泛型 思路从题目信息不难看出,就是让我们实现 PowerUser。 有前面的分析不难得出我们只需要: 合并 User 和 Admin 的属性即可。 借助 & 操作符可以实现。即 User & Admin。 增加特有的属性 type: powerUser。 首先去掉上一步合并的 type 属性, 然后继续和 { type: “powerUser” } 交叉即可。 增加 { type: “powerUser” } 之前使用内置泛型 Omit 将原本的 type 删掉即可。 代码1type PowerUser = Omit<User & Admin, \"type\"> & { type: \"powerUser\" }; 总结以上就是给大家带来的题目解析。 这八道题的考点有,按照我个人理解的重要程度划分为: type 和 interface 的基本操作(必须掌握) 联合类型 和 交叉类型(强烈建议掌握) 类型断言和类型收缩(强烈建议掌握) 泛型和常见内置泛型(强烈建议掌握) 函数重载(推荐掌握) 最后祝愿大家告别 anyscript,成为 TypeScript 魔法师。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"字节跳动的算法面试题是什么难度?","slug":"byte-dance-algo-ex","date":"2020-09-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.741Z","comments":true,"path":"2020/09/06/byte-dance-algo-ex/","link":"","permalink":"https://lucifer.ren/blog/2020/09/06/byte-dance-algo-ex/","excerpt":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary 实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。 ​","text":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary 实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。 ​ 这套题一共四道题, 两道问答题, 两道编程题。 其中一道问答题是 LeetCode 426 的原题,只不过题型变成了找茬(改错)。可惜的是 LeetCode 的 426 题是一个会员题目,没有会员的就看不来了。不过,剑指 Offer 正好也有这个题,并且力扣将剑指 Offer 全部的题目都 OJ 化了。 这道题大家可以去 https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof 提交答案。简单说一下这个题目的思路,我们只需要中序遍历即可得到一个有序的数列,同时在中序遍历过程中将 pre 和 cur 节点通过指针串起来即可。 另一个问答是红包题目,这里不多说了。我们重点看一下剩下两个算法编程题。 两个问答题由于不能在线判题,我没有做,只做了剩下两个编程题。 球队比赛第一个编程题是一个球队比赛的题目。 题目描述有三只球队,每只球队编号分别为球队 1,球队 2,球队 3,这三只球队一共需要进行 n 场比赛。现在已经踢完了 k 场比赛,每场比赛不能打平,踢赢一场比赛得一分,输了不得分不减分。已知球队 1 和球队 2 的比分相差 d1 分,球队 2 和球队 3 的比分相差 d2 分,每场比赛可以任意选择两只队伍进行。求如果打完最后的 (n-k) 场比赛,有没有可能三只球队的分数打平。 思路假设球队 1,球队 2,球队 3 此时的胜利次数分别为 a,b,c,球队 1,球队 2,球队 3 总的胜利次数分别为 n1,n2,n3。 我一开始的想法是只要保证 n1,n2,n3 相等且都小于等于 n / 3 即可。如果题目给了 n1,n2,n3 的值就直接: 1print(n1 == n2 == n3 == n / 3) 可是不仅 n1,n2,n3 没给, a,b,c 也没有给。 实际上此时我们的信息仅仅是: 123① a + b + c = k② a - b = d1 or b - a = d1③ b - c = d2 or c - b = d2 其中 k 和 d1,d2 是已知的。a ,b,c 是未知的。 也就是说我们需要枚举所有的 a,b,c 可能性,解方程求出合法的 a,b,c,并且 合法的 a,b,c 都小于等于 n / 3 即可。 这个 a,b,c 的求解数学方程就是中学数学难度, 三个等式化简一下即可,具体见下方代码区域。 a 只需要再次赢得 n / 3 - a 次 b 只需要再次赢得 n / 3 - b 次 c 只需要再次赢得 n / 3 - c 次 123n1 = a + n / 3 - a = n / 3n2 = b + (n / 3 - b) = n / 3n3 = c + (n / 3 - c) = n / 3 代码(Python) 牛客有点让人不爽, 需要 print 而不是 return 123456789101112131415161718192021222324t = int(input())for i in range(t): n, k, d1, d2 = map(int, input().split(\" \")) if n % 3 != 0: print('no') continue abcs = [] for r1 in [-1, 1]: for r2 in [-1, 1]: a = (k + 2 * r1 * d1 + r2 * d2) / 3 b = (k + -1 * r1 * d1 + r2 * d2) / 3 c = (k + -1 * r1 * d1 + -2 * r2 * d2) / 3 a + r1 if 0 <= a <= k and 0 <= b <= k and 0 <= c <= k and a.is_integer() and b.is_integer() and c.is_integer(): abcs.append([a, b, c]) flag = False for abc in abcs: if len(abc) > 0 and max(abc) <= n / 3: flag = True break if flag: print('yes') else: print('no') 复杂度分析 时间复杂度:$O(t)$ 空间复杂度:$O(t)$ 小结感觉这个难度也就是力扣中等水平吧,力扣也有一些数学等式转换的题目, 比如 494.target-sum 转换字符串题目描述有一个仅包含’a’和’b’两种字符的字符串 s,长度为 n,每次操作可以把一个字符做一次转换(把一个’a’设置为’b’,或者把一个’b’置成’a’);但是操作的次数有上限 m,问在有限的操作数范围内,能够得到最大连续的相同字符的子串的长度是多少。 思路看完题我就有种似曾相识的感觉。 每次对妹子说出这句话的时候,她们都会觉得好假 ^_^ 不过这次是真的。 ”哦,不!每次都是真的“。 这道题其实就是我之前写的滑动窗口的一道题【1004. 最大连续 1 的个数 III】滑动窗口(Python3)的换皮题。 专题地址:https://github.com/azl397985856/leetcode/blob/master/thinkings/slide-window.md 所以说,如果这道题你完全没有思路的话。说明: 抽象能力不够。 滑动窗口问题理解不到位。 第二个问题可以看我上面贴的地址,仔细读读,并完成课后练习即可解决。 第一个问题就比较困难了, 不过多看我的题解也可以慢慢提升的。比如: 《割绳子》 实际上就是 343. 整数拆分 的换皮题。 力扣 230 和 力扣 645 就是换皮题,详情参考位运算专题 以及 你的衣服我扒了 - 《最长公共子序列》 以及 穿上衣服我就不认识你了?来聊聊最长上升子序列 以及 一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~ 等等 回归这道题。其实我们只需要稍微抽象一下, 就是一个纯算法题。 抽象的另外一个好处则是将很多不同的题目返璞归真,从而可以在茫茫题海中逃脱。这也是我开启《我是你的妈妈呀》 的原因之一。 如果我们把 a 看成是 0 , b 看成是 1。或者将 b 看成 1, a 看成 0。不就抽象成了: 1234给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 m 个值从 0 变成 1 。返回仅包含 1 的最长(连续)子数组的长度。 这就是 力扣 1004. 最大连续 1 的个数 III 原题。 因此实际上我们要求的是上面两种情况: a 表示 0, b 表示 1 a 表示 1, b 表示 0 的较大值。 lucifer 小提示: 其实我们也可以仅仅考虑一种情况,比如 a 看成是 0 , b 看成是 1。这个时候, 我们操作变成了两种情况,0 变成 1 或者 1 变成 0,同时求解的也变成了最长连续 0 或者 最长连续 1 。 由于这种抽象操作起来更麻烦, 我们不考虑。 问题得到了抽象就好解决了。我们只需要记录下加入窗口的是 0 还是 1: 如果是 1,我们什么都不用做 如果是 0,我们将 m 减 1 相应地,我们需要记录移除窗口的是 0 还是 1: 如果是 1,我们什么都不做 如果是 0,说明加进来的时候就是 1,加进来的时候我们 m 减去了 1,这个时候我们再加 1。 lucifer 小提示: 实际上题目中是求连续 a 或者 b 的长度。看到连续,大家也应该有滑动窗口的敏感度, 别管行不行, 想到总该有的。 我们拿 A = [1, 1, 0, 1, 0, 1], m = 1 来说。看下算法的具体过程: lucifer 小提示: 左侧的数字表示此时窗口大小,黄色格子表示修补的墙,黑色方框表示的是窗口。 这里我形象地将 0 看成是洞,1 看成是墙, 我们的目标就是补洞,使得连续的墙最长。 每次碰到一个洞,我们都去不加选择地修补。由于 m 等于 1, 也就是说我们最多补一个洞。因此需要在修补超过一个洞的时候,我们需要调整窗口范围,使得窗口内最多修补一个墙。由于窗口表示的就是连续的墙(已有的或者修补的),因此最终我们返回窗口的最大值即可。 由于下面的图窗口内有两个洞,这和”最多补一个洞“冲突, 我们需要收缩窗口使得满足“最多补一个洞”的先决条件。 因此最大的窗口就是 max(2, 3, 4, …) = 4。 lucifer 小提示: 可以看出我们不加选择地修补了所有的洞,并调整窗口,使得窗口内最多有 m 个修补的洞,因此窗口的最大值就是答案。然而实际上,我们并不需要真的”修补“(0 变成 1),而是仅仅修改 m 的值即可。 我们先来看下抽象之后的其中一种情况的代码: 123456789class Solution: def longestOnes(self, A: List[int], m: int) -> int: i = 0 for j in range(len(A)): m -= 1 - A[j] if m < 0: m += 1 - A[i] i += 1 return j - i + 1 因此完整代码就是: 1234567891011class Solution: def longestOnes(self, A: List[int], m: int) -> int: i = 0 for j in range(len(A)): m -= 1 - A[j] if m < 0: m += 1 - A[i] i += 1 return j - i + 1 def longestAorB(self, A:List[int], m: int) -> int: return max(self.longestOnes(map(lambda x: 0 if x == 'a' else 1, A) ,m), self.longestOnes(map(lambda x: 1 if x == 'a' else 0, A),m)) 这里的两个 map 会生成两个不同的数组。 我只是为了方便大家理解才新建的两个数组, 实际上根本不需要,具体见后面的代码. 代码(Python)1234567891011121314151617181920i = 0n, m = map(int, input().split(\" \"))s = input()ans = 0k = m # 存一下,后面也要用这个初始值# 修补 bfor j in range(n): m -= ord(s[j]) - ord('a') if m < 0: m += ord(s[i]) - ord('a') i += 1ans = j - i + 1i = 0# 修补 afor j in range(n): k += ord(s[j]) - ord('b') if k < 0: k -= ord(s[i]) - ord('b') i += 1print(max(ans, j - i + 1)) 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 小结这道题就是一道换了皮的力扣题,难度中等。如果你能将问题抽象,同时又懂得滑动窗口,那这道题就很容易。我看了题解区的参考答案, 内容比较混乱,不够清晰。这也是我写下这篇文章的原因之一。 总结这一套字节跳动的题目一共四道,一道设计题,三道算法题。 其中三道算法题从难度上来说,基本都是中等难度。从内容来看,基本都是力扣的换皮题。但是如果我不说他们是换皮题, 你们能发现么? 如果你可以的话,说明你的抽象能力已经略有小成了。如果看不出来也没有关系,关注我。 手把手扒皮给你们看,扒多了慢慢就会了。切记,不要盲目做题!如果你做了很多题, 这几道题还是看不出套路,说明你该缓缓,改变下刷题方式了。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"滑动窗口","slug":"算法/滑动窗口","permalink":"https://lucifer.ren/blog/categories/算法/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"},{"name":"字节跳动","slug":"面试/字节跳动","permalink":"https://lucifer.ren/blog/categories/面试/字节跳动/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"},{"name":"字节跳动","slug":"字节跳动","permalink":"https://lucifer.ren/blog/tags/字节跳动/"}]},{"title":"字节跳动的算法面试题是什么难度?(第二弹)","slug":"byte-dance-algo-ex-2017","date":"2020-09-05T16:00:00.000Z","updated":"2023-01-07T12:20:39.954Z","comments":true,"path":"2020/09/06/byte-dance-algo-ex-2017/","link":"","permalink":"https://lucifer.ren/blog/2020/09/06/byte-dance-algo-ex-2017/","excerpt":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary ​","text":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary ​ 这套题一共 11 道题, 三道编程题, 八道问答题。本次给大家带来的就是这三道编程题。更多精彩内容,请期待我的搞定算法面试专栏。 其中有一道题《异或》我没有通过所有的测试用例, 小伙伴可以找找茬,第一个找到并在公众号力扣加加留言的小伙伴奖励现金红包 10 元。 1. 头条校招题目描述123456789101112131415161718192021222324252627头条的 2017 校招开始了!为了这次校招,我们组织了一个规模宏大的出题团队,每个出题人都出了一些有趣的题目,而我们现在想把这些题目组合成若干场考试出来,在选题之前,我们对题目进行了盲审,并定出了每道题的难度系统。一场考试包含 3 道开放性题目,假设他们的难度从小到大分别为 a,b,c,我们希望这 3 道题能满足下列条件:a<=b<=cb-a<=10c-b<=10所有出题人一共出了 n 道开放性题目。现在我们想把这 n 道题分布到若干场考试中(1 场或多场,每道题都必须使用且只能用一次),然而由于上述条件的限制,可能有一些考试没法凑够 3 道题,因此出题人就需要多出一些适当难度的题目来让每场考试都达到要求,然而我们出题已经出得很累了,你能计算出我们最少还需要再出几道题吗?输入描述:输入的第一行包含一个整数 n,表示目前已经出好的题目数量。第二行给出每道题目的难度系数 d1,d2,...,dn。数据范围对于 30%的数据,1 ≤ n,di ≤ 5;对于 100%的数据,1 ≤ n ≤ 10^5,1 ≤ di ≤ 100。在样例中,一种可行的方案是添加 2 个难度分别为 20 和 50 的题目,这样可以组合成两场考试:(20 20 23)和(35,40,50)。输出描述:输出只包括一行,即所求的答案。示例 1输入420 35 23 40输出2 思路这道题看起来很复杂, 你需要考虑很多的情况。,属于那种没有技术含量,但是考验编程能力的题目,需要思维足够严密。这种模拟的题目,就是题目让我干什么我干什么。 类似之前写的囚徒房间问题,约瑟夫环也是模拟,只不过模拟之后需要你剪枝优化。 这道题的情况其实很多, 我们需要考虑每一套题中的难度情况, 而不需要考虑不同套题的难度情况。题目要求我们满足:a<=b<=c b-a<=10 c-b<=10,也就是题目难度从小到大排序之后,相邻的难度不能大于 10 。 因此我们的思路就是先排序,之后从小到大遍历,如果满足相邻的难度不大于 10 ,则继续。如果不满足, 我们就只能让字节的老师出一道题使得满足条件。 由于只需要比较同一套题目的难度,因此我的想法就是比较同一套题目的第二个和第一个,以及第三个和第二个的 diff。 如果 diff 小于 10,什么都不做,继续。 如果 diff 大于 10,我们必须补充题目。 这里有几个点需要注意。 对于第二题来说: 比如 1 30 40 这样的难度。 我可以在 1,30 之间加一个 21,这样 1,21,30 就可以组成一一套。 比如 1 50 60 这样的难度。 我可以在 1,50 之间加 21, 41 才可以组成一套,自身(50)是无论如何都没办法组到这套题中的。 不难看出, 第二道题的临界点是 diff = 20 。 小于等于 20 都可以将自身组到套题,增加一道即可,否则需要增加两个,并且自身不能组到当前套题。 对于第三题来说: 比如 1 20 40。 我可以在 20,40 之间加一个 30,这样 1,20,30 就可以组成一一套,自身(40)是无法组到这套题的。 比如 1 20 60。 也是一样的,我可以在 20,60 之间加一个 30,自身(60)同样是没办法组到这套题中的。 不难看出, 第三道题的临界点是 diff = 10 。 小于等于 10 都可以将自身组到套题,否则需要增加一个,并且自身不能组到当前套题。 这就是所有的情况了。 有的同学比较好奇,我是怎么思考的。 我是怎么保障不重不漏的。 实际上,这道题就是一个决策树, 我画个决策树出来你就明白了。 图中红色边框表示自身可以组成套题的一部分, 我也用文字进行了说明。#2 代表第二题, #3 代表第三题。 从图中可以看出, 我已经考虑了所有情况。如果你能够像我一样画出这个决策图,我想你也不会漏的。当然我的解法并不一定是最优的,不过确实是一个非常好用,具有普适性的思维框架。 需要特别注意的是,由于需要凑整, 因此你需要使得题目的总数是 3 的倍数向上取整。 代码12345678910111213141516171819202122232425n = int(input())nums = list(map(int, input().split()))cnt = 0cur = 1nums.sort()for i in range(1, n): if cur == 3: cur = 1 continue diff = nums[i] - nums[i - 1] if diff <= 10: cur += 1 if 10 < diff <= 20: if cur == 1: cur = 3 if cur == 2: cur = 1 cnt += 1 if diff > 20: if cur == 1: cnt += 2 if cur == 2: cnt += 1 cur = 1print(cnt + 3 - cur) 复杂度分析 时间复杂度:由于使用了排序, 因此时间复杂度为 $O(NlogN)$。(假设使用了基于比较的排序) 空间复杂度:$O(1)$ 2. 异或题目描述12345678910111213141516171819202122给定整数 m 以及 n 各数字 A1,A2,..An,将数列 A 中所有元素两两异或,共能得到 n(n-1)/2 个结果,请求出这些结果中大于 m 的有多少个。输入描述:第一行包含两个整数 n,m.第二行给出 n 个整数 A1,A2,...,An。数据范围对于 30%的数据,1 <= n, m <= 1000对于 100%的数据,1 <= n, m, Ai <= 10^5输出描述:输出仅包括一行,即所求的答案输入例子 1:3 106 5 10输出例子 1:2 前置知识 异或运算的性质 如何高效比较两个数的大小(从高位到低位) 首先普及一下前置知识。 第一个是异或运算: 异或的性质:两个数字异或的结果 a^b 是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是如果同一位的数字相同则为 0,不同则为 1 异或的规律: 任何数和本身异或则为 0 任何数和 0 异或是本身 异或运算满足交换律,即: a ^ b ^ c = a ^ c ^ b 同时建议大家去看下我总结的几道位运算的经典题目。 位运算系列 其次要知道一个常识, 即比较两个数的大小, 我们是从高位到低位比较,这样才比较高效。 比如: 1231234561234 这三个数比较大小, 为了方便我们先补 0 ,使得大家的位数保持一致。 123012304561234 先比较第一位,1 比较 0 大, 因此 1234 最大。再比较第二位, 4 比 1 大, 因此 456 大于 123,后面位不需要比较了。这其实就是剪枝的思想。 有了这两个前提,我们来试下暴力法解决这道题。 思路暴力法就是枚举 $N^2 / 2$ 中组合, 让其两两按位异或,将得到的结果和 m 进行比较, 如果比 m 大, 则计数器 + 1, 最后返回计数器的值即可。 暴力的方法就如同题目描述的那样, 复杂度为 $N^2$。 一定过不了所有的测试用例, 不过大家实在没有好的解法的情况可以兜底。不管是牛客笔试还是实际的面试都是可行的。 接下来,让我们来分析一下暴力为什么低效,以及如何选取数据结构和算法能够使得这个过程变得高效。 记住这句话, 几乎所有的优化都是基于这种思维产生的,除非你开启了上帝模式,直接看了答案。 只不过等你熟悉了之后,这个思维过程会非常短, 以至于变成条件反射, 你感觉不到有这个过程, 这就是有了题感。 其实我刚才说的第二个前置知识就是我们优化的关键之一。 我举个例子, 比如 3 和 5 按位异或。 3 的二进制是 011, 5 的二进制是 101, 12011101 按照我前面讲的异或知识, 不难得出其异或结构就是 110。 上面我进行了三次异或: 第一次是最高位的 0 和 1 的异或, 结果为 1。 第二次是次高位的 1 和 0 的异或, 结果为 1。 第三次是最低位的 1 和 1 的异或, 结果为 0。 那如何 m 是 1 呢? 我们有必要进行三次异或么? 实际上进行第一次异或的时候已经知道了一定比 m(m 是 1) 大。因为第一次异或的结构导致其最高位为 1,也就是说其最小也不过是 100,也就是 4,一定是大于 1 的。这就是剪枝, 这就是算法优化的关键。 看出我一步一步的思维过程了么?所有的算法优化都需要经过类似的过程。 因此我的算法就是从高位开始两两异或,并且异或的结果和 m 对应的二进制位比较大小。 如果比 m 对应的二进制位大或者小,我们提前退出即可。 如果相等,我们继续往低位移动重复这个过程。 这虽然已经剪枝了,但是极端情况下,性能还是很差。比如: 123m: 1111a: 1010b: 0101 a,b 表示两个数,我们比较到最后才发现,其异或的值和 m 相等。因此极端情况,算法效率没有得到改进。 这里我想到了一点,就是如果一个数 a 的前缀和另外一个数 b 的前缀是一样的,那么 c 和 a 或者 c 和 b 的异或的结构前缀部分一定也是一样的。比如: 123a: 111000b: 111101c: 101011 a 和 b 有共同的前缀 111,c 和 a 异或过了,当再次和 b 异或的时候,实际上前三位是没有必要进行的,这也是重复的部分。这就是算法可以优化的部分, 这就是剪枝。 分析算法,找到算法的瓶颈部分,然后选取合适的数据结构和算法来优化到。 这句话很重要, 请务必记住。 在这里,我们用的就是剪枝技术,关于剪枝,91 天学算法也有详细的介绍。 回到前面讲到的算法瓶颈, 多个数是有共同前缀的, 前缀部分就是我们浪费的运算次数, 说到前缀大家应该可以想到前缀树。如果不熟悉前缀树的话,看下我的这个前缀树专题,里面的题全部手写一遍就差不多了。 因此一种想法就是建立一个前缀树, 树的根就是最高的位。 由于题目要求异或, 我们知道异或是二进制的位运算, 因此这棵树要存二进制才比较好。 反手看了一眼数据范围:m, n<=10^5 。 10^5 = 2 ^ x,我们的目标是求出 满足条件的 x 的 ceil(向上取整),因此 x 应该是 17。 树的每一个节点存储的是:n 个数中,从根节点到当前节点形成的前缀有多少个是一样的,即多少个数的前缀是一样的。这样可以剪枝,提前退出的时候,就直接取出来用了。比如异或的结果是 1, m 当前二进制位是 0 ,那么这个前缀有 10 个,我都不需要比较了, 计数器直接 + 10 。 我用 17 直接复杂度过高,目前仅仅通过了 70 % - 80 % 测试用例, 希望大家可以帮我找找毛病,我猜测是语言的锅。 代码12345678910111213141516171819202122232425262728293031323334353637class TreeNode: def __init__(self): self.cnt = 1 self.children = [None] * 2def solve(num, i, cur): if cur == None or i == -1: return 0 bit = (num >> i) & 1 mbit = (m >> i) & 1 if bit == 0 and mbit == 0: return (cur.children[1].cnt if cur.children[1] else 0) + solve(num, i - 1, cur.children[0]) if bit == 1 and mbit == 0: return (cur.children[0].cnt if cur.children[0] else 0) + solve(num, i - 1, cur.children[1]) if bit == 0 and mbit == 1: return solve(num, i - 1, cur.children[1]) if bit == 1 and mbit == 1: return solve(num, i - 1, cur.children[0])def preprocess(nums, root): for num in nums: cur = root for i in range(16, -1, -1): bit = (num >> i) & 1 if cur.children[bit]: cur.children[bit].cnt += 1 else: cur.children[bit] = TreeNode() cur = cur.children[bit]n, m = map(int, input().split())nums = list(map(int, input().split()))root = TreeNode()preprocess(nums, root)ans = 0for num in nums: ans += solve(num, 16, root)print(ans // 2) 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 3. 字典序题目描述1234567891011121314151617181920212223给定整数 n 和 m, 将 1 到 n 的这 n 个整数按字典序排列之后, 求其中的第 m 个数。对于 n=11, m=4, 按字典序排列依次为 1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9, 因此第 4 个数是 2.对于 n=200, m=25, 按字典序排列依次为 1 10 100 101 102 103 104 105 106 107 108 109 11 110 111 112 113 114 115 116 117 118 119 12 120 121 122 123 124 125 126 127 128 129 13 130 131 132 133 134 135 136 137 138 139 14 140 141 142 143 144 145 146 147 148 149 15 150 151 152 153 154 155 156 157 158 159 16 160 161 162 163 164 165 166 167 168 169 17 170 171 172 173 174 175 176 177 178 179 18 180 181 182 183 184 185 186 187 188 189 19 190 191 192 193 194 195 196 197 198 199 2 20 200 21 22 23 24 25 26 27 28 29 3 30 31 32 33 34 35 36 37 38 39 4 40 41 42 43 44 45 46 47 48 49 5 50 51 52 53 54 55 56 57 58 59 6 60 61 62 63 64 65 66 67 68 69 7 70 71 72 73 74 75 76 77 78 79 8 80 81 82 83 84 85 86 87 88 89 9 90 91 92 93 94 95 96 97 98 99 因此第 25 个数是 120…输入描述:输入仅包含两个整数 n 和 m。数据范围:对于 20%的数据, 1 <= m <= n <= 5 ;对于 80%的数据, 1 <= m <= n <= 10^7 ;对于 100%的数据, 1 <= m <= n <= 10^18.输出描述:输出仅包括一行, 即所求排列中的第 m 个数字.示例 1输入11 4输出2 前置知识 十叉树 完全十叉树 计算完全十叉树的节点个数 字典树 思路和上面题目思路一样, 先从暴力解法开始,尝试打开思路。 暴力兜底的思路是直接生成一个长度为 n 的数组, 排序,选第 m 个即可。代码: 1234n, m = map(int, input().split())nums = [str(i) for i in range(1, n + 1)]print(sorted(nums)[m - 1]) 复杂度分析 时间复杂度:取决于排序算法, 不妨认为是 $O(NlogN)$ 空间复杂度: $O(N)$ 这种算法可以 pass 50 % case。 上面算法低效的原因是开辟了 N 的空间,并对整 N 个 元素进行了排序。 一种简单的优化方法是将排序换成堆,利用堆的特性求第 k 大的数, 这样时间复杂度可以减低到 $mlogN$。 我们继续优化。实际上,你如果把字典序的排序结构画出来, 可以发现他本质就是一个十叉树,并且是一个完全十叉树。 接下来,我带你继续分析。 如图, 红色表示根节点。节点表示一个十进制数, 树的路径存储真正的数字,比如图上的 100,109 等。 这不就是上面讲的前缀树么? 如图黄色部分, 表示字典序的顺序,注意箭头的方向。因此本质上,求字典序第 m 个数, 就是求这棵树的前序遍历的第 m 个节点。 因此一种优化思路就是构建一颗这样的树,然后去遍历。 构建的复杂度是 $O(N)$,遍历的复杂度是 $O(M)$。因此这种算法的复杂度可以达到 $O(max(m, n))$ ,由于 n >= m,因此就是 $O(N)$。 实际上, 这样的优化算法依然是无法 AC 全部测试用例的,会超内存限制。 因此我们的思路只能是不使用 N 的空间去构造树。想想也知道, 由于 N 最大可能为 10^18,一个数按照 4 字节来算, 那么这就有 400000000 字节,大约是 381 M,这是不能接受的。 上面提到这道题就是一个完全十叉树的前序遍历,问题转化为求完全十叉树的前序遍历的第 m 个数。 十叉树和二叉树没有本质不同, 我在二叉树专题部分, 也提到了 N 叉树都可以用二叉树来表示。 对于一个节点来说,第 m 个节点: 要么就是它本身 要么其孩子节点中 要么在其兄弟节点 要么在兄弟节点的孩子节点中 究竟在上面的四个部分的哪,取决于其孩子节点的个数。 count > m ,m 在其孩子节点中,我们需要深入到子节点。 count <= m ,m 不在自身和孩子节点, 我们应该跳过所有孩子节点,直接到兄弟节点。 这本质就是一个递归的过程。 需要注意的是,我们并不会真正的在树上走,因此上面提到的深入到子节点, 以及 跳过所有孩子节点,直接到兄弟节点如何操作呢? 你仔细观察会发现: 如果当前节点的前缀是 x ,那么其第一个子节点(就是最小的子节点)是 x * 10,第二个就是 x * 10 + 1,以此类推。因此: 深入到子节点就是 x * 10。 跳过所有孩子节点,直接到兄弟节点就是 x + 1。 ok,铺垫地差不多了。 接下来,我们的重点是如何计算给定节点的孩子节点的个数。 这个过程和完全二叉树计算节点个数并无二致,这个算法的时间复杂度应该是 $O(logN*logN)$。 如果不会的同学,可以参考力扣原题: 222. 完全二叉树的节点个数 ,这是一个难度为中等的题目。 因此这道题本身被划分为 hard,一点都不为过。 这里简单说下,计算给定节点的孩子节点的个数的思路, 我的 91 天学算法里出过这道题。 一种简单但非最优的思路是分别计算左右子树的深度。 如果当前节点的左右子树高度相同,那么左子树是一个满二叉树,右子树是一个完全二叉树。 否则(左边的高度大于右边),那么左子树是一个完全二叉树,右子树是一个满二叉树。 如果是满二叉树,当前节点数 是 2 ^ depth,而对于完全二叉树,我们继续递归即可。 123456789101112131415class Solution: def countNodes(self, root): if not root: return 0 ld = self.getDepth(root.left) rd = self.getDepth(root.right) if ld == rd: return 2 ** ld + self.countNodes(root.right) else: return 2 ** rd + self.countNodes(root.left) def getDepth(self, root): if not root: return 0 return 1 + self.getDepth(root.left) 复杂度分析 时间复杂度:$O(logN * log N)$ 空间复杂度:$O(logN)$ 而这道题, 我们可以更简单和高效。 比如我们要计算 1 号节点的子节点个数。 它的孩子节点个数是 。。。 它的孙子节点个数是 。。。 。。。 全部加起来即可。 它的孩子节点个数是 20 - 10 = 10 。 也就是它的右边的兄弟节点的第一个子节点 减去 它的第一个子节点。 由于是完全十叉树,而不是满十叉树 。因此你需要考虑边界情况,比如题目的 n 是 15。 那么 1 的子节点个数就不是 20 - 10 = 10 了, 而是 15 - 10 + 1 = 16。 其他也是类似的过程, 我们只要: Go deeper and do the same thing 或者: Move to next neighbor and do the same thing 不断重复,直到 m 降低到 0 。 代码12345678910111213141516171819202122def count(c1, c2, n): steps = 0 while c1 <= n: steps += min(n + 1, c2) - c1 c1 *= 10 c2 *= 10 return stepsdef findKthNumber(n: int, k: int) -> int: cur = 1 k = k - 1 while k > 0: steps = count(cur, cur + 1, n) if steps <= k: cur += 1 k -= steps else: cur *= 10 k -= 1 return curn, m = map(int, input().split())print(findKthNumber(n, m)) 复杂度分析 时间复杂度:$O(logM * log N)$ 空间复杂度:$O(1)$ 总结其中三道算法题从难度上来说,基本都是困难难度。从内容来看,基本都是力扣的换皮题,且都或多或少和树有关。如果大家一开始没有思路,建议大家先给出暴力的解法兜底,再画图或举简单例子打开思路。 我也刷了很多字节的题了,还有一些难度比较大的题。如果你第一次做,那么需要你思考比较久才能想出来。加上面试紧张,很可能做不出来。这个时候就更需要你冷静分析,先暴力打底,慢慢优化。有时候即使给不了最优解,让面试官看出你的思路也很重要。 比如小兔的棋盘 想出最优解难度就不低,不过你可以先暴力 DFS 解决,再 DP 优化会慢慢帮你打开思路。有时候面试官也会引导你,给你提示, 加上你刚才“发挥不错”,说不定一下子就做出最优解了,这个我深有体会。 另外要提醒大家的是, 刷题要适量,不要贪多。要完全理清一道题的来龙去脉。多问几个为什么。 这道题暴力法怎么做?暴力法哪有问题?怎么优化?为什么选了这个算法就可以优化?为什么这种算法要用这种数据结构来实现? 扩展最近的力扣的周赛 1803. 统计异或值在范围内的数对有多少竟然也和这篇文章中的第二道题撞了。lucifer 我直接将第二道题的代码稍微改下就 AC 了。 这道题涉及了二进制前缀和的考点,看来大家还是比较喜欢考察的。一般二进制前缀树的题目难度基本都是困难。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"滑动窗口","slug":"算法/滑动窗口","permalink":"https://lucifer.ren/blog/categories/算法/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"},{"name":"字节跳动","slug":"面试/字节跳动","permalink":"https://lucifer.ren/blog/categories/面试/字节跳动/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"},{"name":"字节跳动","slug":"字节跳动","permalink":"https://lucifer.ren/blog/tags/字节跳动/"}]},{"title":"对《丢鸡蛋问题》的一点补充","slug":"887.super-egg-drop-extension","date":"2020-08-29T16:00:00.000Z","updated":"2023-01-05T12:24:49.765Z","comments":true,"path":"2020/08/30/887.super-egg-drop-extension/","link":"","permalink":"https://lucifer.ren/blog/2020/08/30/887.super-egg-drop-extension/","excerpt":"​ 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 于是【异议!】系列应运而生。它是个什么东西呢?我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,「力扣加加」 来回答你。我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列「第二弹」。 ​","text":"​ 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 于是【异议!】系列应运而生。它是个什么东西呢?我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,「力扣加加」 来回答你。我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列「第二弹」。 ​ 原题地址:https://leetcode-cn.com/problems/super-egg-drop/ 事情的起源昨天有人在我的力扣题解下留言,问我《丢鸡蛋问题》重制版来袭~》题解中为什么第二种算法是加法而不是 min 什么的。毕竟我的第一种算法可是 min(max(碎, 不碎)),为什么第二种就是加法了呢?这个细节我在写题解的时候漏掉了,我打算详细给大家说一下。 题目描述你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。 每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。 你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。 每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。 你的目标是确切地知道 F 的值是多少。 无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少? 示例 1: 输入:K = 1, N = 2 输出:2 解释:鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。如果它没碎,那么我们肯定知道 F = 2 。因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。示例 2: 输入:K = 2, N = 6 输出:3 示例 3: 输入:K = 3, N = 14 输出:4 提示: 1 <= K <= 100 1 <= N <= 10000 我当时的解法我们这样来思考这个问题。 既然题目要求最少的扔的次数,假设有一个函数 f(k, i),他的功能是求出 k 个鸡蛋,扔 i 次所能检测的最高楼层。 我们只需要不断进行发问: ”f 函数啊 f 函数,我扔一次可以么?“, 也就是判断 f(k, 1) >= N 的返回值 ”f 函数啊 f 函数,我扔两次呢?“, 也就是判断 f(k, 2) >= N 的返回值 … ”f 函数啊 f 函数,我扔 m 次呢?“, 也就是判断 f(k, m) >= N 的返回值 我们只需要返回第一个返回值为 true 的 m 即可。 想到这里,我条件发射地想到了二分法。 聪明的小朋友们,你们觉得二分可以么?为什么?欢迎评论区留言讨论。 那么这个神奇的 f 函数怎么实现呢?其实很简单。 摔碎的情况,可以检测的最高楼层是f(m - 1, k - 1) + 1。因为碎了嘛,我们多检测了摔碎的这一层。 没有摔碎的情况,可以检测的最高楼层是f(m - 1, k)。因为没有碎,也就是说我们啥都没检测出来(对能检测的最高楼层无贡献)。 我们来看下代码: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: def f(m, k): if k == 0 or m == 0: return 0 return f(m - 1, k - 1) + 1 + f(m - 1, k) m = 0 while f(m, K) < N: m += 1 return m 上面的代码可以 AC。我们来顺手优化成迭代式。 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m 代码代码支持:JavaSCript,Python Python: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m JavaSCript: 12345678910111213var superEggDrop = function (K, N) { // 不选择dp[K][M]的原因是dp[M][K]可以简化操作 const dp = Array(N + 1) .fill(0) .map((_) => Array(K + 1).fill(0)); let m = 0; while (dp[m][K] < N) { m++; for (let k = 1; k <= K; ++k) dp[m][k] = dp[m - 1][k - 1] + 1 + dp[m - 1][k]; } return m;}; 复杂度分析 时间复杂度:$O(m * K)$,其中 m 为答案。 空间复杂度:$O(K * N)$ 为什么是加法在解法一种我提到了:算法一的本质就是暴力地枚举所有的可能楼层,然后比较最坏情况下的最少扔鸡蛋次数。而实际上解法二也是基于这个大前提,假设我们选择一个楼层是 x。那么在 x 层扔鸡蛋会有两种可能: 鸡蛋碎了。 说明目标楼层 F 就是本层或者比本层低,楼上的 N - x 层不需要检测了,全部排除了。因此我们需要继续探测楼下 x - 1 层,而我们剩下可以探测的最高楼层为 dp[k - 1][m - 1]。由于我们需要探测到具体的 F,因此我们需要使得 dp[k - 1][m - 1] >= x - 1。 鸡蛋没碎。 说明目标楼层 F 比本层高,楼下的 x - 1 不需要检测了,全部排除了。因此我们需要继续探测楼上 N - x 层,而我们剩下可以探测的最高楼层为 dp[k][m - 1]。由于我们需要探测到具体的 F,因此我们需要使得 dp[k][m - 1] >= N - x。 无论鸡蛋碎还是不碎,我们都只需要检测上面或者检测下面,不需要同时检测。 也就是说我需要找到一个楼层 x, 使得 x 同时满足: dp[k - 1][m - 1] >= x - 1 dp[k][m - 1] >= N - x 这样能保证 100% 可以检测出来目标楼层 F。实际上不管鸡蛋碎不碎,可以检测的楼层都是 max(dp[k - 1][m - 1], x -1) + max(dp[k][m - 1], N -x) + 1,由于 dp[k - 1][m - 1] >= x - 1,dp[k][m - 1] >= N - x,因此可以检测的楼层就是 dp[k - 1][m - 1] + dp[k][m - 1] + 1。 这个我可能需要解释一下。 由于选择 x 开始扔之前已经确定了上面的两个不等式是成立的了,因此如果鸡蛋没碎,我们需要继续往上检测,下面是不需要检测的,虽然下面是 x - 1,但是为了保证万一碎的情况也有解,所以有 dp[k - 1][m - 1] >= x - 1,因此实际上可以确定的最大楼层是 dp[k][m - 1] + max(dp[k - 1][m - 1], x -1) + 1,也就是 dp[k][m - 1] + dp[k - 1][m - 1] + 1。鸡蛋如果碎的情况也是一样的分析逻辑。 大家对这道题还有任何问题,都可以留言告诉我! 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"动态规划问题为什么要画表格?","slug":"91algo-dp-lecture","date":"2020-08-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.748Z","comments":true,"path":"2020/08/27/91algo-dp-lecture/","link":"","permalink":"https://lucifer.ren/blog/2020/08/27/91algo-dp-lecture/","excerpt":"本文是我的 91 算法第一期的部分讲义内容。 91 算法第一期已经接近尾声,二期的具体时间关注我的公众号即可,一旦开放,会第一时间在公众号《力扣加加》通知大家。 动态规划可以理解为是查表的递归(记忆化)。那么什么是递归?什么是查表(记忆化)? ​","text":"本文是我的 91 算法第一期的部分讲义内容。 91 算法第一期已经接近尾声,二期的具体时间关注我的公众号即可,一旦开放,会第一时间在公众号《力扣加加》通知大家。 动态规划可以理解为是查表的递归(记忆化)。那么什么是递归?什么是查表(记忆化)? ​ 递归定义: 递归是指在函数的定义中使用函数自身的方法。 算法中使用递归可以很简单地完成一些用循环实现的功能,比如二叉树的左中右序遍历。递归在算法中有非常广泛的使用,包括现在日趋流行的函数式编程。 纯粹的函数式编程中没有循环,只有递归。 有意义的递归算法会把问题分解成规模缩小的同类子问题,当子问题缩写到寻常的时候,我们可以知道它的解。然后我们建立递归函数之间的联系即可解决原问题,这也是我们使用递归的意义。准确来说, 递归并不是算法,它是和迭代对应的一种编程方法。只不过,我们通常借助递归去分解问题而已。 一个问题要使用递归来解决必须有递归终止条件(算法的有穷性),也就是顺递归会逐步缩小规模到寻常。 虽然以下代码也是递归,但由于其无法结束,因此不是一个有效的算法: 12def f(n): return n + f(n - 1) 更多的情况应该是: 123def f(n): if n == 1: return 1 return n + f(n - 1) 练习递归一个简单练习递归的方式是将你写的迭代全部改成递归形式。比如你写了一个程序,功能是“将一个字符串逆序输出”,那么使用迭代将其写出来会非常容易,那么你是否可以使用递归写出来呢?通过这样的练习,可以让你逐步适应使用递归来写程序。 如果你已经对递归比较熟悉了,那么我们继续往下看。 递归中的重复计算递归中可能存在这么多的重复计算,为了消除这种重复计算,一种简单的方式就是记忆化递归。即一边递归一边使用“记录表”(比如哈希表或者数组)记录我们已经计算过的情况,当下次再次碰到的时候,如果之前已经计算了,那么直接返回即可,这样就避免了重复计算。而动态规划中 DP 数组其实和这里“记录表”的作用是一样的。 递归的时间复杂度分析敬请期待我的新书。 小结使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。这里我列举了几道算法题目,这几道算法题目都可以用递归轻松写出来: 递归实现 sum 二叉树的遍历 走楼梯问题 汉诺塔问题 杨辉三角 当你已经适应了递归的时候,那就让我们继续学习动态规划吧! 动态规划如果你已经熟悉了递归的技巧,那么使用递归解决问题非常符合人的直觉,代码写起来也比较简单。这个时候我们来关注另一个问题 - 重复计算 。我们可以通过分析(可以尝试画一个递归树),可以看出递归在缩小问题规模的同时是否可能会重复计算。 279.perfect-squares 中 我通过递归的方式来解决这个问题,同时内部维护了一个缓存来存储计算过的运算,这么做可以减少很多运算。 这其实和动态规划有着异曲同工的地方。 小提示:如果你发现并没有重复计算,那么就没有必要用记忆化递归或者动态规划了。 因此动态规划就是枚举所以可能。不过相比暴力枚举,动态规划不会有重复计算。因此如何保证枚举时不重不漏是关键点之一。 递归由于使用了函数调用栈来存储数据,因此如果栈变得很大,那么会容易爆栈。 爆栈我们结合求和问题来讲解一下,题目是给定一个数组,求出数组中所有项的和,要求使用递归实现。 代码: 123456function sum(nums) { if (nums.length === 0) return 0; if (nums.length === 1) return nums[0]; return nums[0] + sum(nums.slice(1));} 我们用递归树来直观地看一下。 这种做法本身没有问题,但是每次执行一个函数都有一定的开销,拿 JS 引擎执行 JS 来说,每次函数执行都会进行入栈操作,并进行预处理和执行过程,所以内存会有额外的开销,数据量大的时候很容易造成爆栈。 浏览器中的 JS 引擎对于代码执行栈的长度是有限制的,超过会爆栈,抛出异常。 重复计算我们再举一个重复计算的例子,问题描述: 一个人爬楼梯,每次只能爬 1 个或 2 个台阶,假设有 n 个台阶,那么这个人有多少种不同的爬楼梯方法? 由于上第 n 级台阶一定是从 n - 1 或者 n - 2 来的,因此 上第 n 级台阶的数目就是 上 n - 1 级台阶的数目加上 n - 1 级台阶的数目。 递归代码: 12345function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; return climbStairs(n - 1) + climbStairs(n - 2);} 我们继续用一个递归树来直观感受以下: 红色表示重复的计算 可以看出这里面有很多重复计算,我们可以使用一个 hashtable 去缓存中间计算结果,从而省去不必要的计算。 那么动态规划是怎么解决这个问题呢? 答案也是“查表”,不过区别于递归使用函数调用栈,动态规划通常使用的是 dp 数组,数组的索引通常是问题规模,值通常是递归函数的返回值。递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。 如果上面的爬楼梯问题,使用动态规划,代码是这样的: 1234567891011function climbStairs(n) { if (n == 1) return 1; const dp = new Array(n); dp[0] = 1; dp[1] = 2; for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 不会也没关系,我们将递归的代码稍微改造一下。其实就是将函数的名字改一下: 12345function dp(n) { if (n === 1) return 1; if (n === 2) return 2; return dp(n - 1) + dp(n - 2);} dp[n] 和 dp(n) 对比看,这样是不是有点理解了呢? 只不过递归用调用栈枚举状态, 而动态规划使用迭代枚举状态。 动态规划的查表过程如果画成图,就是这样的: 虚线代表的是查表过程 这道题目是动态规划中最简单的问题了,因为设计到单个因素的变化,如果涉及到多个因素,就比较复杂了,比如著名的背包问题,挖金矿问题等。 对于单个因素的,我们最多只需要一个一维数组即可,对于如背包问题我们需要二维数组等更高纬度。 爬楼梯我们并没有必要使用一维数组,而是借助两个变量来实现的,空间复杂度是 O(1)。代码: 12345678910111213141516function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; let a = 1; let b = 2; let temp; for (let i = 3; i <= n; i++) { temp = a + b; a = b; b = temp; } return temp;} 之所以能这么做,是因为爬楼梯问题的状态转移方程中当前状态只和前两个状态有关,因此只需要存储这两个即可。 动态规划问题有很多这种讨巧的方式,这个技巧叫做滚动数组。 再次强调一下: 如果说递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。 记忆化递归和动态规划没有本质不同。都是枚举状态,并根据状态直接的联系逐步推导求解。 动态规划性能通常更好。 一方面是递归的栈开销,一方面是滚动数组的技巧。 动态规划的三个要素 状态转移方程 临界条件 枚举状态 可以看出,用递归解决也是一样的思路 在上面讲解的爬楼梯问题中,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么: 12f(1) 与 f(2) 就是【边界】f(n) = f(n-1) + f(n-2) 就是【状态转移公式】 我用动态规划的形式表示一下: 12dp[0] 与 dp[1] 就是【边界】dp[n] = dp[n - 1] + dp[n - 2] 就是【状态转移方程】 可以看出两者是多么的相似。 实际上临界条件相对简单,大家只有多刷几道题,里面就有感觉。困难的是找到状态转移方程和枚举状态。这两个核心点的都建立在已经抽象好了状态的基础上。比如爬楼梯的问题,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么 f(1), f(2), … 就是各个独立的状态。 不过状态的定义都有特点的套路。 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 当然状态转移方程可能不止一个, 不同的转移方程对应的效率也可能大相径庭,这个就是比较玄学的话题了,需要大家在做题的过程中领悟。 搞定了状态的定义,那么我们来看下状态转移方程。 状态转移方程爬楼梯问题由于上第 n 级台阶一定是从 n - 1 或者 n - 2 来的,因此 上第 n 级台阶的数目就是 上 n - 1 级台阶的数目加上 n - 1 级台阶的数目。 上面的这个理解是核心, 它就是我们的状态转移方程,用代码表示就是 f(n) = f(n - 1) + f(n - 2)。 实际操作的过程,有可能题目和爬楼梯一样直观,我们不难想到。也可能隐藏很深或者维度过高。 如果你实在想不到,可以尝试画图打开思路,这也是我刚学习动态规划时候的方法。当你做题量上去了,你的题感就会来,那个时候就可以不用画图了。 状态转移方程实在是没有什么灵丹妙药,不同的题目有不同的解法。状态转移方程同时也是解决动态规划问题中最最困难和关键的点,大家一定要多多练习,提高题感。接下来,我们来看下不那么困难,但是新手疑问比较多的问题 - 如何枚举状态。 如何枚举状态前面说了如何枚举状态,才能不重不漏是枚举状态的关键所在。 如果是一维状态,那么我们使用一层循环可以搞定。 如果是两维状态,那么我们使用两层循环可以搞定。 。。。 这样可以保证不重不漏。 但是实际操作的过程有很多细节比如: 一维状态我是先枚举左边的还是右边的?(从左到右遍历还是从右到左遍历) 二维状态我是先枚举左上边的还是右上的,还是左下的还是右下的? 里层循环和外层循环的位置关系(可以互换么) 。。。 其实这个东西和很多因素有关,很难总结出一个规律,而且我认为也完全没有必要去总结规律。不过这里我还是总结了一个关键点,那就是: 如果你没有使用滚动数组的技巧,那么遍历顺序取决于状态转移方程。比如: 12for i in range(1, n + 1): dp[i] = dp[i - 1] + 1; 那么我们就需要从左到右遍历,原因很简单,因为 dp[i] 依赖于 dp[i - 1],因此计算 dp[i] 的时候, dp[i - 1] 需要已经计算好了。 二维的也是一样的,大家可以试试。 如果你使用了滚动数组的技巧,则怎么遍历都可以,但是不同的遍历意义通常不不同的。比如我将二维的压缩到了一维: 123for i in range(1, n + 1): for j in range(1, n + 1): dp[j] = dp[j - 1] + 1; 这样是可以的。 dp[j - 1] 实际上指的是压缩前的 dp[i][j - 1] 而: 1234for i in range(1, n + 1): # 倒着遍历 for j in range(n, 0, -1): dp[j] = dp[j - 1] + 1; 这样也是可以的。 但是 dp[j - 1] 实际上指的是压缩前的 dp[i - 1][j - 1]。因此实际中采用怎么样的遍历手段取决于题目。我特意写了一个 【完全背包问题】套路题(1449. 数位成本和为目标值的最大数字 文章,通过一个具体的例子告诉大家不同的遍历有什么实际不同,强烈建议大家看看,并顺手给个三连。 关于里外循环的问题,其实和上面原理类似。 这个比较微妙,大家可以参考这篇文章理解一下 0518.coin-change-2。 小结关于如何确定临界条件通常是比较简单的,多做几个题就可以快速掌握。 关于如何确定状态转移方程,这个其实比较困难。 不过所幸的是,这些套路性比较强, 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 这样遇到新的题目可以往上套, 实在套不出那就先老实画图,不断观察,提高题感。 关于如何枚举状态,如果没有滚动数组, 那么根据转移方程决定如何枚举即可。 如果用了滚动数组,那么要注意压缩后和压缩前的 dp 对应关系即可。 动态规划为什么要画表格动态规划问题要画表格,但是有的人不知道为什么要画,就觉得这个是必然的,必要要画表格才是动态规划。 其实动态规划本质上是将大问题转化为小问题,然后大问题的解是和小问题有关联的,换句话说大问题可以由小问题进行计算得到。这一点是和用递归解决一样的, 但是动态规划是一种类似查表的方法来缩短时间复杂度和空间复杂度。 画表格的目的就是去不断推导,完成状态转移, 表格中的每一个 cell 都是一个小问题, 我们填表的过程其实就是在解决问题的过程, 我们先解决规模为寻常的情况,然后根据这个结果逐步推导,通常情况下,表格的右下角是问题的最大的规模,也就是我们想要求解的规模。 比如我们用动态规划解决背包问题, 其实就是在不断根据之前的小问题A[i - 1][j] A[i -1][w - wj]来询问: 应该选择它 还是不选择它 至于判断的标准很简单,就是价值最大,因此我们要做的就是对于选择和不选择两种情况分别求价值,然后取最大,最后更新 cell 即可。 其实大部分的动态规划问题套路都是“选择”或者“不选择”,也就是说是一种“选择题”。 并且大多数动态规划题目还伴随着空间的优化(滚动数组),这是动态规划相对于传统的记忆化递归优势的地方。除了这点优势,就是上文提到的使用动态规划可以减少递归产生的函数调用栈,因此性能上更好。 相关问题 0091.decode-ways 0139.word-break 0198.house-robber 0309.best-time-to-buy-and-sell-stock-with-cooldown 0322.coin-change 0416.partition-equal-subset-sum 0518.coin-change-2 总结本篇文章总结了算法中比较常用的两个方法 - 递归和动态规划。递归的话可以拿树的题目练手,动态规划的话则将我上面推荐的刷完,再考虑去刷力扣的动态规划标签即可。 大家前期学习动态规划的时候,可以先尝试使用记忆化递归解决。然后将其改造为动态规划,这样多练习几次就会有感觉。之后大家可以练习一下滚动数组,这个技巧很有用,并且相对来说比较简单。 比较动态规划的难点在于枚举所以状态(无重复) 和 寻找状态转移方程。 如果你只能记住一句话,那么请记住:递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。 另外,大家可以去 LeetCode 探索中的 递归 I 中进行互动式学习。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"TypeScript 配置文件该怎么写?","slug":"ts-config","date":"2020-08-23T16:00:00.000Z","updated":"2023-01-07T12:34:34.455Z","comments":true,"path":"2020/08/24/ts-config/","link":"","permalink":"https://lucifer.ren/blog/2020/08/24/ts-config/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(已发布) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写?(就是本文) TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(已发布) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写?(就是本文) TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言这篇文章是我的 TypeScript 系列的第 5 篇。今天我们就来看下, TypeScript 的配置文件 tsconfig.json 该如何写。 和 package.json 一样, 它也是一个 JSON 文件。package.json 是包描述文件,对应的 Commonjs 规范,而 tsconfig.json 是最终被 TypeScript Compiler 解析和使用的一个 JSON 文件。 TypeScript Compiler 用这个配置文件来决定如何对项目进行编译。 说到编译,不得不提一个知名选手 - babel。 和 TypeScript 类似, 他们都可以将一种语法静态编译成另外一种语法。如果说我想编译一个文件,我只需要告诉 babel 我的文件路径即可。 1npx babel script.js 有时候我想编译整个文件夹: 1npx babel src --out-dir lib babel 也可以指定输出目录,指定需要忽略的文件或目录等等, TypeScript 也是一样!你当然可以像 babel 一样在命令行中全部指定好,也可以将这些配置放到 tsconfig.json 中,以配置文件的形式传递给 TypeScript Compiler 。 这就是 tsconfig.json 文件的初衷,即接受用户输入作为配置项。 初探 tsconfig我们先来看一个简单的 tsconfig 文件。 12345678{ \"compilerOptions\": { \"outDir\": \"./built\", \"allowJs\": true, \"target\": \"es5\" }, \"include\": [\"./src/**/*\"]} 如上配置做了: 读取所有可识别的 src 目录下的文件(通过 include)。 接受 JavaScript 做为输入(通过 allowJs)。 生成的所有文件放在 built 目录下(通过 outDir)。 将 JavaScript 代码降级到低版本比如 ECMAScript 5(通过 target)。 实际项目有比这个更复杂。 接下来, 我们来进一步解读。 不过在讲配置项之前,我们先来看下 tsconfig.json 是如何被解析的。 tsconfig 是如何被解析的?如果一个目录下存在一个 tsconfig.json 文件,那么意味着这个目录是 TypeScript 项目的根目录。 如果你使用 tsc 编译你的项目,并且没有显式地指定配置文件的路径,那么 tsc 则会逐级向上搜索父目录寻找 tsconfig.json ,这个过程类似 node 的模块查找机制。 如图: 在 _uglify-js@3.7.2@uglify-js 下执行 tsc 则会找到 配置文件 1,在 _uglify-js@3.7.2@uglify-js/bin 下执行 tsc 也会找到 配置文件 1 同理在 lib,node_modules 也会找到 配置文件 1 在 _uglify-js@3.7.2@uglify-js/bin/lucifer 下执行 tsc 则会找到 配置文件 2 在 _uglify-js@3.7.2@uglify-js/lib/lucifer 下执行 tsc 则会找到 配置文件 3 我在 上帝视角看 TypeScript 一种讲述了 TypeScript 究竟做了什么,带你从宏观的角度看了一下 TypeScript。 其中提到了 TypeScript 编译器会接受文件或者文件集合作为输入,最终转换为 JavaScript(noEmit 为 false) 和 .d.ts(declarations 为 true)。 这里其实还少了一个点,那就是除了接受文件或者文件集合作为输入,还会接受 tsconfig.json。tsconfig.json 的内容决定了编译的范围和行为,不同的 配置可能会得到不同的输出,或者得到不同的检查结果。 当 tsc 找到了一个 tsconfig.json 文件,那么其规定的编译目录则全部会被 typescript 处理,当然也包括其依赖的文件。 如果 tsc 没有找到一个 tsconfig.json 或 tsconfig 没有有效信息,那么 tsc 会使用默认配置。 比如 tsconfig 是一个空的就没有有效信息: 1{} tsconfig 的全部属性,以及属性的默认值可以在这里找到: http://json.schemastore.org/tsconfig 总结一下 tsc 解析 tsconfig.json 的逻辑。 如果命令行指定了配置选项或者指定了配置文件的路径,那么直接会读取。 根据 tsconfig json schema 校验是否格式正确。 如果正确,则将其和默认配置合并(如果有 extends 字段,也会一起合并),将合并后的配置传递给 TypeScript 编译器并开始编译。 否则抛出错误 否则,会从当前目录查找 tsconfig.json 文件, 如果找不到则逐层向上搜索父目录。 如果找到了则会去根据 tsconfig json schema 校验是否格式正确。 如果正确,则将其和默认配置合并(如果有 extends 字段,也会一起合并),将合并后的配置传递给 TypeScript 编译器并开始编译。 否则抛出错误 否则,始终找不到则直接使用默认配置 tsconfig 的顶层属性tsconfig 的顶层属性(Top Level)不多,主要有:compilerOptions, files, include, exclude,extends,compileOnSave等。 compilerOptions 是重头戏,其属性也是最多的,我们的项目也是对这个定制比较多,这个我后面会重点讲。 files 则是你需要编译的文件 exclude 则是你不需要编译的文件目录(支持 glob) include 是你需要编译的文件目录(支持 glob) extends 就是继承另外一个配置文件,TypeScript 会对其进行合并,多项目公共配置有用。你也可以直接继承社区的“最佳实践”,比如: 12345678{ \"extends\": \"@tsconfig/node12/tsconfig.json\", \"compilerOptions\": {}, \"include\": [\"src/**/*\"], \"exclude\": [\"node_modules\"]} compileOnSave 则是和编辑器(确切地说是文件系统)联动的配置,即是否在文件保存后进行编译,实际项目不建议使用。 除了 compilerOptions,其他也相对比较好理解。 因此接下来我只针对 compilerOptions 详细讲解一番。 tsconfig 的编译项详细全面的内容,大家只需要参考官网的就好了。官网写的不仅全面,而且做了分类,非常清晰。 接下来,我会根据功能分开讲几个常用 的配置。 文件相关常用的是以下四个,由于前面已经做了介绍,因此就不赘述了。 exclude extends files include 严格检查 alwaysStrict 默认:false 首次发布版本:2.1 这个是和 ECMAScript 规范相关的,工作机制和 ES 5 的严格模式一样, 并且输出的 JS 顶部也会也会带上 ‘use strict’。 noImplicitAny(推荐打开) 默认:true 首次发布版本:- 我在 - TypeScript 类型系统 中提到了如果不对变量显式声明类型,那么 TypeScript 会对变量进行类型推导,这当然也有推导不出的情况,这个时候该变量的类型就是 any,这个叫做隐式 any。区别于显式 any: 1const a: any = {}; 隐式 any 是 TypeScript 编译器推断的。 noImplicitThis(推荐打开) 默认:true 首次发布版本:2.0 和隐式 any 类型, 只不过这次是针对的特殊的一个关键字 this,也就是你需要显式地指定 this 的类型。 strict(推荐打开) 默认:true 首次发布版本:2.3 实际上 strict 只是一个简写,是多个规则的合集。 类似于 babel 中插件(plugins)和 预设(presets)的差别。换句话说如果你指定了 strict 为 true ,那么所有严格相关的规则的都会开启,我所讲的严格检查都是,还有一部分我没有提到的。另外将来如果增加更多严格规则,你只要开启了 strict 则会自动加进来。 模块解析模块相关目的:allowSyntheticDefaultImports,allowUmdGlobalAccess,esModuleInterop,moduleResolution 都是为了和其他模块化规范兼容做的。 allowSyntheticDefaultImports allowUmdGlobalAccess esModuleInterop moduleResolution 还有一个配置 module,规定了项目的模块化方式,选项有 AMD,UMD,commonjs 等。 路径相关目的: baseUrl,paths,rootDirs, typeRoots,types 都是为了简化路径的拼写做的。 baseUrl 这个配置是告诉 TypeScript 如何解析模块路径的。比如: 123import { helloWorld } from \"hello/world\";console.log(helloWorld); 这个就会从 baseUrl 下找 hello 目录下的 world 文件。 paths 定义类似别名的存在,从而简化路径的书写。 rootDirs 注意是 rootDirs ,而不是 rootDir,也就是说根目录可以有多个。 当你指定了多个根目录的时候, 不同根目录的文件可以像在一个目录下一样互相访问。 实际上也有一个叫 rootDir 的, 和 rootDirs 的区别就是其只能指定一个。 typeRoots types types 和 typeRoots 我在 - types 和 @types 是什么? 已经讲得很清楚了,这里就不多说了。 项目配置JavaScript 相关 allowJs 默认:false 首次发布版本:1.8 顾名思义,允许在 TypeScript 项目中使用 JavaScript,这在从 JavaScript 迁移到 TypeScript 中是非常重要的。 checkJs 默认:false 首次发布版本:- 和 allowJs 类似, 只不过 checkJs 会额外对 JS 文件进行校验。 声明文件相关如果 TypeScript 是将 TS 文件编译为 JS,那么声明文件 + JS 文件就可以反推出 TS 文件。 这两个用来生成 .d.ts 和 .d.ts 的 sourcemap 文件。 declaration 默认:false 首次发布版本:1.0 declarationMap 默认:false 首次发布版本:2.9 外部库相关 jsx 默认:react 首次发布版本:2.2 这个是告诉 TypeScript 如何编译 jsx 语法的。 lib 默认:- 首次发布版本:2.0 lib 我在 TypeScript 类型系统 中讲过。 Typescript 提供了诸如 lib.d.ts 等类型库文件。随着 ES 的不断更新, JavaScript 类型和全局变量会逐渐变多。Typescript 也是采用这种 lib 的方式来解决的。 (TypeScript 提供的部分 lib) 输出相关outDir 和 outFile 这两个配置则是告诉 TypeScript 将文件生成到哪里。 outDir 默认:和 ts 文件同目录(且同名,只是后缀不同) 首次发布版本:- outFile 默认:- 首次发布版本:1.0 module 是 CommonJS 和 ES6 module 不能知道 outFile,只有是 None, System 或 AMD 才行,其会将这些模块的文件内容打包到全局文件内容之后。 而 noEmit 则是控制是否输出 JS 文件的。 noEmit 默认:false 首次发布版本:- 如果你只希望用 TypeScript 进行类型检查,不希望要它生成文件,则可以将 noEmit 设置成 true。 target 即输出的 JavaScript 对标的 ECMA 规范。 比如 “target”: “es6” 就是将 es6 + 的语法转换为 ES6 的 代码。其选项有 ES3,ES5,ES6 等。 为什么没有 ES4 ? ^_^ 总结 tsconfig 就是一个 JSON 文件,TypeScript 会使用该文件来决定如何编译和检查 TypeScript 项目。和 babel 类似,甚至很多配置项都是相通的。 如果一个目录下存在一个 tsconfig.json 文件,那么意味着这个目录是 TypeScript 项目的根目录。 如果你使用 tsc 编译你的项目,并且没有显式地指定配置文件的路径,那么 tsc 则会逐级向上搜索父目录寻找 tsconfig.json ,这个过程类似 node 的模块查找机制。 tsconfig 中最重要的恐怕就是编译器选项(compilerOptions)了。如果你按照功能去记忆则会比较简单, 比如文件相关的有哪些, 严格检查的有哪些,声明文件的有哪些等等。 参考 typescriptlang’s tsconfig 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"types 和 @types 是什么?","slug":"ts-type","date":"2020-08-20T16:00:00.000Z","updated":"2023-01-07T12:34:34.563Z","comments":true,"path":"2020/08/21/ts-type/","link":"","permalink":"https://lucifer.ren/blog/2020/08/21/ts-type/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在_逻辑上_比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(就是本文) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在_逻辑上_比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(就是本文) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言 作者:feiker & Lucifer TypeScript 中有几个概念和名字很像,会让初学者傻傻分不清楚。比如配置文件中的 _types 和 typeRoots_,并且还有一个 @types。接触过 TypeScript 的人一定接触过它们, 这几个有什么区别和联系呢?今天就带你来重新认识下它们。 一个例子这里我通过一个例子来说明一下什么是 @types,这样大家理解起来更深刻一点。 当我们用 npm 等包管理工具安装第三方包的时候,有些包并不是 TypeScript 编写的,自然也不会导出 TypeScript 声明文件。这种情况下,如果我们在 TypeScript 项目中引入了这种包,则会编译报错(没有设置 allowJS)。举个例子,当我们通过npm install jquery --save 安装 jquery 包并引用的时候,TypeScript 会报错。 allowJS 是 TypeScript 1.8 引进的一个编译项。 报错内容如下: Could not find a declaration file for module ‘jquery’. Try npm install @types/jquery if it exists or add a new declaration (.d.ts) file containing declare module 'jquery'; 这里的意思是 TypeScript 没有找到 jquery 这个包的定义,你可以通过npm install @types/jquery安装相关声明,或者自己定义一份.d.ts 文件,并将 jquery 声明为 module。 全世界不是 TypeScript 编写的包多了去了。即使你的包是 TypeScript 编写的,如果你没有导出声明文件,也是没用的。(TypeScript 默认不会导出声明文件,只会编译输出 JavaScript 文件)。因此 TypeScript 必须对这种情况提供解决方案,而上面的两种方案(安装 @types 和 自己 declare module)就是 TypeScript 官方提出的, 你可以选择适合你的方案。我的推荐是尽量使用 @types 下的声明,实在没有,再使用第二种方法。 值得一提的是,并不是所有的包都可以通过这种方式解决的, 能解决的是 DefinitelyTyped 组织已经写好定义的包, 好消息是比较流行的包基本都有。 如果你想查一个包是否在 @type 下,可以访问 https://microsoft.github.io/TypeSearch/ 那么 TypeScript 是怎么找定义的,什么情况会找不到定义而报类似上面举的例子的错误,这里简单介绍下原理。 包类型定义的查找就好像 node 的包查找是先在当前文件夹找 node_modules,在它下找递归找,如果找不到则往上层目录继续找,直到顶部一样, TypeScript 类型查找也是类似的方式。 具体来说就是: TypeScript 编译器先在当前编译上下文找 jquery 的定义。 如果找不到,则会去 node_modules 中的@types (默认情况,目录可以修改,后面会提到)目录下去寻找对应包名的模块声明文件。 @types/*模块声明文件由社区维护,通过发布到@types 空间下。 GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions. 变量类型定义的查找和包查找类似,默认情况下变量类型定义的查找也会去 @types 下去寻找。只不过并不是直接去 @types 找,而是有一定的优先级, 这个过程类似原型链或者作用域链。 比如如下代码: 1const user: User = { name: \"lucifer\" }; Typescript 则会先在本模块查找 User 的定义。 如果找到,则直接返回。 如果找不到, 则会到全局作用域找,而这个全局默认就是指的就是 @types 下的所有类型定义。(注意目录页是可以配的) 也就是说 @types 下的定义都是全局的。当然你可以导入 @types 下导出的定义,使得它们的作用域变成你的模块内部。 typeRoots 与 types前面说了 TypeScript 会默认引入node_modules下的所有@types声明,但是开发者也可以通过修改tsconfig.json的配置来修改默认的行为. tsconfig.json 中有两个配置和类型引入有关。 typeRoots: 用来指定默认的类型声明文件查找路径,默认为node_modules/@types, 指定typeRoots后,TypeScript 编译器会从指定的路径去引入声明文件,而不是node_modules/@types, 比如以下配置会从typings路径下去搜索声明 12345{ \"compilerOptions\": { \"typeRoots\": [\"./typings\"] }} types: TypeScript 编译器会默认引入typeRoot下所有的声明文件,但是有时候我们并_不希望全局引入所有定义_,而是仅引入部分模块。这种情景下可以通过types指定模块名只引入我们想要的模块,比如以下只会引入 jquery 的声明文件 12345{ \"compilerOptions\": { \"types\": [\"jquery\"] }} 总结 typeRoots 是 tsconfig 中 compilerOptions 的一个配置项,typeRoots 下面的包会被 ts 编译器自动包含进来,typeRoots 默认指向 node_modules/@types。 @types 是 npm 的 scope 命名空间,和@babel 类似,@types 下的所有包会默认被引入,你可以通过修改 compilerOptions 来修改默认策略。 types 和 typeRoots 一样也是 compilerOptions 的配置,指定 types 后,typeRoots 下只有被指定的包才会被引入。 参考 GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions. @types | 深入理解 TypeScript tsconfig.json · TypeScript 中文网 · TypeScript——JavaScript 的超集 理解 Typescript 配置文件 - 个人文章 - SegmentFault 思否 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"值得关注的技术类大会","slug":"tech-conf","date":"2020-08-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.645Z","comments":true,"path":"2020/08/20/tech-conf/","link":"","permalink":"https://lucifer.ren/blog/2020/08/20/tech-conf/","excerpt":"作为一个技术人,怎么能不参加和关注几场技术大会呢?让我们来看下那些你不能错过的技术大会吧。","text":"作为一个技术人,怎么能不参加和关注几场技术大会呢?让我们来看下那些你不能错过的技术大会吧。 JSConf地址 React Conf地址 Google IO地址 D2地址 QCon地址 更多技术大会 https://juejin.im/events https://segmentfault.com/events https://www.huodongxing.com https://www.bagevent.com https://www.hdb.com https://www.meetup.com … 欢迎大家补充~","categories":[{"name":"技术大会","slug":"技术大会","permalink":"https://lucifer.ren/blog/categories/技术大会/"},{"name":"JSConf","slug":"技术大会/JSConf","permalink":"https://lucifer.ren/blog/categories/技术大会/JSConf/"},{"name":"React Conf","slug":"技术大会/React-Conf","permalink":"https://lucifer.ren/blog/categories/技术大会/React-Conf/"},{"name":"Google IO","slug":"技术大会/Google-IO","permalink":"https://lucifer.ren/blog/categories/技术大会/Google-IO/"},{"name":"D2","slug":"技术大会/D2","permalink":"https://lucifer.ren/blog/categories/技术大会/D2/"},{"name":"QCon","slug":"技术大会/QCon","permalink":"https://lucifer.ren/blog/categories/技术大会/QCon/"}],"tags":[{"name":"技术大会","slug":"技术大会","permalink":"https://lucifer.ren/blog/tags/技术大会/"},{"name":"JSConf","slug":"JSConf","permalink":"https://lucifer.ren/blog/tags/JSConf/"},{"name":"Google IO","slug":"Google-IO","permalink":"https://lucifer.ren/blog/tags/Google-IO/"},{"name":"D2","slug":"D2","permalink":"https://lucifer.ren/blog/tags/D2/"},{"name":"QCon","slug":"QCon","permalink":"https://lucifer.ren/blog/tags/QCon/"}]},{"title":"力扣刷题插件","slug":"leetcode-cheat","date":"2020-08-15T16:00:00.000Z","updated":"2023-01-07T12:34:34.356Z","comments":true,"path":"2020/08/16/leetcode-cheat/","link":"","permalink":"https://lucifer.ren/blog/2020/08/16/leetcode-cheat/","excerpt":"之前我做了一个视频, 介绍我的刷题浏览器扩展插件,视频地址:https://www.bilibili.com/video/BV1UK4y1x7zj/。 今天我在上次的基础上增加了部分公司的显示以及优化了若干体验功能。","text":"之前我做了一个视频, 介绍我的刷题浏览器扩展插件,视频地址:https://www.bilibili.com/video/BV1UK4y1x7zj/。 今天我在上次的基础上增加了部分公司的显示以及优化了若干体验功能。 这个刷题插件能做什么?题解模板(新功能)为了方便大家写出格式良好的题解,插件现在内置题解模板功能,目前模板只有一套,这套模板是我经常使用的题解模板。 安装好我们的插件(版本需要 v0.8.0 及以上)后,打开力扣中文版,会发现如下的按钮。 点击之后会自动引导你到一个新的页面, 该页面的题解语言,题目地址和题目名称信息会自动填充。 你可以快速完成时间复杂度,空间复杂度的插入,复杂度已经按照性能好坏的顺序给大家排好了,点击即可插入。 此外我们提供了若干常用的公式供你快速复制使用。除了公式,其他内容都可以在右侧的预览区域查看。 写完只会可以点击复制,将其复制到其他地方以便持久化存储。由于我们没有做持久化存储,因此页面刷新内容就会消失哦。 最后祝大家写出漂亮的题解! 数据结构可视化你可以使用 canvas 自由绘制各种数据结构,不管是写题解还是帮助理解题目都很有用。 我们提供了很多内置的模板供你快速上手。 如果你对内置的模板不满意,也可以将自己的模板保存以便下次使用。 学习路线算法怎么学?推荐按专题来。具体到某一个专题怎么学?这里提供了一个学习路线帮助你。本功能旨在将一个专题中的题目进行分类。专题本质就是对题目的一种划分,学习路线基于专题又进行了一次划分。 复杂度分析你的代码能会超时么?复杂度分析帮助你。 一键复制所有内置测试用例省去了一个个手动复制的过程,效率翻倍! 模板提供了大量的经过反复验证的模板。模板的作用是在你理解了问题的基础上,快速书写,并减少出错概率,即使出错,也容易 debug。 禅定模式 点击之后会变成这样: 底部控制台会消失,当你鼠标重新移过来或者退出禅定模式就出现了。 查看题解当你在任意非题目详情页或者我还没有收录的题目详情页的时候, 我都会列出当前我总结的所有题解。 其实我给比较经典的题目做了题解,因此这个题目数目不是很多,目前是 173 道题。另外有时候我直接写了专题,没有单独给每道题写题解,因此数量上要比 173 多很多。 当你进到一个我写了题解的题目详情页的时候, 你就可以正式使用我的插件了。 它可以: 给出这道题目的前置知识。换句话说就是我需要先掌握什么才能做出这道题。 这个题目的关键点。 哪些公司出了这道题。 我实在不会了,给我看看题解吧。好,满足你。 题解我就不看了,直接 show me code 吧。好,满足你。 根据公司,查找题目。面试突击必备 其他功能 刷题插件可以隐藏测试用例啦 有了这个可视化插件,刷题调试更轻松 力扣刷题插件近期更新盘点 我怎么才能获取呢?公众号《力扣加加》后台回复刷题插件即可。 如何离线安装 将下载的压缩包解压 在 Chrome 浏览器的地址栏输入 chrome://extensions/ 点击 load uppack 不知道中文是什么名字,反正就是上面三个按钮最左边的。 选择你解压之后的文件夹 出现下面这个就说明你安装成功了,点一下试试吧。 后期的规划是怎么样的? 后期的功能计划先对 91 活动的用户开发。关于 91 活动,大家可以关注我的公众号《力扣加加》了解详情。 更多公司信息。 持续完善题目的公司信息,这个过程需要大家的帮助,大家可以把自己面试遇到的问题发给我(附带公司和岗位信息),我可以免费提供咨询服务。 岗位信息。 这个过程同样需要大家的帮助,大家可以把自己面试遇到的问题发给我(附带公司和岗位信息),我可以免费提供咨询服务。 可视化调试。 可视化展示你的代码允许情况。 (一个双指针题目的可视化调试过程) 自动制定复习计划。 AI 智能提示。即新的提示也可以根据题目信息推测可能的解法。 等等 关注更新大家可以关注我的公众号, 如果插件有更新,会第一时间在公众号同步的哦~ 想看题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"},{"name":"刷题","slug":"刷题","permalink":"https://lucifer.ren/blog/tags/刷题/"}]},{"title":"TypeScript 类型系统","slug":"ts-type-system","date":"2020-08-14T16:00:00.000Z","updated":"2023-01-07T12:34:34.562Z","comments":true,"path":"2020/08/15/ts-type-system/","link":"","permalink":"https://lucifer.ren/blog/2020/08/15/ts-type-system/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(就是本文) types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(就是本文) types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言上一节的上帝视角看 TypeScript,我们从宏观的角度来对 Typescript 进行了一个展望。之所以把那个放到开头讲是让大家有一个大体的认识,不想让大家一叶障目。当你对整个宏观层面有了一定的了解,那么对 Typescript 的理解就不会错太多。相反,一开始就是具体的概念和 API,则很可能会让你丧失都整体的基本判断。 实际上, Typescript 一直在不断更新迭代。一方面是因为当初许下的诺言”Typescript 是 JavaScript 的超集“(JavaScript 的特性你要同步支持,同时也要处理各种新语法带来的不兼容情况)。不单是 ECMA,社区的其他发展可能也会让 Typescript 很难受。 比如 JSX 的广泛使用就给 Typescript 泛型的使用带来了影响。 TypeScript 一直处于高速的迭代。除了修复日常的 bug 之外,TypeScript 也在不断发布新的功能,比如最新 4.0.0 beta 版本的标签元祖 的功能就对智能提示这块很有用。Typescript 在社区发展方面也做的格外好,以至于它的竞争对手 Flow 被 Typescript 完美击败,这在很大程度上就是因为 Typescript 没有烂尾。如今微软在开源方向的发力是越来越显著了,我很期待微软接下来的表现,让我们拭目以待。 变量类型和值类型有的同学可能有疑问, JavaScript 不是也有类型么? 它和 Typescript 的类型是一回事么?JavaScript 不是动态语言么,那么经过 Typescript 的限定会不会丧失动态语言的动态性呢?我们继续往下看。 JavaScript 中的类型其实是值的类型。实际上不仅仅是 JavaScript,任何动态类型语言都是如此,这也是动态类型语言的本质。 Typescript 中的类型其实是变量的类型。实际上不仅仅是 Typescript,任何静态类型语言都是如此,这也是静态类型语言的本质。 记住这两句话,我们接下来解释一下这两句话。 对于 JavaScript 来说,一个变量可以是任意类型。 1234var a = 1;a = \"lucifer\";a = {};a = []; 上面的值是有类型的。比如 1 是 number 类型,”lucifer” 是字符串类型, {} 是对象类型, [] 是数组类型。而变量 a 是没有固定类型的。 对于 Typescript 来说, 一个变量只能接受和它类型兼容的类型的值。说起来比较拗口, 看个例子就明白了。 123456var a: number = 1;a = \"lucifer\"; // errorvar b: any = 1;b = \"lucifer\"; // okb = {}; // okb = []; // ok 我们不能将 string 类型的值赋值给变量 a, 因为 string 和 number 类型不兼容。而我们可以将 string,Object,Array 类型的值赋值给 b,因此 它们和 any 类型兼容。简单来说就是,一旦一个变量被标注了某种类型,那么其就只能接受这个类型以及它的子类型。 类型空间和值空间类型和值居住在不同的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。类型不能当做值来用,反之亦然。 类型空间如下代码会报类型找不到的错: 1const aa: User = { name: \"lucifer\", age: 17 }; 这个比较好理解,我们只需要使用 interface 声明一下 User 就行。 123456interface User { name: string; age: number;}const aa: User = { name: \"lucifer\", age: 17 }; 也就是说使用 interface 可以在类型空间声明一个类型,这个是 Typescript 的类型检查的基础之一。 实际上类型空间内部也会有子空间。我们可以用 namespace(老)和 module(新) 来创建新的子空间。子空间之间不能直接接触,需要依赖导入导出来交互。 值空间比如,我用 Typescript 写出如下的代码: 1const a = window.lucifer(); Typescript 会报告一个类似Property 'lucifer' does not exist on type 'Window & typeof globalThis'. 的错误。 实际上,这种错误并不是类型错误,而是找不到成员变量的错误。我们可以这样解决: 1declare var lucifer: () => any; 也就是说使用 declare 可以在值空间声明一个变量。这个是 Typescript 的变量检查的基础,不是本文要讲的主要内容,大家知道就行。 明白了 JavaScript 和 TypeScript 类型的区别和联系之后,我们就可以来进入我们本文的主题了:类型系统。 类型系统是 TypeScript 最主要的功能TypeScript 官方描述中有一句:TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications。实际上这也正是 Typescript 的主要功能,即给 JavaScript 添加静态类型检查。要想实现静态类型检查,首先就要有类型系统。总之,我们使用 Typescript 的主要目的仍然是要它的静态类型检查,帮助我们提供代码的扩展性和可维护性。因此 Typescript 需要维护一套完整的类型系统。 类型系统包括 1. 类型 和 2.对类型的使用和操作,我们先来看类型。 类型TypeScript 支持 JavaScript 中所有的类型,并且还支持一些 JavaScript 中没有的类型(毕竟是超集嘛)。没有的类型可以直接提供,也可以提供自定义能力让用户来自己创造。 那为什么要增加 JavaScript 中没有的类型呢?我举个例子,比如如下给一个变量声明类型为 Object,Array 的代码。 12const a: Object = {};const b: Array = []; 其中: 第一行代码 Typescript 允许,但是太宽泛了,我们很难得到有用的信息,推荐的做法是使用 interface 来描述,这个后面会讲到。 第二行 Typescript 则会直接报错,原因的本质也是太宽泛,我们需要使用泛型来进一步约束。 对类型的使用和操作上面说了类型和值居住在不同的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。 使用 declare 和 interface or type 就是分别在两个空间编程。比如 Typescript 的泛型就是在类型空间编程,叫做类型编程。除了泛型,还有集合运算,一些操作符比如 keyof 等。值的编程在 Typescript 中更多的体现是在类似 lib.d.ts 这样的库。当然 lib.d.ts 也会在类型空间定义各种内置类型。我们没有必要去死扣这个,只需要了解即可。 lib.d.ts 的内容主要是一些变量声明(如:window、document、math)和一些类似的接口声明(如:Window、Document、Math)。寻找代码类型(如:Math.floor)的最简单方式是使用 IDE 的 F12(跳转到定义)。 类型是如何做到静态类型检查的?TypeScript 要想解决 JavaScript 动态语言类型太宽松的问题,就需要: 提供给变量设定类型的能力 注意是变量,不是值。 提供常用类型(不必须,但是没有用户体验会极差)并可以扩展出自定义类型(必须)。 根据第一步给变量设定的类型进行类型检查,即不允许类型不兼容的赋值, 不允许使用值空间和类型空间不存在的变量和类型等。 第一个点是通过类型注解的语法来完成。即类似这样: 1const a: number = 1; Typescript 的类型注解是这样, Java 的类型注解是另一个样子,Java 类似 int a = 1。 这个只是语法差异而已,作用是一样的。 第二个问题, Typescript 提供了诸如 lib.d.ts 等类型库文件。随着 ES 的不断更新, JavaScript 类型和全局变量会逐渐变多。Typescript 也是采用这种 lib 的方式来解决的。 (TypeScript 提供的部分 lib) 第三个问题,Typescript 主要是通过 interface,type,函数类型等打通类型空间,通过 declare 等打通值空间,并结合 binder 来进行类型诊断。关于 checker ,binder 是如何运作的,可以参考我第一篇的介绍。 接下来,我们介绍类型系统的功能,即它能为我们带来什么。如果上面的内容你已经懂了,那么接下来的内容会让你感到”你也不过如此嘛“。 类型系统的主要功能 定义类型以及其上的属性和方法。 比如定义 String 类型, 以及其原型上的方法和属性。 length, includes 以及 toString 是 String 的成员变量, 生活在值空间, 值空间虽然不能直接和类型空间接触,但是类型空间可以作用在值空间,从而给其添加类型(如上图黄色部分)。 提供自定义类型的能力 12345interface User { name: string; age: number; say(name: string): string;} 这个是我自定义的类型 User,这是 Typescript 必须提供的能力。 类型兼容体系。 这个主要是用来判断类型是否正确的,上面我已经提过了,这里就不赘述了。 类型推导 有时候你不需要显式说明类型(类型注解),Typescript 也能知道他的类型,这就是类型推导结果。 1const a = 1; 如上代码,编译器会自动推导出 a 的类型 为 number。还可以有连锁推导,泛型的入参(泛型的入参是类型)推导等。类型推导还有一个特别有用的地方,就是用到类型收敛。 接下来我们详细了解下类型推导和类型收敛。 类型推导和类型收敛1let a = 1; 如上代码。 Typescript 会推导出 a 的类型为 number。 如果只会你这么写就会报错: 1a = \"1\"; 因此 string 类型的值不能赋值给 number 类型的变量。我们可以使用 Typescript 内置的 typeof 关键字来证明一下。 12let a = 1;type A = typeof a; 此时 A 的类型就是 number,证明了变量 a 的类型确实被隐式推导成了 number 类型。 有意思的是如果 a 使用 const 声明,那么 a 不会被推导为 number,而是推导为类型 1。即值只能为 1 的类型,这就是类型收敛。 12const a = 1;type A = typeof a; 通过 const ,我们将 number 类型收缩到了 值只能为 1 的类型。 实际情况的类型推导和类型收敛要远比这个复杂, 但是做的事情都是一致的。 比如这个: 1234function test(a: number, b: number) { return a + b;}type A = ReturnType<typeof test>; A 就是 number 类型。 也就是 Typescript 知道两个 number 相加结果也是一个 number。因此即使你不显示地注明返回值是 number, Typescript 也能猜到。这也是为什么 JavaScript 项目不接入 Typescript 也可以获得类型提示的原因之一。 除了 const 可以收缩类型, typeof, instanceof 都也可以。 原因很简单,就是Typescript 在这个时候可以 100% 确定你的类型了。 我来解释一下: 比如上面的 const ,由于你是用 const 声明的,因此 100% 不会变,一定永远是 1,因此类型可以收缩为 1。 再比如: 12345let a: number | string = 1;a = \"1\";if (typeof a === \"string\") { a.includes;} if 语句内 a 100% 是 string ,不能是 number。因此 if 语句内类型会被收缩为 string。instanceof 也是类似,原理一模一样。大家只要记住Typescript 如果可以 100% 确定你的类型,并且这个类型要比你定义的或者 Typescript 自动推导的范围更小,那么就会发生类型收缩就行了。 总结本文主要讲了 Typescript 的类型系统。 Typescript 和 JavaScript 的类型是很不一样的。从表面上来看, TypeScript 的类型是 JavaScript 类型的超集。但是从更深层次上来说,两者的本质是不一样的,一个是值的类型,一个是变量的类型。 Typescript 空间分为值空间和类型空间。两个空间不互通,因此值不能当成类型,类型不能当成值,并且值和类型不能做运算等。不过 TypeScript 可以将两者结合起来用,这个能力只有 TypeScript 有, 作为 TypeScript 的开发者的你没有这个能力,这个我在第一节也简单介绍了。 TypeScript 既会对变量存在与否进行检查,也会对变量类型进行兼容检查。因此 TypeScript 就需要定义一系列的类型,以及类型之间的兼容关系。默认情况,TypeScript 是没有任何类型和变量的,因此你使用 String 等都会报错。TypeScript 使用库文件来解决这个问题,最经典的就是 lib.d.ts。 TypeScript 已经做到了足够智能了,以至于你不需要写类型,它也能猜出来,这就是类型推导和类型收缩。当然 TypeScript 也有一些功能,我们觉得应该有,并且也是可以做到的功能空缺。但是我相信随着 TypeScript 的逐步迭代(截止本文发布,TypeScript 刚刚发布了 4.0.0 的 beta 版本),一定会越来越完善,用着越来越舒服的。 我们每个项目的需要是不一样的, 简单的基本类型肯定无法满足多样的项目需求,因此我们必须支持自定义类型,比如 interface, type 以及复杂一点的泛型。当然泛型很大程度上是为了减少样板代码而生的,和 interface , type 这种刚需不太一样。 有了各种各样的类型以及类型上的成员变量,以及成员变量的类型,再就加上类型的兼容关系,我们就可以做类型检查了,这就是 TypeScript 类型检查的基础。TypeScript 内部需要维护这样的一个关系,并对变量进行类型绑定,从而给开发者提供类型分析服务。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"想去力扣当前端,TypeScript 需要掌握到什么程度?","slug":"leetcode-interview-ts","date":"2020-08-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.910Z","comments":true,"path":"2020/08/13/leetcode-interview-ts/","link":"","permalink":"https://lucifer.ren/blog/2020/08/13/leetcode-interview-ts/","excerpt":"2018 年底的时候,力扣发布了岗位招聘,其中就有前端,仓库地址:https://github.com/LeetCode-OpenSource/hire 。与大多数 JD 不同, 其提供了 5 道题, 并注明了完成一个或多个面试题,获取免第一轮面试的面试机会。完成的题目越多,质量越高,在面试中的加分更多。完成后的代码可以任意形式发送给 jobs@lingkou.com。以上几个问题完成一个或多个都有可能获得面试机会,具体情况取决于提交给我们的代码。 (力扣中国前端工程师 JD) 今天我们就来看下第二题:编写复杂的 TypeScript 类型。通过这道题来看下, TypeScript 究竟要到什么水平才能进力扣当前端? 其它四道题也蛮有意思的,值得一看。","text":"2018 年底的时候,力扣发布了岗位招聘,其中就有前端,仓库地址:https://github.com/LeetCode-OpenSource/hire 。与大多数 JD 不同, 其提供了 5 道题, 并注明了完成一个或多个面试题,获取免第一轮面试的面试机会。完成的题目越多,质量越高,在面试中的加分更多。完成后的代码可以任意形式发送给 jobs@lingkou.com。以上几个问题完成一个或多个都有可能获得面试机会,具体情况取决于提交给我们的代码。 (力扣中国前端工程师 JD) 今天我们就来看下第二题:编写复杂的 TypeScript 类型。通过这道题来看下, TypeScript 究竟要到什么水平才能进力扣当前端? 其它四道题也蛮有意思的,值得一看。 问题描述假设有一个叫 EffectModule 的类 1class EffectModule {} 这个对象上的方法只可能有两种类型签名: 12345678interface Action<T> { payload?: T type: string}asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>syncMethod<T, U>(action: Action<T>): Action<U> 这个对象上还可能有一些任意的非函数属性: 1234567891011121314151617181920212223interface Action<T> { payload?: T; type: string;}class EffectModule { count = 1; message = \"hello!\"; delay(input: Promise<number>) { return input.then((i) => ({ payload: `hello ${i}!`, type: \"delay\", })); } setMessage(action: Action<Date>) { return { payload: action.payload!.getMilliseconds(), type: \"set-message\", }; }} 现在有一个叫 connect 的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有EffectModule 的同名方法,但是方法的类型签名被改变了: 12asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> 变成了asyncMethod<T, U>(input: T): Action<U> 12syncMethod<T, U>(action: Action<T>): Action<U> 变成了syncMethod<T, U>(action: T): Action<U> 例子: EffectModule 定义如下: 1234567891011121314151617181920212223interface Action<T> { payload?: T; type: string;}class EffectModule { count = 1; message = \"hello!\"; delay(input: Promise<number>) { return input.then((i) => ({ payload: `hello ${i}!`, type: \"delay\", })); } setMessage(action: Action<Date>) { return { payload: action.payload!.getMilliseconds(), type: \"set-message\", }; }} connect 之后: 123456type Connected = { delay(input: number): Action<string>; setMessage(action: Date): Action<number>;};const effectModule = new EffectModule();const connected: Connected = connect(effectModule); 要求: 在 题目链接 里面的 index.ts 文件中,有一个 type Connect = (module: EffectModule) => any,将 any 替换成题目的解答,让编译能够顺利通过,并且 index.ts 中 connected 的类型与: 1234type Connected = { delay(input: number): Action<string>; setMessage(action: Date): Action<number>;}; 完全匹配。 以上是官方题目描述,下面我的补充 上文提到的index.ts 比 题目描述多了两个语句,它们分别是: (题目额外信息) 思路首先来解读下题目。 题目要求我们补充类型 Connect 的定义, 也就是将 any 替换为不报错的其他代码。 回顾一下题目信息: 有一个叫 connect 的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有EffectModule 的同名方法,但是方法的类型签名被改变了 这个对象上还可能有一些任意的非函数属性 这个对象(EffectModule 实例)上的方法只可能有两种类型签名 根据以上信息,我们能够得到:我们只需要将作为参数传递进来的 EffectModule 实例上的函数类型签名修改一下,非函数属性去掉即可。所以,我们有两件问题要解决: 如何将非函数属性去掉 如何转换函数类型签名 如何将非函数属性去掉我们需要定义一个泛型,功能是接受一个对象,如果对象的 value 是 函数,则保留,否则去掉即可。不懂泛型的朋友可以先看下我之前写的文章: 你不知道的 TypeScript 泛型(万字长文,建议收藏) 这让我想起了官方提供的 Omit 泛型 Omit<T,K>。举个例子: 12345678910111213interface Todo { title: string; description: string; completed: boolean;}type TodoPreview = Omit<Todo, \"description\">;// description 属性没了const todo: TodoPreview = { title: \"Clean room\", completed: false,}; 官方的 Omit 实现: 12345type Pick<T, K extends keyof T> = { [P in K]: T[P];};type Exclude<T, U> = T extends U ? never : T;type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; 实际上我们要做的就是 Omit 的变种,不是 Omit 某些 key,而是 Omit 值为非函数的 key。 由于 Omit 非函数实际就就是 Pick 函数,并且无需显式指定 key,因此我们的泛型只接受一个参数即可。 于是模仿官方的 Pick 写出了如下代码: 1234567// 获取值为函数的 key,形如: 'funcKeyA' | 'funcKeyB'type PickFuncKeys<T> = { [K in keyof T]: T[K] extends Function ? K : never;}[keyof T];// 获取值为函数的 key value 对,形如: { 'funcKeyA': ..., 'funKeyB': ...}type PickFunc<T> = Pick<T, PickFuncKeys<T>>; 使用效果: 123456789101112131415interface Todo { title: string; description: string; addTodo(): string;}type AddTodo = PickFunc<Todo>;const todo: AddTodo = { addTodo() { return \"关注脑洞前端~\"; },};type ADDTodoKey = PickFuncKeys<Todo>; // 'addTodo' 可以看出,PickFunc 只提取了函数属性,忽略了非函数属性。 如何转换函数类型签名我们再来回顾一下题目要求: 也就是我们需要知道怎么才能提取 Promise 和 Action 泛型中的值。 实际上这两个几乎一样,会了一个,另外一个也就会了。我们先来看下 Promise。 从: 1(arg: Promise<T>) => Promise<U> 变为: 1(arg: T) => U; 如果想要完成这个需求,需要借助infer。只需要在类型前加一个关键字前缀 infer,TS 会将推导出的类型自动填充进去。 infer 最早出现在此 官方 PR 中,表示在 extends 条件语句中待推断的类型变量。 简单示例如下: 1type ParamType<T> = T extends (param: infer P) => any ? P : T; 在这个条件语句 T extends (param: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。 整句表示为:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T。 一个更具体的例子: 123456789interface User { name: string; age: number;}type Func = (user: User) => void;type Param = ParamType<Func>; // Param = Usertype AA = ParamType<string>; // string 这些知识已经够我们用了。 更多用法可以参考 深入理解 TypeScript - infer 。 根据上面的知识,不难写出如下代码: 1234567type ExtractPromise<P> = { [K in PickFuncKeys<P>]: P[K] extends ( arg: Promise<infer T> ) => Promise<infer U> ? (arg: T) => U : never;}; 提取 Action 的 代码也是类似: 1234567type ExtractAction<P> = { [K in keyof PickFunc<P>]: P[K] extends ( arg: Action<infer T> ) => Action<infer U> ? (arg: T) => Action<U> : never;}; 至此我们已经解决了全部两个问题,完整代码见下方代码区。 关键点 泛型 extends 做类型约束 infer 做类型提取 内置基本范型的使用和实现 代码我们将这几个点串起来,不难写出如下最终代码: 123456type ExtractContainer<P> = { [K in PickFuncKeys<P>]: P[K] extends (arg: Promise<infer T>) => Promise<infer U> ? (arg: T) => U : P[K] extends (arg: Action<infer T>) => Action<infer U> ? (arg: T) => Action<U> : nevertype Connect = (module: EffectModule) => ExtractContainer<EffectModule> 完整代码在我的 Gist 上。 总结我们先对问题进行定义,然后分解问题为:1. 如何将非函数属性去掉, 2. 如何转换函数类型签名。最后从分解的问题,以及基础泛型工具入手,联系到可能用到的语法。 这个题目不算难,最多只是中等。但是你可能也看出来了,其不仅仅是考一个语法和 API 而已,而是考综合实力。这点在其他四道题体现地尤为明显。这种考察方式能真正考察一个人的综合实力,背题是背不来的。我个人在面试别人的时候也非常喜欢问这种问题。 只有掌握基础 + 解决问题的思维方法,面对复杂问题才能从容不迫,手到擒来。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/categories/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"Webkit 内核初探","slug":"webkit-intro","date":"2020-08-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.712Z","comments":true,"path":"2020/08/11/webkit-intro/","link":"","permalink":"https://lucifer.ren/blog/2020/08/11/webkit-intro/","excerpt":"作者: 阿吉校对&整理: lucifer 当下浏览器内核主要有 Webkit、Blink 等。本文分析注意是自 2001 年 Webkit 从 KHTML 分离出去并开源后,各大浏览器厂商魔改 Webkit 的时期,这些魔改的内核最终以 Chromium 受众最多而脱颖而出。本文就以 Chromium 浏览器架构为基础,逐层探入进行剖析。","text":"作者: 阿吉校对&整理: lucifer 当下浏览器内核主要有 Webkit、Blink 等。本文分析注意是自 2001 年 Webkit 从 KHTML 分离出去并开源后,各大浏览器厂商魔改 Webkit 的时期,这些魔改的内核最终以 Chromium 受众最多而脱颖而出。本文就以 Chromium 浏览器架构为基础,逐层探入进行剖析。 引子这里以一个面试中最常见的题目从 URL 输入到浏览器渲染页面发生了什么?开始。 这个很常见的题目,涉及的知识非常广泛。大家可先从浏览器监听用户输入开始,浏览器解析 url 的部分,分析出应用层协议 是 HTTPS 还是 HTTP 来决定是否经过会话层 TLS 套接字,然后到 DNS 解析获取 IP,建立 TCP 套接字池 以及 TCP 三次握手,数据封装切片的过程,浏览器发送请求获取对应数据,如何解析 HTML,四次挥手等等等等。 这个回答理论上可以非常详细,远比我提到的多得多。 本文试图从浏览器获取资源开始探究 Webkit。如浏览器如何获取资源,获取资源时 Webkit 调用了哪些资源加载器(不同的资源使用不同的加载器),Webkit 如何解析 HTML 等入手。想要从前端工程师的角度弄明白这些问题,可以先暂时抛开 C++源码,从浏览器架构出发,做到大致了解。之后学有余力的同学再去深入研究各个底层细节。 本文的路线循序渐进,从 Chromium 浏览器架构出发,到 Webkit 资源下载时对应的浏览器获取对应资源如 HTML、CSS 等,再到 HTML 的解析,再到 JS 阻塞 DOM 解析而产生的 Webkit 优化 引出浏览器多线程架构,继而出于安全性和稳定性的考虑引出浏览器多进程架构。 一. Chromium 浏览器架构 (Chromium 浏览器架构) 我们通常说的浏览器内核,指的是渲染引擎。 WebCore 基本是共享的,只是在不同浏览器中使用 Webkit 的实现方式不同。它包含解析 HTML 生成 DOM、解析 CSS、渲染布局、资源加载器等等,用于加载和渲染网页。 JS 解析可以使用 JSCore 或 V8 等 JS 引擎。我们熟悉的谷歌浏览器就是使用 V8。比如比较常见的有内置属性 [[scope]] 就仅在 V8 内部使用,用于对象根据其向上索引自身不存在的属性。而对外暴露的 API,如 __proto__ 也可用于更改原型链。实际上 __proto__ 并不是 ES 标准提供的,它是浏览器提供的(浏览器可以不提供,因此如果有浏览器不提供的话这也并不是 b ug)。 Webkit Ports 是不共享的部分。它包含视频、音频、图片解码、硬件加速、网络栈等等,常用于移植。 同时,浏览器是多进程多线程架构,稍后也会细入。 在解析 HTML 文档之前,需要先获取资源,那么资源的获取在 Webkit 中应该如何进行呢? 二.Webkit 资源加载HTTP 是超文本传输协议,超文本的含义即包含了文本、图片、视频、音频等等。其对应的不同文件格式,在 Webkit 中 需要调用不同的资源加载器,即 特定资源加载器。 而浏览器有四级缓存,Disk Cache 是我们最常说的通过 HTTP Header 去控制的,比如强缓存、协商缓存。同时也有浏览器自带的启发式缓存。而 Webkit 对应使用的加载器是资源缓存机制的资源加载器 CachedResoureLoader 类。 如果每个资源加载器都实现自己的加载方法,则浪费内存空间,同时违背了单一职责的原则,因此可以抽象出一个共享类,即通用资源加载器 ResoureLoader 类。 Webkit 资源加载是使用了三类加载器:特定资源加载器,资源缓存机制的资源加载器 CachedResoureLoader 和 通用资源加载器 ResoureLoader。 既然说到了缓存,那不妨多谈一点。 资源既然缓存了,那是如何命中的呢?答案是根据资源唯一性的特征 URL。资源存储是有一定有效期的,而这个有效期在 Webkit 中采用的就是 LRU 算法。那什么时候更新缓存呢?答案是不同的缓存类型对应不同的缓存策略。我们知道缓存多数是利用 HTTP 协议减少网络负载的,即强缓存、协商缓存。但是如果关闭缓存了呢? 比如 HTTP/1.0 Pragma:no-cache 和 HTTP/1.1 Cache-Control: no-cache。此时,对于 Webkit 来说,它会清空全局唯一的对象 MemoryCache 中的所有资源。 资源加载器内容先到这里。浏览器架构是多进程多线程的,其实多线程可以直接体现在资源加载的过程中,在 JS 阻塞 DOM 解析中发挥作用,下面我们详细讲解一下。 三.浏览器架构浏览器是多进程多线程架构。 对于浏览器来讲,从网络获取资源是非常耗时的。从资源是否阻塞渲染的角度,对浏览器而言资源仅分为两类:阻塞渲染如 JS 和 不阻塞渲染如图片。 我们都知道 JS 阻塞 DOM 解析,反之亦然。然而对于阻塞,Webkit 不会傻傻等着浪费时间,它在内部做了优化:启动另一个线程,去遍历后续的 HTML 文档,收集需要的资源 URL,并发下载资源。最常见的比如<script async>和<script defer>,其 JS 资源下载和 DOM 解析是并行的,JS 下载并不会阻塞 DOM 解析。这就是浏览器的多线程架构。 总结一下,多线程的好处就是,高响应度,UI 线程不会被耗时操作阻塞而完全阻塞浏览器进程。 关于多线程,有 GUI 渲染线程,负责解析 HTML、CSS、渲染和布局等等,调用 WebCore 的功能。JS 引擎线程,负责解析 JS 脚本,调用 JSCore 或 V8。我们都知道 JS 阻塞 DOM 解析,这是因为 Webkit 设计上 GUI 渲染线程和 JS 引擎线程的执行是互斥的。如果二者不互斥,假设 JS 引擎线程清空了 DOM 树,在 JS 引擎线程清空的过程中 GUI 渲染线程仍继续渲染页面,这就造成了资源的浪费。更严重的,还可能发生各种多线程问题,比如脏数据等。 另外我们常说的 JS 操作 DOM 消耗性能,其实有一部分指的就是 JS 引擎线程和 GUI 渲染线程之间的通信,线程之间比较消耗性能。 除此之外还有别的线程,比如事件触发线程,负责当一个事件被触发时将其添加到待处理队列的队尾。 值得注意的是,多启动的线程,仅仅是收集后续资源的 URL,线程并不会去下载资源。该线程会把下载的资源 URL 送给 Browser 进程,Browser 进程调用网络栈去下载对应的资源,返回资源交由 Renderer 进程进行渲染,Renderer 进程将最终的渲染结果返回 Browser 进程,由 Browser 进程进行最终呈现。这就是浏览器的多进程架构。 多进程加载资源的过程是如何的呢?我们上面说到的 HTML 文档在浏览器的渲染,是交由 Renderer 进程的。Renderer 进程在解析 HTML 的过程中,已搜集到所有的资源 URL,如 link CSS、Img src 等等。但出于安全性和效率的角度考虑,Renderer 进程并不能直接下载资源,它需要通过进程间通信将 URL 交由 Browser 进程,Browser 进程有权限调用 URLRequest 类从网络或本地获取资源。 近年来,对于有的浏览器,网络栈由 Browser 进程中的一个模块,变成一个单独的进程。 同时,多进程的好处远远不止安全这一项,即沙箱模型。还有单个网页或者第三方插件的崩溃,并不会影响到浏览器的稳定性。资源加载完成,对于 Webkit 而言,它需要调用 WebCore 对资源进行解析。那么我们先看下 HTML 的解析。之后我们再谈一下,对于浏览器来说,它拥有哪些进程呢? 四.HTML 解析对于 Webkit 而言,将解析半结构化的 HTML 生成 DOM,但是对于 CSS 样式表的解析,严格意义 CSSOM 并不是树,而是一个映射表集合。我们可以通过 document.styleSheets 来获取样式表的有序集合来操作 CSSOM。对于 CSS,Webkit 也有对应的优化策略—-ComputedStyle。ComputedStyle 就是如果多个元素的样式可以不经过计算就确认相等,那么就仅会进行一次样式计算,其余元素仅共享该 ComputedStyle。 共享 ComputedStyle 原则: (1) TagName 和 Class 属性必须一样。 (2)不能有 Style。 (3)不能有 sibling selector。 (4)mappedAttribute 必须相等。 对于 DOM 和 CSSOM,大家说的合成的 render 树在 Webkit 而言是不存在的,在 Webkit 内部生成的是 RenderObject,在它的节点在创建的同时,会根据层次结构创建 RenderLayer 树,同时构建一个虚拟的绘图上下文,生成可视化图像。这四个内部表示结构会一直存在,直到网页被销毁。 RenderLayer 在浏览器控制台中 Layers 功能卡中可以看到当前网页的图层分层。图层涉及到显式和隐式,如 scale()、z-index 等。层的优点之一是只重绘当前层而不影响其他层,这也是 Webkit 做的优化之一。同时 V8 引擎也做了一些优化,比如说隐藏类、优化回退、内联缓存等等。 五.浏览器进程浏览器进程包括 Browser 进程、Renderer 进程、GPU 进程、NPAPI 插件进程、Pepper 进程等等。下面让我们详细看看各大进程。 Browser 进程:浏览器的主进程,有且仅有一个,它是进程祖先。负责页面的显示和管理、其他进程的管理。 Renderer 进程:网页的渲染进程,可有多个,和网页数量不一定是一一对应关系。它负责网页的渲染,Webkit 的渲染工作就是在这里完成的。 GPU 进程:最多一个。仅当 GPU 硬件加速被打开时创建。它负责 3D 绘制。 NPAPI 进程:为 NPAPI 类型的插件而创建。其创建的基本原则是每种类型的插件都只会被创建一次,仅当使用时被创建,可被共享。 Pepper 进程:同 NPAPI 进程,不同的是 它为 Pepper 插件而创建的进程。 注意:如果页面有 iframe,它会形成影子节点,会运行在单独的进程中。 我们仅仅在围绕 Chromium 浏览器来说上述进程,因为在移动端,毕竟手机厂商很多,各大厂商对浏览器进程的支持也不一样。这其实也是我们最常见的 H5 兼容性问题,比如 IOS margin-bottom 失效等等。再比如 H5 使用 video 标签做直播,也在不同手机之间会存在问题。有的手机直播页面跳出主进程再回来,就会黑屏。 以 Chromium 的 Android 版为例子,不存在 GPU 进程,GPU 进程变成了 Browser 进程的线程。同时,Renderer 进程演变为服务进程,同时被限制了最大数量。 为了方便起见,我们以 PC 端谷歌浏览器为例子,打开任务管理器,查看当前浏览器中打开的网页及其进程。 当前我打开了 14 个网页,不太好容易观察,但可以从下图中看到,只有一个 Browser 进程,即第 1 行。但是打开的网页对应的 Renderer 进程,并不一定是一个网页对应一个 Renderer 进程,这跟 Renderer 进程配置有关系。比如你看第 6、7 行是每个标签页创建独立 Renderer 进程,但是蓝色光标所在的第 8、9、10 行是共用一个 Renderer 进程,这属于为每个页面创建一个 Renderer 进程。因为第 9、10 行打开的页面是从第 8 行点击链接打开的。第 2 行的 GPU 进程也清晰可见,以及第 3、4、5 行的插件进程。 关于,Renderer 进程和打开的网页并不一定是一一对应的关系,下面我们详细说一下 Renderer 进程。当前只有四种多进程策略: Process-per-site-instance: 为每个页面单独创建一个进程,从某个网站打开的一系列网站都属于同一个进程。这是浏览器的默认项。上图中的蓝色光标就是这种情况。 Process-per-site:同一个域的页面共享一个进程。 Process-per-tab:为每个标签页创建一个独立的进程。比如上图第 6、7 行。 Single process:所有的渲染工作作为多个线程都在 Browser 进程中进行。这个基本不会用到的。 Single process 突然让我联想到零几年的时候,那会 IE 应该还是单进程浏览器。单进程就是指所有的功能模块全部运行在一个进程,就类似于 Single process。那会玩 4399 如果一个网页卡死了,没响应,点关闭等一会,整个浏览器就崩溃了,得重新打开。所以多进程架构是有利于浏览器的稳定性的。虽然当下浏览器架构为多进程架构,但如果 Renderer 进程配置为 Process-per-site-instance,也可能会出现由于单个页面卡死而导致所有页面崩溃的情况。 故浏览器多进程架构综上所述,好处有三: (1)单个网页的崩溃不会影响这个浏览器的稳定性。 (2)第三方插件的崩溃不会影响浏览器的稳定性。 (3)沙箱模型提供了安全保障。 总结Webkit 使用三类资源加载器去下载对应的资源,并存入缓存池中,对于 HTML 文档的解析,在阻塞时调用另一个线程去收集后续资源的 URL,将其发送给 Browser 进程,Browser 进程调用网络栈去下载对应的本地或网络资源,返回给 Renderer 进程进行渲染,Renderer 进程将最终渲染结果(一系列的合成帧)发送给 Browser 进程,Browser 进程将这些合成帧发送给 GPU 从而显示在屏幕上。(文中有部分不严谨的地方,已由 lucifer 指出修改)","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/categories/浏览器/"},{"name":"浏览器","slug":"前端/浏览器","permalink":"https://lucifer.ren/blog/categories/前端/浏览器/"},{"name":"webkit","slug":"前端/webkit","permalink":"https://lucifer.ren/blog/categories/前端/webkit/"},{"name":"webkit","slug":"浏览器/webkit","permalink":"https://lucifer.ren/blog/categories/浏览器/webkit/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/tags/浏览器/"},{"name":"webkit","slug":"webkit","permalink":"https://lucifer.ren/blog/tags/webkit/"}]},{"title":"上帝视角看 TypeScript","slug":"ts-internal","date":"2020-08-03T16:00:00.000Z","updated":"2023-01-07T12:34:34.557Z","comments":true,"path":"2020/08/04/ts-internal/","link":"","permalink":"https://lucifer.ren/blog/2020/08/04/ts-internal/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 而本篇文章则是这个系列的开篇。 系列安排: 上帝视角看 TypeScript(就是本文) TypeScript 类型系统 什么是 types?什么是 @types? 类型推导, 类型断言与类型保护 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 练习题 TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 而本篇文章则是这个系列的开篇。 系列安排: 上帝视角看 TypeScript(就是本文) TypeScript 类型系统 什么是 types?什么是 @types? 类型推导, 类型断言与类型保护 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 练习题 TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 从输入输出上来看如果我们把 Typescript 编译器看成一个黑盒的话。其输入则是使用 TypeScript 语法书写的文本或者文本集合。 (文本) 如果几个文本有引用关系,比如 a.ts 依赖 foo.ts 和 bar.ts,其就是一个文本集合。 (文本集合) 输出是编译之后的 JS 文件 和 .d.ts 的声明文件。 其中 JS 是将来需要运行的文件,而 .d.ts 声明文件则是 ts 文件中的类型声明,这个类型声明就是你在 ts 文件中声明的类型和 TypeScript 类型推导系统推导的类型。当然你也可以自己写 .d.ts 声明文件。 从功能上来看从宏观的视角来看,TypeScript 的功能就是: 提供了丰富的类型系统。 最简单的就是 变量名:类型 = 值 1const a: Number = 1; 除了这些基本类型,还提供了函数类型,复合类型等。 提供了类型操作 API。TypeScript 不但提供内置类型,用户也可以利用集合操作和泛型对类型操作从而生成新的类型。 对每一种类型的属性和方法都进行了定义。 比如 String 类型有 toString 方法,但是没有 toFixed 方法,这就是 lib.d.ts 定义的。这样我在 String 类型的变量上使用 toFixed 方法就会报错,达到了“类型检查”的作用。 小提示:lib.d.ts 的内容主要是一些变量声明(如:window、document、math)和一些类似的接口声明(如:Window、Document、Math)。 你可以通过 —noLib 来关闭这一功能 提供了模块系统(module,namespace)。 提供了更加方面的 API,比如 class(这在 ES6 class 出来之前尤其好用),装饰器等。 。。。 TypeScript 编译器是如何工作的?上面已经讨论了 TypeScript 编译器的输入和输出。那黑盒内部是怎么工作呢?这里我简单介绍一下: TypeScript 文本首先会被解析为 token 流。这个过程比较简单,就是单纯地按照分隔符去分割文本即可。 接着 token 流会被转换为 AST,也就是抽象语法树。 binder 则根据 AST 信息生成 Symbol(TypeScript 中的一个数据结构)。拿上面的图来说,就是 number 节点。 当我们需要类型检查的时候, checker 会根据前面生成的 AST 和 symbols 生成类型检查结果。 当我们需要生成 JS 文件的时候,emitter 同样会根据前面生成的 AST 和 symbols 生成 JS 文件。 完整图: 总结总的来说,TypeScript 就是一门语言,和 Java,Python,C++ 等类似。只不过这门语言主要目标就是为了弥补 JavaScript 弱类型带来的问题的。因此设计语言的出发点就是: 静态类型系统 可以编译成 JavaScript 因此 TypeScript 是一门最终编译为 JavaScript 的语言(当然还有类型文件)。既然是一门语言,就涉及词法分析,语法分析等流程。由于相对 JavaScript 增加了很多功能, 其中最主要的就是类型系统。因此 TypeScript 的分析工作要比 JavaScript 更加复杂, 集中体现在 binder 和 checker 部分。 由于提供了静态类型, 因此就需要提供一些内置类型给我们用,比如 number,string,Array 等。但是这并不能满足我们的所有需求,我们需要自定义类型,因此有了 type,有了 interface 等。后来我们又发现自定义的类型重复代码太多, 要是类型也可以通过编程生成新的类型就好了,于是有了集合运算和泛型。 代码都放到一起不方便维护,要是可以放到不同文件,需要用的时候组装起来就好了,于是有了模块化。我用了别人的用 TypeScript 开发的库,如果也能有类型校验就好了,于是有了 types。 。。。 其实这些都是有因果关系的,如果你可以牢牢地掌握这些因果关系,那么学起来还不是易如反掌? 相关阅读 TypeScript 编译原理 Bring your own TypeScript with more internal definitions Compiler Internals TypeScript 编译器是用 TypeScript 写的,那是先有编译器还是 TS? 点关注,不迷路大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"},{"name":"泛型","slug":"前端/TypeScript/泛型","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/泛型/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"},{"name":"泛型","slug":"泛型","permalink":"https://lucifer.ren/blog/tags/泛型/"}]},{"title":"《我是你的妈妈呀》 - 第一期","slug":"mother-01","date":"2020-08-02T16:00:00.000Z","updated":"2023-01-07T12:34:34.364Z","comments":true,"path":"2020/08/03/mother-01/","link":"","permalink":"https://lucifer.ren/blog/2020/08/03/mother-01/","excerpt":"记得我初中的时候,学校发的一个小册子的名字就是母题啥的。 大概意思是市面上的题(尤其是中考题)都是这些母题生的,都是它们的儿子。 熟悉我的朋友应该知道,我有一个风格:”喜欢用通俗易懂的语言以及图片,还原解题过程“。包括我是如何抽象的,如何与其他题目建立联系的等。比如: 一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~ 超级详细记忆化递归,图解,带你一次攻克三道 Hard 套路题(44. 通配符匹配) 穿上衣服我就不认识你了?来聊聊最长上升子序列 扒一扒这种题的外套(343. 整数拆分) 如果把这个思考过程称之为自顶向下的话,那么实际上能写出来取决于你: 是否有良好的抽象能力 是否有足够的基础知识 是否能与学过的基础知识建立联系 如果反着呢? 我先把所有抽象之后的纯粹的东西掌握,也就是母题。那么遇到新的题,我就往上套呗?这就是我在《LeetCode 题解仓库》中所说的只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 这种思路就是自底向上。(有点像动态规划?) 市面上的题那么多,但是题目类型就是那几种。甚至出题人出题的时候都是根据以前的题目变个条件,变个说法从而搞出一个“新”的题。 这个专题的目标就是从反的方向来,我们先学习和记忆底层的被抽象过的经典的题目。遇到新的题目,就往这些母题上套即可。 那让我们来自底向上看下第一期的这八道母题吧~","text":"记得我初中的时候,学校发的一个小册子的名字就是母题啥的。 大概意思是市面上的题(尤其是中考题)都是这些母题生的,都是它们的儿子。 熟悉我的朋友应该知道,我有一个风格:”喜欢用通俗易懂的语言以及图片,还原解题过程“。包括我是如何抽象的,如何与其他题目建立联系的等。比如: 一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~ 超级详细记忆化递归,图解,带你一次攻克三道 Hard 套路题(44. 通配符匹配) 穿上衣服我就不认识你了?来聊聊最长上升子序列 扒一扒这种题的外套(343. 整数拆分) 如果把这个思考过程称之为自顶向下的话,那么实际上能写出来取决于你: 是否有良好的抽象能力 是否有足够的基础知识 是否能与学过的基础知识建立联系 如果反着呢? 我先把所有抽象之后的纯粹的东西掌握,也就是母题。那么遇到新的题,我就往上套呗?这就是我在《LeetCode 题解仓库》中所说的只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 这种思路就是自底向上。(有点像动态规划?) 市面上的题那么多,但是题目类型就是那几种。甚至出题人出题的时候都是根据以前的题目变个条件,变个说法从而搞出一个“新”的题。 这个专题的目标就是从反的方向来,我们先学习和记忆底层的被抽象过的经典的题目。遇到新的题目,就往这些母题上套即可。 那让我们来自底向上看下第一期的这八道母题吧~ 母题 1题目描述给你两个有序的非空数组 nums1 和 nums2,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路 初始化 ans 为无限大 使用两个指针,一个指针指向数组 1,一个指针指向数组 2 比较两个指针指向的数字的大小,并更新较小的那个的指针,使其向后移动一位。更新的过程顺便计算 ans 最后返回 ans 代码12345678910def f(nums1, nums2): i = j = 0 ans = float('inf') while i < len(nums1) and j < len(nums2): ans = min(ans, abs(nums1[i] - nums2[j])) if nums1[i] < nums2[j]: i += 1 else: j += 1 return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 母题 2题目描述给你两个非空数组 nums1 和 nums2,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路数组没有说明是有序的,可以选择暴力。两两计算绝对值,返回最小的即可。 代码: 123456def f(nums1, nums2): ans = float('inf') for num1 in nums1: for num2 in nums2: ans = min(ans, abs(num1 - num2)) return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(1)$ 由于暴力的时间复杂度是 $O(N^2)$,因此其实也可以先排序将问题转换为母题 1,然后用母题 1 的解法求解。 复杂度分析 时间复杂度:$O(NlogN)$ 空间复杂度:$O(1)$ 母题 3题目描述给你 k 个有序的非空数组,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路继续使用母题 1 的思路,使用 k 个 指针即可。 复杂度分析 时间复杂度:$O(klogM)$,其中 M 为 k 个非空数组的长度的最小值。 空间复杂度:$O(1)$ 我们也可以使用堆来处理,代码更简单,逻辑更清晰。这里我们使用小顶堆,作用就是选出最小值。 代码12345678910111213141516def f(matrix): ans = float('inf') max_value = max(nums[0] for nums in matrix) heap = [(nums[0], i, 0) for i, nums in enumerate(nums)] heapq.heapify(heap) while True: min_value, row, idx = heapq.heappop(heap) if max_value - min_value < ans: ans = max_value - min_value if idx == len(matrix[row]) - 1: break max_value = max(max_value, matrix[row][idx + 1]) heapq.heappush(heap, (matrix[row][idx + 1], row, idx + 1)) return ans 复杂度分析 建堆的时间和空间复杂度为 $O(k)$。 while 循环会执行 M 次 ,其中 M 为 k 个非空数组的长度的最小值。heappop 和 heappush 的时间复杂度都是 logk。因此 while 循环总的时间复杂度为 $O(Mlogk)$。 时间复杂度:$O(max(Mlogk, k))$,其中 M 为 k 个非空数组的长度的最小值。 空间复杂度:$O(k)$ 母题 4题目描述给你 k 个非空数组,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路先排序,然后转换为母题 3 母题 5题目描述给你两个有序的非空数组 nums1 和 nums2,让你将两个数组合并,使得新的数组有序。 LeetCode 地址: https://leetcode-cn.com/problems/merge-sorted-array/ 思路和母题 1 类似。 代码123456789101112131415def f(nums1, nums2): i = j = 0 ans = [] while i < len(nums1) and j < len(nums2): if nums1[i] < nums2[j]: ans.append(nums1[i]) i += 1 else: ans.append(nums2[j]) j += 1 if nums1: ans += nums2[j:] else: ans += nums1[i:] return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 母题 6题目描述给你 k 个有序的非空数组 nums1 和 nums2,让你将 k 个数组合并,使得新的数组有序。 思路和母题 5 类似。 只不过不是两个,而是多个。我们继续套用堆的思路。 代码123456789101112131415import heapqdef f(matrix): ans = [] heap = [] for row in matrix: heap += row heapq.heapify(heap) while heap: cur = heapq.heappop(heap) ans.append(cur) return ans 复杂度分析 建堆的时间和空间复杂度为 $O(N)$。 heappop 的时间复杂度为 $O(logN)$。 时间复杂度:$O(NlogN)$,其中 N 是矩阵中的数字总数。 空间复杂度:$O(N)$,其中 N 是矩阵中的数字总数。 母题 7题目描述给你两个有序的链表 root1 和 root2,让你将两个链表合并,使得新的链表有序。 LeetCode 地址:https://leetcode-cn.com/problems/merge-two-sorted-lists/ 思路和母题 5 类似。 不同的地方在于数据结构从数组变成了链表,我们只需要注意链表的操作即可。 这里我使用了迭代和递归两种方式。 大家可以把母题 5 使用递归写一下。 代码12345678910111213141516# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 if l1.val < l2.val: l1.next = self.mergeTwoLists(l1.next, l2) return l1 else: l2.next = self.mergeTwoLists(l1, l2.next) return l2 复杂度分析 时间复杂度:$O(N)$,其中 N 为两个链表中较短的那个的长度。 空间复杂度:$O(N)$,其中 N 为两个链表中较短的那个的长度。 123456789101112131415161718192021222324252627# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 ans = cur = ListNode(0) while l1 and l2: if l1.val < l2.val: cur.next = l1 cur = cur.next l1 = l1.next else: cur.next = l2 cur = cur.next l2 = l2.next if l1: cur.next = l1 else: cur.next = l2 return ans.next 复杂度分析 时间复杂度:$O(N)$,其中 N 为两个链表中较短的那个的长度。 空间复杂度:$O(1)$ 母题 8题目描述给你 k 个有序的链表,让你将 k 个链表合并,使得新的链表有序。 LeetCode 地址:https://leetcode-cn.com/problems/merge-k-sorted-lists/ 思路和母题 7 类似,我们使用递归可以轻松解决。其实本质上就是 代码1234567891011121314151617181920# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 if l1.val < l2.val: l1.next = self.mergeTwoLists(l1.next, l2) return l1 else: l2.next = self.mergeTwoLists(l1, l2.next) return l2 def mergeKLists(self, lists: List[ListNode]) -> ListNode: if not lists: return None if len(lists) == 1: return lists[0] return self.mergeTwoLists(lists[0], self.mergeKLists(lists[1:])) 复杂度分析 mergeKLists 执行了 k 次,每次都执行一次 mergeTwoLists,mergeTwoLists 的时间复杂度前面已经分析过了,为 $O(N)$,其中 N 为两个链表中较短的那个的长度。 时间复杂度:$O(k * N)$,其中 N 为两个链表中较短的那个的长度 空间复杂度:$O(max(k, N))$ 1234567891011121314151617181920# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 if l1.val < l2.val: l1.next = self.mergeTwoLists(l1.next, l2) return l1 else: l2.next = self.mergeTwoLists(l1, l2.next) return l2 def mergeKLists(self, lists: List[ListNode]) -> ListNode: if not lists: return None if len(lists) == 1: return lists[0] return self.mergeTwoLists(self.mergeKLists(lists[:len(lists) // 2]), self.mergeKLists(lists[len(lists) // 2:])) 复杂度分析 mergeKLists 执行了 logk 次,每次都执行一次 mergeTwoLists,mergeTwoLists 的时间复杂度前面已经分析过了,为 $O(N)$,其中 N 为两个链表中较短的那个的长度。 时间复杂度:$O(Nlogk)$,其中 N 为两个链表中较短的那个的长度 空间复杂度:$O(max(logk, N))$,其中 N 为两个链表中较短的那个的长度 全家福最后送大家一张全家福: 子题实际子题数量有很多,这里提供几个供大家练习。一定要练习,不能眼高手低。多看我的题解,多练习,多总结,你也可以的。 面试题 17.14. 最小 K 个数 1200. 最小绝对差 632. 最小区间 两数和,三数和,四数和。。。 k 数和 总结母题就是抽象之后的纯粹的东西。如果你掌握了母题,即使没有掌握抽象的能力,依然有可能套出来。但是随着题目做的变多,“抽象能力”也会越来越强。因为你知道这些题背后是怎么产生的。 本期给大家介绍了八道母题, 大家可以在之后的刷题过程中尝试使用母题来套模板。之后会给大家带来更多的母题。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"母题","slug":"算法/母题","permalink":"https://lucifer.ren/blog/categories/算法/母题/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"母题","slug":"母题","permalink":"https://lucifer.ren/blog/tags/母题/"}]},{"title":"平衡二叉树专题","slug":"balanced-tree","date":"2020-07-20T16:00:00.000Z","updated":"2023-01-05T12:24:49.992Z","comments":true,"path":"2020/07/21/balanced-tree/","link":"","permalink":"https://lucifer.ren/blog/2020/07/21/balanced-tree/","excerpt":"力扣关于平衡二叉树的题目还是有一些的,并且都非常经典,推荐大家练习。今天给大家精选了 4 道题,如果你彻底搞明白了这几道题,碰到其他的平衡二叉树的题目应该不至于没有思路。当你领会了我的思路之后, 建议再找几个题目练手,巩固一下学习成果。","text":"力扣关于平衡二叉树的题目还是有一些的,并且都非常经典,推荐大家练习。今天给大家精选了 4 道题,如果你彻底搞明白了这几道题,碰到其他的平衡二叉树的题目应该不至于没有思路。当你领会了我的思路之后, 建议再找几个题目练手,巩固一下学习成果。 110. 平衡二叉树(简单)最简单的莫过于判断一个树是否为平衡二叉树了,我们来看下。 题目描述1234567891011121314151617181920212223242526272829给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。示例 1:给定二叉树 [3,9,20,null,null,15,7] 3 / \\ 9 20 / \\ 15 7返回 true 。示例 2:给定二叉树 [1,2,2,3,3,null,null,4,4] 1 / \\ 2 2 / \\ 3 3 / \\ 4 4返回 false 思路由于平衡二叉树定义为就是一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1。用伪代码描述就是: 1234if abs(高度(root.left) - 高度(root.right)) <= 1 and root.left 也是平衡二叉树 and root.right 也是平衡二叉树: print('是平衡二叉树')else: print('不是平衡二叉树') 而 root.left 和 root.right 如何判断是否是二叉平衡树就和 root 是一样的了,可以看出这个问题有明显的递归性。 因此我们首先需要知道如何计算一个子树的高度。这个可以通过递归的方式轻松地计算出来。计算子树高度的 Python 代码如下: 12345def dfs(node): if not node: return 0 l = dfs(node.left) r = dfs(node.right) return max(l, r) + 1 代码代码支持: Python3 Python3 Code: 12345678910class Solution: def isBalanced(self, root: TreeNode) -> bool: def dfs(node): if not node: return 0 l = dfs(node.left) r = dfs(node.right) return max(l, r) + 1 if not root: return True if abs(dfs(root.left) - dfs(root.right)) > 1: return False return self.isBalanced(root.left) and self.isBalanced(root.right) 复杂度分析 时间复杂度:对于 isBalanced 来说,由于每个节点最多被访问一次,这部分的时间复杂度为 $O(N)$,而 dfs 函数 每次被调用的次数不超过 $log N$,因此总的时间复杂度为 $O(NlogN)$,其中 $N$ 为 树的节点总数。 空间复杂度:由于使用了递归,这里的空间复杂度的瓶颈在栈空间,因此空间复杂度为 $O(h)$,其中 $h$ 为树的高度。 108. 将有序数组转换为二叉搜索树(简单)108 和 109 基本是一样的,只不过数据结构不一样,109 变成了链表了而已。由于链表操作比数组需要考虑更多的因素,因此 109 是 中等难度。 题目描述123456789101112131415将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。示例:给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 0 / \\ -3 9 / / -10 5 思路对于这个问题或者 给定一个二叉搜索树,将其改为平衡(后面会讲) 基本思路都是一样的。 题目的要求是将有序数组转化为: 高度平衡的二叉树 二叉搜索树 由于平衡二叉树是左右两个子树的高度差的绝对值不超过 1。因此一种简单的方法是选择中点作为根节点,根节点左侧的作为左子树,右侧的作为右子树即可。原因很简单,这样分配可以保证左右子树的节点数目差不超过 1。因此高度差自然也不会超过 1 了。 上面的操作同时也满足了二叉搜索树,原因就是题目给的数组是有序的。 你也可以选择别的数作为根节点,而不是中点,这也可以看出答案是不唯一的。 代码代码支持: Python3 Python3 Code: 12345678class Solution: def sortedArrayToBST(self, nums: List[int]) -> TreeNode: if not nums: return None mid = (len(nums) - 1) // 2 root = TreeNode(nums[mid]) root.left = self.sortedArrayToBST(nums[:mid]) root.right = self.sortedArrayToBST(nums[mid + 1:]) return root 复杂度分析 时间复杂度:由于每个节点最多被访问一次,因此总的时间复杂度为 $O(N)$,其中 $N$ 为数组长度。 空间复杂度:由于使用了递归,这里的空间复杂度的瓶颈在栈空间,因此空间复杂度为 $O(h)$,其中 $h$ 为树的高度。同时由于是平衡二叉树,因此 $h$ 就是 $log N$。 109. 有序链表转换二叉搜索树(中等)题目描述123456789101112131415`给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。示例:给定的有序链表: [-10, -3, 0, 5, 9],一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树: 0 / \\ -3 9 / / -10 5 思路和 108 思路一样。 不同的是数据结构的不同,因此我们需要关注的是链表和数组的操作差异。 (数组的情况) 我们再来看下链表: (链表的情况) 找到中点,只需要使用经典的快慢指针即可。同时为了防止环的出现, 我们需要斩断指向 mid 的 next 指针,因此需要记录一下中点前的一个节点,这只需要用一个变量 pre 记录即可。 代码代码代码支持:JS,Java,Python,C++ JS Code 123456789101112131415161718var sortedListToBST = function (head) { if (!head) return null; return dfs(head, null);};function dfs(head, tail) { if (head == tail) return null; let fast = head; let slow = head; while (fast != tail && fast.next != tail) { fast = fast.next.next; slow = slow.next; } let root = new TreeNode(slow.val); root.left = dfs(head, slow); root.right = dfs(slow.next, tail); return root;} Java Code: 123456789101112131415161718class Solution { public TreeNode sortedListToBST(ListNode head) { if(head == null) return null; return dfs(head,null); } private TreeNode dfs(ListNode head, ListNode tail){ if(head == tail) return null; ListNode fast = head, slow = head; while(fast != tail && fast.next != tail){ fast = fast.next.next; slow = slow.next; } TreeNode root = new TreeNode(slow.val); root.left = dfs(head, slow); root.right = dfs(slow.next, tail); return root; }} Python Code: 123456789101112131415161718class Solution: def sortedListToBST(self, head: ListNode) -> TreeNode: if not head: return head pre, slow, fast = None, head, head while fast and fast.next: fast = fast.next.next pre = slow slow = slow.next if pre: pre.next = None node = TreeNode(slow.val) if slow == fast: return node node.left = self.sortedListToBST(head) node.right = self.sortedListToBST(slow.next) return node C++ Code: 1234567891011121314151617181920212223class Solution {public: TreeNode* sortedListToBST(ListNode* head) { if (head == nullptr) return nullptr; return sortedListToBST(head, nullptr); } TreeNode* sortedListToBST(ListNode* head, ListNode* tail) { if (head == tail) return nullptr; ListNode* slow = head; ListNode* fast = head; while (fast != tail && fast->next != tail) { slow = slow->next; fast = fast->next->next; } TreeNode* root = new TreeNode(slow->val); root->left = sortedListToBST(head, slow); root->right = sortedListToBST(slow->next, tail); return root; }}; 复杂度分析 令 n 为链表长度。 时间复杂度:递归树的深度为 $logn$,每一层的基本操作数为 $n$,因此总的时间复杂度为$O(nlogn)$ 空间复杂度:空间复杂度为$O(logn)$ 有的同学不太会分析递归的时间复杂度和空间复杂度,我们在这里给大家再次介绍一下。 首先我们尝试画出如下的递归树。由于递归树的深度为 $logn$ 因此空间复杂度就是 $logn$ * 递归函数内部的空间复杂度,由于递归函数内空间复杂度为 $O(1)$,因此总的空间复杂度为 $O(logn)$。 时间复杂度稍微困难一点点。之前西法在先导篇给大家说过:如果有递归那就是:递归树的节点数 * 递归函数内部的基础操作数。而这句话的前提是所有递归函数内部的基本操作数是一样的,这样才能直接乘。而这里递归函数的基本操作数不一样。 不过我们发现递归树内部每一层的基本操作数都是固定的, 为啥固定已经在图上给大家算出来了。因此总的空间复杂度其实可以通过递归深度 * 每一层基础操作数计算得出,也就是 $nlogn$。 类似的技巧可以用于归并排序的复杂度分析中。 另外大家也直接可以通过公式推导得出。对于这道题来说,设基本操作数 T(n),那么就有 T(n) = T(n/2) * 2 + n/2,推导出来 T(n) 大概是 nlogn。这应该高中的知识。具体推导过程如下: T(n) = T(n/2) _ 2 + n/2 = \\frac{n}{2} + 2 _ (\\frac{n}{2}) ^ 2 + 2 ^ 2 _ (\\frac{n}{2}) ^ 3 + ... = logn _ \\frac{n}{2}类似地,如果递推公式为 T(n) = T(n/2) * 2 + 1 ,那么 T(n) 大概就是 logn。 1382. 将二叉搜索树变平衡(中等)题目描述123456789给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的 。如果有多种构造方法,请你返回任意一种。 示例: 12345678910输入:root = [1,null,2,null,3,null,4,null,null]输出:[2,1,3,null,null,null,4]解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。 提示:树节点的数目在 1 到 10^4 之间。树节点的值互不相同,且在 1 到 10^5 之间。 思路由于二叉搜索树的中序遍历是一个有序数组,因此问题很容易就转化为 108. 将有序数组转换为二叉搜索树(简单)。 代码代码支持: Python3 Python3 Code: 123456789101112131415class Solution: def inorder(self, node): if not node: return [] return self.inorder(node.left) + [node.val] + self.inorder(node.right) def balanceBST(self, root: TreeNode) -> TreeNode: nums = self.inorder(root) def dfs(start, end): if start == end: return TreeNode(nums[start]) if start > end: return None mid = (start + end) // 2 root = TreeNode(nums[mid]) root.left = dfs(start, mid - 1) root.right = dfs(mid + 1, end) return root return dfs(0, len(nums) - 1) 复杂度分析 时间复杂度:由于每个节点最多被访问一次,因此总的时间复杂度为 $O(N)$,其中 $N$ 为链表长度。 空间复杂度:虽然使用了递归,但是瓶颈不在栈空间,而是开辟的长度为 $N$ 的 nums 数组,因此空间复杂度为 $O(N)$,其中 $N$ 为树的节点总数。 总结本文通过四道关于二叉平衡树的题帮助大家识别此类型题目背后的思维逻辑,我们来总结一下学到的知识。 平衡二叉树指的是:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。 如果需要让你判断一个树是否是平衡二叉树,只需要死扣定义,然后用递归即可轻松解决。 如果需要你将一个数组或者链表(逻辑上都是线性的数据结构)转化为平衡二叉树,只需要随便选一个节点,并分配一半到左子树,另一半到右子树即可。 同时,如果要求你转化为平衡二叉搜索树,则可以选择排序数组(或链表)的中点,左边的元素为左子树, 右边的元素为右子树即可。 小提示 1: 如果不需要是二叉搜索树则不需要排序,否则需要排序。 小提示 2: 你也可以不选择中点, 算法需要相应调整,感兴趣的同学可以试试。 小提示 3: 链表的操作需要特别注意环的存在。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"平衡二叉树","slug":"数据结构/平衡二叉树","permalink":"https://lucifer.ren/blog/categories/数据结构/平衡二叉树/"},{"name":"二叉搜索树","slug":"数据结构/二叉搜索树","permalink":"https://lucifer.ren/blog/categories/数据结构/二叉搜索树/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/tags/二叉树/"}]},{"title":"听说这题套个BFS模板就可以 AC?","slug":"1091.shortest-path-in-binary-matrix","date":"2020-07-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.779Z","comments":true,"path":"2020/07/04/1091.shortest-path-in-binary-matrix/","link":"","permalink":"https://lucifer.ren/blog/2020/07/04/1091.shortest-path-in-binary-matrix/","excerpt":"​","text":"​ 题目描述123456789在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1)。一条从左上角到右下角、长度为 k 的畅通路径,由满足下述条件的单元格 C_1, C_2, ..., C_k 组成:相邻单元格 C*i 和 C*{i+1} 在八个方向之一上连通(此时,C*i 和 C*{i+1} 不同且共享边或角)C_1 位于 (0, 0)(即,值为 grid[0][0])C_k 位于 (N-1, N-1)(即,值为 grid[N-1][n-1])如果 C_i 位于 (r, c),则 grid[r][c] 为空(即,grid[r][c] == 0)返回这条从左上角到右下角的最短畅通路径的长度。如果不存在这样的路径,返回 -1 。 示例 1: 输入:[[0,1],[1,0]] 输出:2 示例 2: 输入:[[0,0,0],[1,1,0],[1,1,0]] 输出:4 提示: 1 <= grid.length == grid[0].length <= 100grid[i][j] 为 0 或 1 思路这道题乍一看很像之前写过的一些“机器人”。但是不同的地方在于机器人只能“向下移动和向右移动”,因此机器人那个题目就很适合用动态规划来做。为什么呢? 因为这道题可以移动的范围是八个方向,题目给的示例不是很好,我这里给大家画了一个示例。我相信你一看就明白了。 (图 1) 如图,我们发现每一个点的状态其实依赖了周围八个方向。如果我们使用动态规划来求解的时候,我们如何遍历(枚举所有子问题)呢? 由于每一个 cell 依赖了周围八个 cell,那么我应该先更新谁呢?这个问题就会比较复杂。 具体来说, 当我需要计算 dp[1][2]的值的时候,实际上我需要先计算dp[0][2],dp[1][1],dp[2][2] … 等八个值,这样才能确定 dp[1][2]的值。而计算 dp[0][2] 又是八个值,dp[1][1]等也是同理。 这样就会很复杂。 而如果你做题比较多的话,分析到这里会发现,应该会想到 BFS。 即使你做题不多,那么根据题目给出的关键字最短畅通路径,也应该想到 BFS 才对。 这道题我直接复制了一个我直接总结的模板,稍微改了一下就 OK 了。大家也可以在平时刷题过程总结自己的解题模板,这在争分夺秒的打比赛环节是很重要的。 我复制的模板是下面这个,大家可以对比下我提交的代码看看相似度有多少。 12345678910111213141516171819202122232425class Solution: def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: m = len(matrix) if m == 0: return [] n = len(matrix[0]) ans = [[0] * n for _ in range(m)] seen = set() queue = collections.deque() steps = 0 for i in range(m): for j in range(n): if matrix[i][j] == 0: queue.append((i, j)) seen.add((i, j)) while queue: for _ in range(len(queue)): i, j = queue.popleft() if matrix[i][j] == 1: ans[i][j] = steps for x, y in [(i + 1, j), (i - 1, j),(i, j + 1),(i, j - 1)]: if x >= 0 and x < m and y >=0 and y < n and (x, y) not in seen: queue.append((x, y)) seen.add((x, y)) steps += 1 return ans (Python BFS 模板代码) 我来用伪代码解释下这段代码的意思: 1234567891011121314151617template BFS(board) { 边界处理 seen = set() # 存储已经遍历过的节点,防止环的出现。 初始化队列 steps = 0 while 队列不为空 { 逐个取出队列中的元素(不包括在 while 循环内新添加的) if 满足条件 return steps for dir in dirs { 将周围的都加到队列,注意边界处理 } steps += 1 } return 不存在(一般是 -1)} (BFS 模板伪代码) 大家可以根据我的伪代码,自己定制属于自己的模板。 值得注意的是,本题我并没有使用 seen 来记录访问过的节点,而是直接原地修改,这是一个很常见的技巧,对这个技巧不熟悉的可以看下我的小岛专题 关键点 BFS BFS 模板 代码代码支持:Python3 123456789101112131415161718192021class Solution: def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: n = len(grid) if not grid or grid[0][0] == 1 or grid[n-1][n-1] == 1: return -1 steps = 1 queue = collections.deque() queue.append((0, 0)) grid[0][0] = 1 while queue: for _ in range(len(queue)): i, j = queue.popleft() if i == n - 1 and j == n - 1: return steps for dx, dy in [(-1,-1), (1,0), (0,1), (-1,0), (0,-1), (1,1), (1,-1), (-1,1)]: # 注意越界处理 if 0 <= i + dx < n and 0 <= j + dy < n and grid[i+dx][j+dy] == 0: queue.append((i + dx, j + dy)) grid[i + dx][j + dy] = 1 steps += 1 return -1 复杂度分析 时间复杂度:最坏的情况,我们需要遍历整个 board,因此时间复杂度取决于 cell 数,故时间复杂度为 $O(N ^ 2)$,其中 N 为边长。 空间复杂度:我们没有使用 seen,仅仅是借助了队列, 故空间复杂度为 $O(N)$,如果使用 seen 的话复杂度会上升到$O(N ^ 2)$,其中 N 为边长。 补充: 空间复杂度的$O(N)$ 是怎么来的? 我这里给大家画了一个图, 相信大家一下子就懂来。其中不同的颜色表示不同的层次,从红色开始表示第一层,然后往外扩张。可以看出队列最长的情况下和$N$同阶,因此空间复杂度为$O(N)$。 相关题目 200. 岛屿数量 695. 岛屿的最大面积 1162. 地图分析 62. 不同路径 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加 sa》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"BFS","slug":"算法/BFS","permalink":"https://lucifer.ren/blog/categories/算法/BFS/"}],"tags":[{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"BFS","slug":"BFS","permalink":"https://lucifer.ren/blog/tags/BFS/"}]},{"title":"你的衣服我扒了 - 《最长公共子序列》","slug":"LCS","date":"2020-06-30T16:00:00.000Z","updated":"2023-01-07T12:34:34.312Z","comments":true,"path":"2020/07/01/LCS/","link":"","permalink":"https://lucifer.ren/blog/2020/07/01/LCS/","excerpt":"之前出了一篇穿上衣服我就不认识你了?来聊聊最长上升子序列,收到了大家的一致好评。今天给大家带来的依然是换皮题 - 最长公共子序列系列。 最长公共子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长公共子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长公共子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法可能不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。","text":"之前出了一篇穿上衣服我就不认识你了?来聊聊最长上升子序列,收到了大家的一致好评。今天给大家带来的依然是换皮题 - 最长公共子序列系列。 最长公共子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长公共子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长公共子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法可能不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。 718. 最长重复子数组题目地址https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/ 题目描述1234567891011121314给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。示例 1:输入:A: [1,2,3,2,1]B: [3,2,1,4,7]输出: 3解释:长度最长的公共子数组是 [3, 2, 1]。说明:1 <= len(A), len(B) <= 10000 <= A[i], B[i] < 100 前置知识 哈希表 数组 二分查找 动态规划 思路这就是最经典的最长公共子序列问题。一般这种求解两个数组或者字符串求最大或者最小的题目都可以考虑动态规划,并且通常都定义 dp[i][j] 为 以 A[i], B[j] 结尾的 xxx。这道题就是:以 A[i], B[j] 结尾的两个数组中公共的、长度最长的子数组的长度。 关于状态转移方程的选择可以参考: 穿上衣服我就不认识你了?来聊聊最长上升子序列 算法很简单: 双层循环找出所有的 i, j 组合,时间复杂度 $O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 如果 A[i] == B[j],dp[i][j] = dp[i - 1][j - 1] + 1 否则,dp[i][j] = 0 循环过程记录最大值即可。 记住这个状态转移方程,后面我们还会频繁用到。 关键点解析 dp 建模套路 代码代码支持:Python Python Code: 1234567891011class Solution: def findLength(self, A, B): m, n = len(A), len(B) ans = 0 dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if A[i - 1] == B[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 ans = max(ans, dp[i][j]) return ans 复杂度分析 时间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 空间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 二分查找也是可以的,不过并不容易想到,大家可以试试。 1143.最长公共子序列题目地址https://leetcode-cn.com/problems/longest-common-subsequence 题目描述给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。例如,”ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 若这两个字符串没有公共子序列,则返回 0。 示例 1: 输入:text1 = “abcde”, text2 = “ace” 输出:3解释:最长公共子序列是 “ace”,它的长度为 3。示例 2: 输入:text1 = “abc”, text2 = “abc” 输出:3 解释:最长公共子序列是 “abc”,它的长度为 3。示例 3: 输入:text1 = “abc”, text2 = “def” 输出:0 解释:两个字符串没有公共子序列,返回 0。 提示: 1 <= text1.length <= 1000 1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。 前置知识 数组 动态规划 思路和上面的题目类似,只不过数组变成了字符串(这个无所谓),子数组(连续)变成了子序列 (非连续)。 算法只需要一点小的微调: 如果 A[i] != B[j],那么 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 关键点解析 dp 建模套路 代码 你看代码多像 代码支持:Python Python Code: 12345678910111213class Solution: def longestCommonSubsequence(self, A: str, B: str) -> int: m, n = len(A), len(B) ans = 0 dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if A[i - 1] == B[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 ans = max(ans, dp[i][j]) else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) return ans 复杂度分析 时间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 空间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 1035. 不相交的线题目地址https://leetcode-cn.com/problems/uncrossed-lines/description/ 题目描述我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。 现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。 以这种方法绘制线条,并返回我们可以绘制的最大连线数。 示例 1: 输入:A = [1,4,2], B = [1,2,4] 输出:2 解释:我们可以画出两条不交叉的线,如上图所示。我们无法画出第三条不相交的直线,因为从 A[1]=4 到 B[2]=4 的直线将与从 A[2]=2 到 B[1]=2 的直线相交。示例 2: 输入:A = [2,5,1,2,5], B = [10,5,2,1,5,2] 输出:3 示例 3: 输入:A = [1,3,7,1,7,5], B = [1,9,2,5,1] 输出:2 提示: 1 <= A.length <= 500 1 <= B.length <= 500 1 <= A[i], B[i] <= 2000 前置知识 数组 动态规划 思路从图中可以看出,如果想要不相交,则必然相对位置要一致,换句话说就是:公共子序列。因此和上面的 1143.最长公共子序列 一样,属于换皮题,代码也是一模一样。 关键点解析 dp 建模套路 代码 你看代码多像 代码支持:Python Python Code: 12345678910111213class Solution: def longestCommonSubsequence(self, A: str, B: str) -> int: m, n = len(A), len(B) ans = 0 dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if A[i - 1] == B[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 ans = max(ans, dp[i][j]) else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) return ans 复杂度分析 时间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 空间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 总结第一道是“子串”题型,第二和第三则是“子序列”。不管是“子串”还是“子序列”,状态定义都是一样的,不同的只是一点小细节。 只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 基础算法,把它彻底搞懂,再去面对出题人的各种换皮就不怕了。相反,如果你不去思考题目背后的逻辑,就会刷地很痛苦。题目稍微一变化你就不会了,这也是为什么很多人说刷了很多题,但是碰到新的题目还是不会做的原因之一。关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"动态规划","slug":"LeetCode/动态规划","permalink":"https://lucifer.ren/blog/categories/LeetCode/动态规划/"}],"tags":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"最长公共子序列","slug":"最长公共子序列","permalink":"https://lucifer.ren/blog/tags/最长公共子序列/"}]},{"title":"【LeetCode日记】 611. 有效三角形的个数","slug":"611.triangle","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.943Z","comments":true,"path":"2020/06/20/611.triangle/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/611.triangle/","excerpt":"题目地址: https://leetcode-cn.com/problems/valid-triangle-number/ ​","text":"题目地址: https://leetcode-cn.com/problems/valid-triangle-number/ ​ 题目描述123456789101112131415给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。示例 1:输入: [2,2,3,4]输出: 3解释:有效的组合是:2,3,4 (使用第一个 2)2,3,4 (使用第二个 2)2,2,3注意:数组长度不超过1000。数组里整数的范围为 [0, 1000]。 前置知识 排序 双指针 二分法 三角形边的关系 暴力法(超时)思路首先要有一个数学前提: 如果三条线段中任意两条的和都大于第三边,那么这三条线段可以组成一个三角形。即给定三个线段 a,b,c,如果满足 a + b > c and a + c > b and b + c > a,则线段 a,b,c 可以构成三角形,否则不可以。 力扣中有一些题目是需要一些数学前提的,不过这些数学前提都比较简单,一般不会超过高中数学知识,并且也不会特别复杂。一般都是小学初中知识即可。 如果你在面试中碰到不知道的数学前提,可以寻求面试官提示试试。 关键点解析 三角形边的关系 三层循环确定三个线段 代码代码支持: Python 1234567891011121314class Solution: def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False if a + b > c and a + c > b and b + c > a: return True return False def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 for i in range(n - 2): for j in range(i + 1, n - 1): for k in range(j + 1, n): if self.is_triangle(nums[i], nums[j], nums[k]): ans += 1 return ans 复杂度分析 时间复杂度:$O(N ^ 3)$,其中 N 为 数组长度。 空间复杂度:$O(1)$ 优化的暴力法思路暴力法的时间复杂度为 $O(N ^ 3)$, 其中 $N$ 最大为 1000。一般来说, $O(N ^ 3)$ 的算法在数据量 <= 500 是可以 AC 的。1000 的数量级则需要考虑 $O(N ^ 2)$ 或者更好的解法。 OK,到这里了。我给大家一个干货。 应该是其他博主不太会提的。原因可能是他们不知道, 也可能是他们觉得太小儿科不需要说。 由于前面我根据数据规模推测到到了解法的复杂度区间是 $N ^ 2$, $N ^ 2 * logN$,不可能是 $N$ (WHY?)。 降低时间复杂度的方法主要有: 空间换时间 和 排序换时间(我们一般都是使用基于比较的排序方法)。而排序换时间仅仅在总体复杂度大于 $O(NlogN)$ 才适用(原因不用多说了吧?)。 这里由于总体的时间复杂度是 $O(N ^ 3)$,因此我自然想到了排序换时间。当我们对 nums 进行一次排序之后,我发现: is_triangle 函数有一些判断是无效的 12345def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False # a + c > b 和 b + c > a 是无效的判断,因为恒成立 if a + b > c and a + c > b and b + c > a: return True return False 因此我们的目标变为找到a + b > c即可,因此第三层循环是可以提前退出的。 123456for i in range(n - 2): for j in range(i + 1, n - 1): k = j + 1 while k < n and num[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 这也仅仅是减枝而已,复杂度没有变化。通过进一步观察,发现 k 没有必要每次都从 j + 1 开始。而是从上次找到的 k 值开始就行。原因很简单, 当 nums[i] + nums[j] > nums[k] 时,我们想要找到下一个满足 nums[i] + nums[j] > nums[k] 的 新的 k 值,由于进行了排序,因此这个 k 肯定比之前的大(单调递增性),因此上一个 k 值之前的数都是无效的,可以跳过。 123456for i in range(n - 2): k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 由于 K 不会后退,因此最内层循环总共最多执行 N 次,因此总的时间复杂度为 $O(N ^ 2)$。 这个复杂度分析有点像单调栈,大家可以结合起来理解。 关键点分析 排序 代码12345678910111213class Solution: def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 nums.sort() for i in range(n - 2): if nums[i] == 0: continue k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:取决于排序算法 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"双指针","slug":"算法/双指针","permalink":"https://lucifer.ren/blog/categories/算法/双指针/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/categories/Medium/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/tags/Medium/"}]},{"title":"一文看懂《最大子序列和问题》","slug":"LSS","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:50.087Z","comments":true,"path":"2020/06/20/LSS/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/LSS/","excerpt":"最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。","text":"最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。 题目描述求取数组中最大连续子序列和,例如给定数组为 A = [1, 3, -2, 4, -5], 则最大连续子序列和为 6,即 1 + 3 +(-2)+ 4 = 6。 首先我们来明确一下题意。 题目说的子数组是连续的 题目只需要求和,不需要返回子数组的具体位置。 数组中的元素是整数,可能是正数,负数和 0。 子序列的最小长度为 1,不能为空。(这点很讨厌,不过题目就是这样~) 比如: 对于数组 [1, -2, 3, 5, -3, 2], 应该返回 3 + 5 = 8 对于数组 [0, -2, 3, 5, -1, 2], 应该返回 3 + 5 + -1 + 2 = 9 对于数组 [-9, -2, -3, -5, -3], 应该返回 -2(不能返回 0,即什么都不选) 解法一 - 暴力法(超时法)一般情况下,先从暴力解分析,然后再进行一步步的优化。 思路我们来试下最直接的方法,就是计算所有的子序列的和,然后取出最大值。记 Sum[i,….,j]为数组 A 中第 i 个元素到第 j 个元素的和,其中 0 <= i <= j < n,遍历所有可能的 Sum[i,….,j] 即可。 枚举以 0,1,2…n-1 开头的所有子序列,对于每一个以其开头的子序列,都去枚举从当前开始到索引 n-1 的情况。 这种做法的时间复杂度为 O(N^2), 空间复杂度为 O(1)。 代码JavaScript: 12345678910111213141516function LSS(list) { const len = list.length; let max = -Number.MAX_VALUE; let sum = 0; for (let i = 0; i < len; i++) { sum = 0; for (let j = i; j < len; j++) { sum += list[j]; if (sum > max) { max = sum; } } } return max;} Java: 123456789101112131415class MaximumSubarrayPrefixSum { public int maxSubArray(int[] nums) { int len = nums.length; int maxSum = Integer.MIN_VALUE; int sum = 0; for (int i = 0; i < len; i++) { sum = 0; for (int j = i; j < len; j++) { sum += nums[j]; maxSum = Math.max(maxSum, sum); } } return maxSum; }} Python 3: 12345678910111213import sysclass Solution: def maxSubArray(self, nums: List[int]) -> int: n = len(nums) maxSum = -sys.maxsize sum = 0 for i in range(n): sum = 0 for j in range(i, n): sum += nums[j] maxSum = max(maxSum, sum) return maxSum 空间复杂度非常理想,但是时间复杂度有点高。怎么优化呢?我们来看下下一个解法。 解法二 - 分治法思路先把数组平均分成左右两部分。 此时有三种情况: 最大子序列全部在数组左部分 最大子序列全部在数组右部分 最大子序列横跨左右数组 对于前两种情况,我们相当于将原问题转化为了规模更小的同样问题。 对于第三种情况,由于已知循环的一个端点(即中点),我们只需要进行一次循环,分别找出左边和右边的端点即可。 因此我们可以每次都将数组分成左右两部分,然后分别计算上面三种情况的最大子序列和,取出最大的即可。 举例说明,如下图: (by snowan) 这种做法的时间复杂度为 O(N*logN), 空间复杂度为 O(1)。 代码JavaScript: 1234567891011121314151617181920212223242526function helper(list, m, n) { if (m === n) return list[m]; let sum = 0; let lmax = -Number.MAX_VALUE; let rmax = -Number.MAX_VALUE; const mid = ((n - m) >> 1) + m; const l = helper(list, m, mid); const r = helper(list, mid + 1, n); for (let i = mid; i >= m; i--) { sum += list[i]; if (sum > lmax) lmax = sum; } sum = 0; for (let i = mid + 1; i <= n; i++) { sum += list[i]; if (sum > rmax) rmax = sum; } return Math.max(l, r, lmax + rmax);}function LSS(list) { return helper(list, 0, list.length - 1);} Java: 12345678910111213141516171819202122232425262728class MaximumSubarrayDivideConquer { public int maxSubArrayDividConquer(int[] nums) { if (nums == null || nums.length == 0) return 0; return helper(nums, 0, nums.length - 1); } private int helper(int[] nums, int l, int r) { if (l > r) return Integer.MIN_VALUE; int mid = (l + r) >>> 1; int left = helper(nums, l, mid - 1); int right = helper(nums, mid + 1, r); int leftMaxSum = 0; int sum = 0; // left surfix maxSum start from index mid - 1 to l for (int i = mid - 1; i >= l; i--) { sum += nums[i]; leftMaxSum = Math.max(leftMaxSum, sum); } int rightMaxSum = 0; sum = 0; // right prefix maxSum start from index mid + 1 to r for (int i = mid + 1; i <= r; i++) { sum += nums[i]; rightMaxSum = Math.max(sum, rightMaxSum); } // max(left, right, crossSum) return Math.max(leftMaxSum + rightMaxSum + nums[mid], Math.max(left, right)); }} Python 3 : 123456789101112131415161718192021import sysclass Solution: def maxSubArray(self, nums: List[int]) -> int: return self.helper(nums, 0, len(nums) - 1) def helper(self, nums, l, r): if l > r: return -sys.maxsize mid = (l + r) // 2 left = self.helper(nums, l, mid - 1) right = self.helper(nums, mid + 1, r) left_suffix_max_sum = right_prefix_max_sum = 0 sum = 0 for i in reversed(range(l, mid)): sum += nums[i] left_suffix_max_sum = max(left_suffix_max_sum, sum) sum = 0 for i in range(mid + 1, r + 1): sum += nums[i] right_prefix_max_sum = max(right_prefix_max_sum, sum) cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid] return max(cross_max_sum, left, right) 解法三 - 动态规划思路上面的分治虽然将问题规模缩小了,但是分解的三个子问题有两个是规模变小的同样问题,而有一个不是。 那能不能将其全部拆解为规模更小的同样问题,并且能找出递推关系呢? 如果可以,那么我们就可以使用记忆化递归或者动态规划来解决了。 不妨假设问题 Q(list, i) 表示 list 中以索引 i 结尾的情况下最大子序列和,那么原问题就转化为 max(Q(list, i)), 其中 i = 0,1,2…n-1 。 明确了状态, 继续来看下递归关系,这里是 Q(list, i)和 Q(list, i - 1)的关系,即如何根据 Q(list, i - 1) 推导出 Q(list, i)。 如果已知 Q(list, i - 1), 我们可以将问题分为两种情况,即以索引为 i 的元素终止,或者只有一个索引为 i 的元素。 如果以索引为 i 的元素终止, 那么就是 Q(list, i - 1) + list[i] 如果只有一个索引为 i 的元素,那么就是 list[i] 分析到这里,递推关系就很明朗了,即Q(list, i) = Math.max(0, Q(list, i - 1)) + list[i] 举例说明,如下图: (by snowan) 这种算法的时间复杂度 O(N), 空间复杂度为 O(1) 代码JavaScript: 12345678910function LSS(list) { const len = list.length; let max = list[0]; for (let i = 1; i < len; i++) { list[i] = Math.max(0, list[i - 1]) + list[i]; if (list[i] > max) max = list[i]; } return max;} Java: 1234567891011class MaximumSubarrayDP { public int maxSubArray(int[] nums) { int currMaxSum = nums[0]; int maxSum = nums[0]; for (int i = 1; i < nums.length; i++) { currMaxSum = Math.max(currMaxSum + nums[i], nums[i]); maxSum = Math.max(maxSum, currMaxSum); } return maxSum; }} Python 3: 12345678class Solution: def maxSubArray(self, nums: List[int]) -> int: dp = [0] * len(nums) ans = dp[0] = nums[0] for i in range(1, len(nums)): dp[i] = max(nums[i], dp[i - 1] + nums[i]) ans = max(ans, dp[i]) return ans 解法四 - 数学分析(前缀和)思路最后通过数学分析来看一下这个题目。 定义函数 S(i) ,它的功能是计算以 0(包括 0)开始加到 i(包括 i)的值。那么 S(j) - S(i - 1) 就等于 从 i 开始(包括 i)加到 j(包括 j)的值。 我们进一步分析,实际上我们只需要遍历一次计算出所有的 S(i), 其中 i 等于 0,1,2….,n-1。然后我们再减去之前的 S(k),其中 k 等于 0,1,i - 1,中的最小值即可。 因此我们需要用一个变量来维护这个最小值,还需要一个变量维护最大值。 这种算法的时间复杂度 O(N), 空间复杂度为 O(1)。 其实很多题目,都有这样的思想, 比如之前的《每日一题 - 电梯问题》。 代码JavaScript: 123456789101112131415function LSS(list) { const len = list.length; let max = list[0]; let min = 0; let sum = 0; for (let i = 0; i < len; i++) { sum += list[i]; if (sum - min > max) max = sum - min; if (sum < min) { min = sum; } } return max;} Java: 12345678910111213141516class MaxSumSubarray { public int maxSubArray3(int[] nums) { int maxSum = nums[0]; int sum = 0; int minSum = 0; for (int num : nums) { // prefix Sum sum += num; // update maxSum maxSum = Math.max(maxSum, sum - minSum); // update minSum minSum = Math.min(minSum, sum); } return maxSum; }} Python 3: 1234567891011class Solution: def maxSubArray(self, nums: List[int]) -> int: n = len(nums) maxSum = nums[0] minSum = sum = 0 for i in range(n): sum += nums[i] maxSum = max(maxSum, sum - minSum) minSum = min(minSum, sum) return maxSum 扩展如果题目变化为如下,问题该怎么解决呢? 1求取数组拼接 k 次的最大连续子序列和,例如给定数组为 A = [1, 3, 4, -5] ,k = 2, 拼接会的数组为 [1, 3, 4, -5, 1, 3, 4, -5],那么最大连续子序列和为 11,即子数组 [1, 3, 4, -5, 1, 3, 4] 的和。 直接将 A 拼接 k 次后转化为上面的问题的话空间会超出限制,空间复杂度为 $O(n * k)$。代码: 1234567class Solution: def solve(self, A, k): A = A * k dp = [0] * len(A) for i in range(len(A)): dp[i] = max(A[i], dp[i - 1] + A[i]) return max(dp) 然而实际上,我们可以仅拼接两次,因为当拼接次数大于 2,那么之后只是无限循环罢了。这种算法空间复杂度可以降低到 $O(n)$。 经过这样的处理,问题转化为求: A 拼接 min(2, k) 次后的最大子序和 + max(0, k-2) 次 A 的和(如果 A 的和小于 0,则不必加)。 代码: 123456789class Solution: def solve(self, nums, k): if not nums or not k: return 0 A = nums * min(2, k) dp = [0] * (len(A) + 1) for i in range(len(A)): dp[i] = max(A[i], dp[i - 1] + A[i]) return max(dp) + max(0, sum(nums)) * max(0, (k - 2)) 总结我们使用四种方法解决了《最大子序列和问题》,并详细分析了各个解法的思路以及复杂度,相信下次你碰到相同或者类似的问题的时候也能够发散思维,做到一题多解,多题一解。 实际上,我们只是求出了最大的和,如果题目进一步要求出最大子序列和的子序列呢?如果要题目允许不连续呢? 我们又该如何思考和变通?如何将数组改成二维,求解最大矩阵和怎么计算?这些问题留给读者自己来思考。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"子序列","slug":"算法/子序列","permalink":"https://lucifer.ren/blog/categories/算法/子序列/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"数组","slug":"数组","permalink":"https://lucifer.ren/blog/tags/数组/"}]},{"title":"穿上衣服我就不认识你了?来聊聊最长上升子序列","slug":"LIS","date":"2020-06-19T16:00:00.000Z","updated":"2023-03-31T10:20:34.521Z","comments":true,"path":"2020/06/20/LIS/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/LIS/","excerpt":"最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。","text":"最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。 300. 最长上升子序列题目地址https://leetcode-cn.com/problems/longest-increasing-subsequence 题目描述123456789101112给定一个无序的整数数组,找到其中最长上升子序列的长度。示例:输入: [10,9,2,5,3,7,101,18]输出: 4解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。说明:可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。你算法的时间复杂度应该为 O(n2) 。进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? 思路 美团和华为都考了这个题。 题目的意思是让我们从给定数组中挑选若干数字,这些数字满足: 如果 i < j 则 nums[i] < nums[j]。问:一次可以挑选最多满足条件的数字是多少个。 这种子序列求极值的题目,应该要考虑到贪心或者动态规划。这道题贪心是不可以的,我们考虑动态规划。 按照动态规划定义状态的套路,我们有两种常见的定义状态的方式: dp[i] : 以 i 结尾(一定包括 i)所能形成的最长上升子序列长度, 答案是 max(dp[i]),其中 i = 0,1,2, …, n - 1 dp[i] : 以 i 结尾(可能包括 i)所能形成的最长上升子序列长度,答案是 dp[-1] (-1 表示最后一个元素) 容易看出第二种定义方式由于无需比较不同的 dp[i] 就可以获得答案,因此更加方便。但是想了下,状态转移方程会很不好写,因为 dp[i] 的末尾数字(最大的)可能是 任意 j < i 的位置。 第一种定义方式虽然需要比较不同的 dp[i] 从而获得结果,但是我们可以在循环的时候顺便得出,对复杂度不会有影响,只是代码多了一点而已。因此我们选择第一种建模方式。 由于 dp[j] 中一定会包括 j,且以 j 结尾, 那么 nums[j] 一定是其所形成的序列中最大的元素,那么如果位于其后(意味着 i > j)的 nums[i] > nums[j],那么 nums[i] 一定能够融入 dp[j] 从而形成更大的序列,这个序列的长度是 dp[j] + 1。因此状态转移方程就有了:dp[i] = dp[j] + 1 (其中 i > j, nums[i] > nums[j]) 以 [10, 9, 2, 5, 3, 7, 101, 18] 为例,当我们计算到 dp[5]的时候,我们需要往回和 0,1,2,3,4 进行比较。 具体的比较内容是: 最后从三个中选一个最大的 + 1 赋给 dp[5]即可。 记住这个状态转移方程,后面我们还会频繁用到。 代码123456789101112class Solution: def lengthOfLIS(self, nums: List[int]) -> int: n = len(nums) if n == 0: return 0 dp = [1] * n ans = 1 for i in range(n): for j in range(i): if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) ans = max(ans, dp[i]) return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 435. 无重叠区间题目地址https://leetcode-cn.com/problems/non-overlapping-intervals/ 题目描述123456789101112131415161718192021222324252627给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。注意:可以认为区间的终点总是大于它的起点。区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。示例 1:输入: [ [1,2], [2,3], [3,4], [1,3] ]输出: 1解释: 移除 [1,3] 后,剩下的区间没有重叠。示例 2:输入: [ [1,2], [1,2], [1,2] ]输出: 2解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。示例 3:输入: [ [1,2], [2,3] ]输出: 0解释: 你不需要移除任何区间,因为它们已经是无重叠的了。 思路我们先来看下最终剩下的区间。由于剩下的区间都是不重叠的,因此剩下的相邻区间的后一个区间的开始时间一定是不小于前一个区间的结束时间的。 比如我们剩下的区间是[ [1,2], [2,3], [3,4] ]。就是第一个区间的 2 小于等于 第二个区间的 2,第二个区间的 3 小于等于第三个区间的 3。 不难发现如果我们将前面区间的结束和后面区间的开始结合起来看,其就是一个非严格递增序列。而我们的目标就是删除若干区间,从而剩下最长的非严格递增子序列。这不就是上面的题么?只不过上面是严格递增,这不重要,就是改个符号的事情。 上面的题你可以看成是删除了若干数字,然后剩下剩下最长的严格递增子序列。 这就是抽象的力量,这就是套路。 如果对区间按照起点或者终点进行排序,那么就转化为上面的最长递增子序列问题了。和上面问题不同的是,由于是一个区间。因此实际上,我们是需要拿后面的开始时间和前面的结束时间进行比较。 而由于: 题目求的是需要移除的区间,因此最后 return 的时候需要做一个转化。 题目不是要求严格递增,而是可以相等,因此我们的判断条件要加上等号。 这道题还有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。 代码你看代码多像 123456789101112131415class Solution: def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: n = len(intervals) if n == 0: return 0 dp = [1] * n ans = 1 intervals.sort(key=lambda a: a[0]) for i in range(len(intervals)): for j in range(i - 1, -1, -1): if intervals[i][0] >= intervals[j][1]: dp[i] = max(dp[i], dp[j] + 1) break # 由于是按照开始时间排序的, 因此可以剪枝 return n - max(dp) 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 646. 最长数对链题目地址https://leetcode-cn.com/problems/maximum-length-of-pair-chain/ 题目描述1234567891011121314给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。示例 :输入: [[1,2], [2,3], [3,4]]输出: 2解释: 最长的数对链是 [1,2] -> [3,4]注意:给出数对的个数在 [1, 1000] 范围内。 思路和上面的435. 无重叠区间是换皮题,唯一的区别这里又变成了严格增加。没关系,我们把等号去掉就行了。并且由于这道题求解的是最长的长度,因此转化也不需要了。 当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。 代码这代码更像了! 123456789101112131415class Solution: def findLongestChain(self, intervals: List[List[int]]) -> int: n = len(intervals) if n == 0: return 0 dp = [1] * n ans = 1 intervals.sort(key=lambda a: a[0]) for i in range(len(intervals)): for j in range(i - 1, -1, -1): if intervals[i][0] > intervals[j][1]: dp[i] = max(dp[i], dp[j] + 1) break # 由于是按照开始时间排序的, 因此可以剪枝 return max(dp) 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 452. 用最少数量的箭引爆气球题目地址https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/ 题目描述1234567891011121314在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。Example:输入:[[10,16], [2,8], [1,6], [7,12]]输出:2解释:对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。 思路把气球看成区间,几个箭可以全部射爆,意思就是有多少不重叠的区间。注意这里重叠的情况也可以射爆。这么一抽象,就和上面的646. 最长数对链一模一样了,不用我多说了吧? 当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。 代码代码像不像? 123456789101112131415class Solution: def findMinArrowShots(self, intervals: List[List[int]]) -> int: n = len(intervals) if n == 0: return 0 dp = [1] * n ans = 1 intervals.sort(key=lambda a: a[0]) for i in range(len(intervals)): for j in range(i - 1, -1, -1): if intervals[i][0] > intervals[j][1]: dp[i] = max(dp[i], dp[j] + 1) break # 由于是按照开始时间排序的, 因此可以剪枝 return max(dp) 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 优化大家想看效率高的,其实也不难。 LIS 也可以用 贪心 + 二分 达到不错的效率。代码如下: 代码文字版如下: 12345678910class Solution: def lengthOfLIS(self, A: List[int]) -> int: d = [] for a in A: i = bisect.bisect_left(d, a) if i < len(d): d[i] = a elif not d or d[-1] < a: d.append(a) return len(d) 如果求最长不递减子序列呢? 我们只需要将最左插入改为最右插入即可。代码: 123456789101112class Solution: def lengthOfLIS(self, A: List[int]) -> int: d = [] for a in A: # 这里改为最右 i = bisect.bisect(d, a) if i < len(d): d[i] = a # 这里改为小于等号 elif not d or d[-1] <= a: d.append(a) return len(d) 最左插入和最右插入分不清的可以看看我的二分专题。 也可以这么写,更简单一点: 12345678910def LIS(A): d = [] for a in A: # 如果求要严格递增就改为最左插入 bisect_left 即可 i = bisect.bisect(d, a) if i == len(d): d.append(a) elif d[i] != a: d[i] = a return len(d) More其他的我就不一一说了。 比如 673. 最长递增子序列的个数 (滴滴面试题)。 不就是求出最长序列,之后再循环比对一次就可以得出答案了么? 491. 递增子序列 由于需要找到所有的递增子序列,因此动态规划就不行了,妥妥回溯就行了,套一个模板就出来了。回溯的模板可以看我之前写的回溯专题。 最后推荐两道题大家练习一下,别看它们是 hard, 其实掌握了我这篇文章的内容一点都不难。 面试题 08.13. 堆箱子 参考代码: 12345678910111213class Solution: def pileBox(self, box: List[List[int]]) -> int: box = sorted(box, key=sorted) n = len(box) dp = [0 if i == 0 else box[i - 1][2] for i in range(n + 1)] ans = max(dp) for i in range(1, n + 1): for j in range(i + 1, n + 1): if box[j - 1][0] > box[i - 1][0] and box[j - 1][1] > box[i - 1][1] and box[j - 1][2] > box[i - 1][2]: dp[j] = max(dp[j], dp[i] + box[j - 1][2]) ans = max(ans , dp[j]) return ans 354. 俄罗斯套娃信封问题 参考代码: 1234567891011class Solution: def maxEnvelopes(self, envelopes: List[List[int]]) -> int: if not envelopes: return 0 n = len(envelopes) dp = [1] * n envelopes.sort() for i in range(n): for j in range(i + 1, n): if envelopes[i][0] < envelopes[j][0] and envelopes[i][1] < envelopes[j][1]: dp[j] = max(dp[j], dp[i] + 1) return max(dp) 960. 删列造序 III 参考代码: 1234567891011class Solution: def minDeletionSize(self, A): keep = 1 m, n = len(A), len(A[0]) dp = [1] * n for j in range(n): for k in range(j + 1, n): if all([A[i][k] >= A[i][j] for i in range(m)]): dp[k] = max(dp[k], dp[j] + 1) keep = max(keep, dp[k]) return n - keep 小任务:请尝试使用贪心在 NlogN 的时间内完成算法。(参考我上面的代码就行) 5644. 得到子序列的最少操作次数 由于这道题数据范围是 $10^5$,因此只能使用 $NlogN$ 的贪心才行。 关于为什么 10 ^ 5 就必须使用 $NlogN$ 甚至更优的算法我在刷题技巧提过。更多复杂度速查可参考我的刷题插件,公众号《力扣加加》回复插件获取即可。 参考代码: 12345678910111213141516class Solution: def minOperations(self, target: List[int], A: List[int]) -> int: def LIS(A): d = [] for a in A: i = bisect.bisect_left(d, a) if d and i < len(d): d[i] = a else: d.append(a) return len(d) B = [] target = { t:i for i, t in enumerate(target)} for a in A: if a in target: B.append(target[a]) return len(target) - LIS(B) 1626. 无矛盾的最佳球队 不就是先排下序,然后求 scores 的最长上升子序列么? 参考代码: 1234567891011class Solution: def bestTeamScore(self, scores: List[int], ages: List[int]) -> int: n = len(scores) persons = list(zip(ages, scores)) persons.sort(key=lambda x : (x[0], x[1])) dp = [persons[i][1] for i in range(n)] for i in range(n): for j in range(i): if persons[i][1] >= persons[j][1]: dp[i] = max(dp[i], dp[j]+persons[i][1]) return max(dp) 再比如 这道题 无非就是加了一个条件,我们可以结合循环移位的技巧来做。 关于循环移位算法西法在之前的文章 文科生都能看懂的循环移位算法 也做了详细讲解,不再赘述。 参考代码: 123456789101112131415class Solution: def solve(self, nums): n = len(nums) ans = 1 def LIS(A): d = [] for a in A: i = bisect.bisect_left(d,a) if i == len(d): d.append(a) else: d[i] = a return len(d) nums += nums for i in range(n): ans = max(ans , LIS(nums[i:i+n])) return ans 再再再比如 253 场周赛 Q4 压轴题 1964. 找出到每个位置为止最长的有效障碍赛跑路线 不就是求以每一个元素结尾的 LIS 么? 1234567891011121314class Solution: def longestObstacleCourseAtEachPosition(self, obstacles: List[int]) -> List[int]: def LIS(A): d = [] ans = [] for a in A: i = bisect.bisect_right(d, a) if d and i < len(d): d[i] = a else: d.append(a) ans.append(i+1) return ans return LIS(obstacles) 大家把我讲的思路搞懂,这几个题一写,还怕碰到类似的题不会么?只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 最长上升子序列就是一个非常经典的基础算法,把它彻底搞懂,再去面对出题人的各种换皮就不怕了。相反,如果你不去思考题目背后的逻辑,就会刷地很痛苦。题目稍微一变化你就不会了,这也是为什么很多人说刷了很多题,但是碰到新的题目还是不会做的原因之一。关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"动态规划","slug":"LeetCode/动态规划","permalink":"https://lucifer.ren/blog/categories/LeetCode/动态规划/"}],"tags":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"最长上升子序列","slug":"最长上升子序列","permalink":"https://lucifer.ren/blog/tags/最长上升子序列/"}]},{"title":"好未来-北京-视频面试","slug":"interview-log-haoweilai","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.304Z","comments":true,"path":"2020/06/20/interview-log-haoweilai/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/interview-log-haoweilai/","excerpt":"作者:阿吉 整理&点评:lucifer","text":"作者:阿吉 整理&点评:lucifer 为什么要写这个面经?lucifer 说让我写一下秋招面经,但我很菜,一开始不想写的。最主要的是不想暴露自己的菜,虽然群佬都知道我比较菜。 lucifer 的群大概是我唯一一个每时每刻都能得到响应的群吧。很开心当时只是随便加了一下,但认识了好多大佬,经常在群里问一些很没头脑的问题,但总有人会艾特我回答,尽可能去触摸自己的知识边界帮我解答,大家都在交流(技术+扯皮)。 比较遗憾没偷学到 lucifer 的算法能力,也没能白嫖到小漾的美图。在 lucifer 群里开群语音,还有幸白嫖过川川(若川大佬)的声音 hhhhhh。大概我挺希望小漾看到这个文章能主动点晒照一下 hhhh(感觉第二天我就没了)。 面试体验好未来的四位面试官给予的面试体验很舒服。四面面试官说话一直用“您”。四面面试快结束的时候: 我:“我们的时间价值并不对等,所以很感谢您愿意抽时间面试我” 面试官:“不,我们的时间是对等的,我们今天只是在平等的交流”。 好未来的面试体验可能是目前秋招面试最舒服的,因为平时喜欢看很多闲书(面试很少会问的那种),把那些知识都有机会和面试官进行了分享。 面试官的问题用 Q 表示, 我的回答用 A 表示,序号表示第几个。 比如 Q5 表示面试官的第五个问题, A8 表示我对第八个问题的回答。 一面(男)面试内容 Q1: 聊项目,聊实习公司,问什么时候开始学前端 A1: 大二 Q2: 说一下排序算法。手撕冒泡,快排及其优化 A2: blabla lucifer 小提示: 要可以手撕才行哦。 Q3: TCP 和 UDP 区别。TCP 为什么是可靠的? A3: 《计网 自顶向下》,同时说了应用场景。 Q4: 三次握手,四次挥手 A4: 过程,字段,为何两次不行,各自作用。SYN 半连接攻击。 Q5: 谈一下 HTTP A5: 详细说 HTTP 1.0 1.1 2 3 细说文本格式和二进制格式(这个很感谢 feiker 大表哥在群里说了下),以及 HPACK,应用层和传输层的队头阻塞,多路复用和多路分解瞎扯了一下 302 303 307 在 RFC 规范中的发展历史,其实是跟浏览器大战年代相关的。 Q6: 浏览器缓存 A6: 四级:Service Work, Memory Cache, Disk Cache, Push Cache HTTP 控制的缓存位于 Disk Cache,即强缓存和协商缓存。二者中间的启发式缓存。 Q7: CDN A7: content dispatch newwork。 《计网 自顶向下》 Q8: 从 URL 到浏览器渲染,仅围绕 HTTP 相关展开 A8: 因为仅涉及 HTTP,除了常规回答,谈了下 webkit 里的三类资源加载器,以及网络栈。 Q9: 编译性语言 和 解释性语言的区别 A9: java,js。引申 JVM 和 V8。以及 V8 在早些年间拒绝采用中间码(《了不起的 nodejs》那本书比较老,里面就是无中间码),后来又采用了。其实最开始的原因就是考虑到移动端存储量。 Q10: 单线程的原因,好处 A10: JS 多线程对 DOM 的坏处。引申 webkit 多线程,看面试官比较感兴趣,又分析了浏览器的多进程架构,以及 Renderer 进程的四个配置项。 二面-男面试内容 Q1: 聊项目,前端倒计时,IOS 兼容等 A1: … Q2: 各种排序的时间复杂度 A2: … lucifer 小提示: 不要死记硬背。 Q3: 手撕代码 12345678function tpl(template, data){}// 输入tpl('<div class={%className%}>{%name%}</div>', {className:'hd', name:123})// 输出<div class=\"hd\">123</div>// 面试时编码思路:根据浏览器的词法分析去做,用stack。但存在问题。 A3:… Q4: 看代码说输出,作用域 1234567891011var a = 2;function fn1() { var a = 1; console.log(this.a + a);}function fn2() { var a = 10; fn1();}fn2();// 扯了一下this指向,以及C++中作用域的_variable表默认添加。 A4: … Q5: 你对闭包的理解 A5: 函数执行的保护机制。围绕函数执行机制(后来 lucifer 男神讲可以从词法作用域说,但毕竟是面试,感觉从函数执行来讲比较 ok),结合 V8 生成 AST 角度去谈何为闭包。具像为作用域链,及其 2 个表象。优缺点。理论应用:在 Vue 中的应用 Dep(),React 中的应用 Redux dispatch,设计模式中单例模式。项目应用:H5 前端自拟倒计时 destroyed 销毁引用。 三面-女面试内容 Q1: 自我介绍 A1: 叫 AJ,来自 X,能干活。 Q2: 聊点你的学校经历吧 A2: 在校职务… Q3: 为什么选择前端 A3: 经过大一尝试过 java,py,cnn 后决定,前端作为当下的生存技能。兴趣不局限于此。 lucifer 小提示: 不要把自己局限到前端。 Q4: 为什么不现在就去学后端 A4: 我明白自己每个阶段想要的是什么,当下秋招的我应该找一份匹配自己的工作。 Q5: 未来三年的职业规划 A5: 业务崽。 Q6: 实习公司,对比百度、腾讯、小米,最不喜欢哪一个? A6: 从不同的层面去说最喜欢哪家。 Q7: 你觉得你的缺点有什么 A7: 不喜欢跟人争执,浪费时间且无趣。 Q8: 为什么会投我们公司,了解我们吗 A8: 很好的朋友在开课吧(然哥),给我说好未来还挺不错的。秒投了。 Q9: 我们的业务有…你喜欢哪个业务啊 A10: 直播吧 四面-男面试内容 Q1: Vue 那种左右界面,中间的竖线可以滑动,左右布局跟着变化,怎么去做优化,可以从哪些角度触发 A1: 不会。尝试从 Vue Object.freeze() 和 提升图层角度去说。 Q2: GPU 硬件加速渲染说下原理 A2: 不会。从 CSS3 触发的角度说了下。 Q3: HTTPS 性能损耗在哪里? A3: TLS 握手。从《计算机网络 自顶向下》那本书里提的角度简单说了下。同时认为非对称加密算法对服务端资源消耗比较大。 Q4: 你如何去解决前端人员被需求压满,然后做业务觉得没有技术成长 A4: 不局限在功能点的开发,真正理解业务,理解业务流程中的数据流向以及坑点。当在当前环境遇到技术瓶颈要跳槽时,带着已有经验去下一个环境。 Q5. 谈谈 WebSocket,然后怎么去改造。 A5: 简单说了一点理论,直言没实践过。 lucifer 小提示: 可以自己实现一个 WebSocket 玩玩就啥都知道了。 Q6: 直播业务中,常用的协议是什么 A6: 仅知道 webRTC。 Q7: 海量数据找出最大的 K 个,怎么找?时间复杂度是多少? A7: lucifer 之前的文章应该有过,没记牢固。简单说了下。 lucifer 小提示:我们只需要建立一个大小为 K 的小顶堆,N 个数分别入堆,最后堆顶的元素就是第 K 大的。 时间复杂度 $O(NlogK)$ Q8: 了解好未来吗?为什么要来? A8: 做教育的。我哥推荐的。 PS: 面试白菜起步。SP 面是四面。 lucifer 点评由于是校招的原因,整个面试过程比较注重的是基础知识以及思考和学习方式。并且可以看出侧重点依然是: 网络(TCP,DNS,HTTP,HTTPS,浏览器缓存等) 浏览器渲染(GPU 硬件加速, webkit 原理等) 数据结构与算法(排序算法,复杂度分析,堆的应用等) 对于每一个部分,我们首先要做的是建立大局观,这样即使错,也不会错到哪去。大局观建立好了,相当于基本的知识框架有了,接下来就是填充知识框架了。这个阶段最主要的就是巩固复习和查缺补漏。经过这样的一个学习,相信你也能够在面试中崭露头角,获得心仪的 offer。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"},{"name":"好未来","slug":"好未来","permalink":"https://lucifer.ren/blog/categories/好未来/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"},{"name":"好未来","slug":"好未来","permalink":"https://lucifer.ren/blog/tags/好未来/"}]},{"title":"从零打造一个舒服的Mac开发环境 - 装机篇","slug":"mac-setup-for-fe","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.493Z","comments":true,"path":"2020/06/20/mac-setup-for-fe/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/mac-setup-for-fe/","excerpt":"前两天新买的 16 寸 mac pro 到手了。 本来想直接将旧电脑的数据做一个迁移,但是有同学反应想看“如何从零打造一个舒适的开发环境”,于是就有了这篇文章。","text":"前两天新买的 16 寸 mac pro 到手了。 本来想直接将旧电脑的数据做一个迁移,但是有同学反应想看“如何从零打造一个舒适的开发环境”,于是就有了这篇文章。 开箱 配置和价格在正式开始之前,我们先来介绍下主机的配置。 2.3GHz 8-core 9th-generation Intel Core i9 processor Turbo Boost up to 4.8GHz AMD Radeon Pro 5500M with 4GB of GDDR6 memory 32GB 2666MHz DDR4 memory 1TB SSD storage¹ 16-inch Retina display with True Tone Touch Bar and Touch ID Four Thunderbolt 3 ports 这个电脑要比 15 寸的 pro 重 100 多克,扬声器,显卡要比 15 寸的更加好一点,touch bar 重也将 ESC 和 TouchID 做成了实体键,最关键的是和 15 寸价格一样,我这个配置下来价格是RMB 25,135 。 如何从零打造一个舒适的开发环境 文字版 视频版 P1&P2 视频录制的声音比较小 Next本期视频只是一个简单的装机,以及系统配置。并不涉及到软件的深度使用,如果感兴趣可以给我留言,我会在之后给大家带来相关的攻略。","categories":[],"tags":[{"name":"Mac","slug":"Mac","permalink":"https://lucifer.ren/blog/tags/Mac/"},{"name":"装机","slug":"装机","permalink":"https://lucifer.ren/blog/tags/装机/"},{"name":"必备软件","slug":"必备软件","permalink":"https://lucifer.ren/blog/tags/必备软件/"}]},{"title":"【RFC】XXX 公司监控体系需求与技术调研","slug":"rfc-monitor","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.984Z","comments":true,"path":"2020/06/20/rfc-monitor/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/rfc-monitor/","excerpt":"线上问题回溯困难,无法快速准确重现问题,导致客户满意度下降,影响团队交付效率和质量,建立完善的监控体系可以很好的解决这个问题。","text":"线上问题回溯困难,无法快速准确重现问题,导致客户满意度下降,影响团队交付效率和质量,建立完善的监控体系可以很好的解决这个问题。 背景线上问题回溯困难,无法快速准确重现问题,导致客户满意度下降,影响团队交付效率和质量。 需求期望有一套工具,系统或者平台,可以满足: 在收到用户反馈的时候能够快速重现问题并解决。 测试同学发现问题,不需要花费大量事件和开发人员重现,沟通,以及记录问题重现路径等 线上发现问题可以进行告警,防止大规模用户有问题,并且不能及时感知和解决。 缩短团队内部 BUG 修复的闭环流程,减少非本质复杂度问题的干扰,快速将问题聚焦到具体的代码。 带着上面的需求,我们来看下市面上已有的经典方案, 在这里挑选几个具有代表性的。 市面上已有的方案对比LogRocket一句话概括: 用看录像的方式重现问题。 官网地址: https://logrocket.com/ 特点 更多功能: https://docs.logrocket.com/docs 接入方式 价格 Sentry一句话概括: 开源,强大的监控平台。 官网地址: https://sentry.io/ 特点功能较多,提供了较多的概念和功能,比如 Context,ENvironments,Breadcrumbs 等。另外其和 CI,CD 集成地也非常好。 详细内容: https://docs.sentry.io/workflow/releases/?platform=node 另外其支持的平台和扩展功能非常多,如果对这部分有特殊要求,Sentry 无疑是优先考虑的选择。 接入方式 Sign up for an account Install your SDK 12# Using yarn$ yarn add @sentry/node@5.8.0 Configure it 12const Sentry = require(\"@sentry/node\");Sentry.init({ dsn: \"https://<key>@sentry.io/<project>\" }); 价格 FunDebug一句话概括:国内知名度较高的监控工具,国内业务这块很有竞争力。 https://www.fundebug.com/ 特点支持小程序,小游戏。多种现成的报警方式,支持 WebHook,智能报警(同样的代码产生的同一个错误,在不同浏览器上的报错信息是各不相同的),内置团队协作工具。 接入方式这里以 Vue 项目为例。 免费注册 创建项目 配置 1npm install fundebug-javascript fundebug-vue --save 123456import * as fundebug from \"fundebug-javascript\";import fundebugVue from \"fundebug-vue\";fundebug.init({ apikey: \"API-KEY\",});fundebugVue(fundebug, Vue); 价格 其他后期可能功能点 性能监控 用户行为监控(已经有埋点,不不确定是否可以 Cover 这个需求) 自研假设我们已经做好了我们自己的监控平台,我们需要对公司内部甚至外部宣传我们的监控平台,我们会怎么进行宣传。 然后带着这些东西,我们进行规划,技术选型,排期,写代码,测试,上线。 宣传语 接入方便,侵入性小 支持多端,扩展性强(支持多种框架定制接入),完美契合业务发展 打通客服系统,开发直接对接到客户,免去了中间对接的信息缺失和时间损耗。 重现率高,能够准确重现用户的现场情况 打通报警系统 打通调试平台… 优劣分析优势完美契合我们自身的业务,后期好维护和增添功能 劣势如果功能需要做的超出市面,需要耗费巨大的人力和财力。 如果市面上不断发展,功能不能断完善,内部如果想要这样的功能要么继续追赶,要不买一套商用的,但是之前的努力岂不是白费了。除非内部两套系统,但是这种模式未免太反直觉。 架构与选型对外都宣传完了,我们需要具体开始进行架构与选型了。 定义对外接口我们对外宣传的目标是接入方便,侵入性小。因此一定要简洁,这里参考了以上几个平台的写法,其实这几个平台的都是大同小异。 注册应用获取 AppId 安装 1npm i --save @lucifer/monitor 引用 12345678910import monitor from \"@lucifer/monitor\";monitor.init({ user: { name: \"\", email: \"\", mobile: \"\", isVIP: true, }, appId: \"lucifer520\",}); 多端和多框架支持 Vue: 123456789101112import Vue form 'vue';import monitor from '@lucifer/connectors/vue';monitor.init({ user: { name: '', email: '', mobile: '', isVIP: true }, appId: 'lucifer520'})monitor.use(Vue) Wechat: 12345678910import monitor from \"@lucifer/connectors/wechat\";monitor.init({ user: { name: \"\", email: \"\", mobile: \"\", isVIP: true, }, appId: \"lucifer520\",}); 定义内部接口架构图: 接口系统交互图会在详细设计中给出,这里只给出大致范围: logs 服务器和告警平台的交互接口 rules 的规则解析 logs 的解析 构建系统对接 调试系统对接 … 业务形态特点 数据量会随着采集规模增大而增加,因此预估用户数量以及增长速度对系统架构设计有很大影响 终端的上报策略对影响很大,断网,弱网等情况如何上报也对结果有影响 框架选型 & 规范 & 约定暂无 其他解决方案 Badjs FrontJS","categories":[],"tags":[{"name":"RFC","slug":"RFC","permalink":"https://lucifer.ren/blog/tags/RFC/"},{"name":"技术调研","slug":"技术调研","permalink":"https://lucifer.ren/blog/tags/技术调研/"},{"name":"监控","slug":"监控","permalink":"https://lucifer.ren/blog/tags/监控/"}]},{"title":"算法小白如何高效、快速刷 leetcode?","slug":"刷题新手","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.694Z","comments":true,"path":"2020/06/20/刷题新手/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/刷题新手/","excerpt":"我本身刷了大概 600 道左右的题目,总结 200 多篇的题解,另外总结了十多个常见的算法专题,基本已经覆盖了大多数的常见考点和题型,全部放在我的 Github https://github.com/azl397985856/leetcode 。 然而作为一个新手,看着茫茫多的题解和资料难免会陷入一种“不知从何开始”的境地。不必担心,你不是一个人。 实际上,我最近一直在思考“初学者如何快速提高自己的算法能力,高效刷题”。因此我也一直在不断定位自己,最终我对自己作出了定位“用清晰直白的语言还原解题全过程,做西湖区最好的算法题解”。 然而我意识到,我进去了一个很大的误区。我的想法一直是“努力帮助算法小白提高算法能力,高效刷题”。然而算法小白除了清晰直白的算法题解外,还需要系统的前置知识。因此我的假设“大家都会基础的数据结构和算法”很可能就是不成立的。","text":"我本身刷了大概 600 道左右的题目,总结 200 多篇的题解,另外总结了十多个常见的算法专题,基本已经覆盖了大多数的常见考点和题型,全部放在我的 Github https://github.com/azl397985856/leetcode 。 然而作为一个新手,看着茫茫多的题解和资料难免会陷入一种“不知从何开始”的境地。不必担心,你不是一个人。 实际上,我最近一直在思考“初学者如何快速提高自己的算法能力,高效刷题”。因此我也一直在不断定位自己,最终我对自己作出了定位“用清晰直白的语言还原解题全过程,做西湖区最好的算法题解”。 然而我意识到,我进去了一个很大的误区。我的想法一直是“努力帮助算法小白提高算法能力,高效刷题”。然而算法小白除了清晰直白的算法题解外,还需要系统的前置知识。因此我的假设“大家都会基础的数据结构和算法”很可能就是不成立的。 小白阶段划分如果让我对算法小白进行一个阶段划分的话,我会将其分为: 阶段一 系统学习数据结构和算法知识。第一,你不能根本不懂得基础,比如根本不知道什么哈希表,或者只知道其简单的 API。 第二,你不能从网上不断搜索知识,因为这些知识是零散的,不利于新手形成自己的算法观。 当你成功跨越了上面两个坎,那么恭喜你,你可以进入下一个阶段啦。 对于这个阶段,想要跨过。需要系统性学习一些基础知识,推荐啃《算法 4》或者直接啃各个大学里面的教材。实在有困难的,可以先啃《算法图解》,《我的第一本算法书》这种入个门,然后再去啃。 阶段二 针对性刷题。比如按照力扣的标签去刷。因此上面的学习阶段并不见得你要学习完所有的基础再去刷,而是学习一个专题就可以针对性地刷。比如我学了二分法,就可以找一个二分法的题目刷一下。 想要跨越这一个坎,除了多做题之外,还有一个就是多看题解,多写题解。当然要看优秀的题解,这个我会在后面提到。 如果你跨越完上面两个坎,那么恭喜你, 你已经不是算法小白了(至少对于非算法岗来说)。 我的算法观继续回到刚才的问题“我的定位误区”。正因为很多小白没有跨越阶段一,因此我的所谓的“用清晰直白的语言还原解题全过程,做西湖区最好的算法题解”对他们没有实质帮助。他们迫切需要的是一个系统地整理算法思想,套路 的东西。因此我准备搞 91,这个就是后话,不再这里赘述。 注意,上面我提到了一个名次算法观。我并不知道这个词是否真的存在,不过这并不重要。如果不存在我就赋予其含义,如果存在我就来重新定义它。 算法观指的是你对于算法全面的认识。比如我拿到一个题目,如何审题,如何抽象成算法模型,如何根据模型选取合适的数据结构和算法。这就需要你对各种数据结构与算法的特性,使用场景有着身后的理解。 我举一个例子,这个例子就是今天(2020-06-12)我的 91 群的每日一题。 API 示例: 12345678910111213141516class LRUCache: def __init__(self, capacity: int): def get(self, key: int) -> int: def put(self, key: int, value: int) -> None:# Your LRUCache object will be instantiated and called as such:# obj = LRUCache(capacity)# param_1 = obj.get(key)# obj.put(key,value) 按照上面的过程,我们来套一个。 如何审题 看完题的话,只要抓住一个核心点即可。对于本题,核心点在于 删除最久未使用,O(1)时间。 抽象算法模型 这个题目是一个设计题。API 帮我们设计好了,只需要填充功能即可,也就是说算法模型不需要我们抽象了。 根据模型选取合适的数据结构和算法 我们的算法有两个操作:get 和 put。既然要支持这两个操作,肯定要有一个地方存数据。那么我们存到哪里呢?数组?链表?哈希表?其中链表又有很多,单向双向,循环不循环。 由于第一步审题过程中,我们获取到 O(1)时间 这个关键信息。那么: 数组无法做到更新,删除 $O(1)$ 链表无法做到查找,更新,删除 $O(1)$。 有的人说链表更新,删除是 $O(1)$,那么我要问你如何找到需要删除的节点呢?遍历找到的话最坏情况下就是 $O(N)$ 哈希表是无序的,因此不能实现 删除最久未使用。 似乎单独使用三种的任何一种都是不可以的。那么我们考虑组合多种数据结构。 我刚才说了链表只所以删除和更新都是 $O(N)$,是因为查找的时间损耗。 具体来说,我要删除图中值为 3 的节点,需要移动一次。因为我只能从头开始遍历去找。 (图 1) 又或者我要更新图中值为 7 的节点,则需要移动两次。 (图 2) 有没有什么办法可以省去这种遍历的时间损耗呢?其实我们的根本目的是找到目标节点, 而找到目标节点最暴力的方式是遍历。有没有巧妙一点的方法呢?毫无疑问,如果不借助额外的空间,这是不可能的。我们的想法只有空间换时间。 假设有这么一种数据结构,你告诉它你想要查的对象,它能帮你在 $O(1)$ 的时间内找到并返回给你结果。结合这个神秘数据结构和链表是不是我们就完成这道题了?这个神秘的数据结构就是哈希表。如果你对哈希表熟悉的话,想到几乎应该是瞬间的事情。如果不熟悉,那么经过排除,也应该可以得出这个结论。相信你随着做题数的增加,这种算法直觉会更加敏锐。 然而上面的空间复杂度是 $O(N)$。如果我的内存有限,不能承受 $O(N)$ 的空间,怎么办呢?相应地,我们可能就需要牺牲时间。那么问题是我们必须要退化到 $O(N)$ 么?显然不是,我们可以搞一些存档点。比如: 这样,我们需要操作 1 前面的,我们就从头开始遍历,如果需要操作 1 后面的,就从 1 开始遍历。时间复杂度最坏的情况可以降低到 $O(N / 2)$。通过进一步增加存档点,可以进一步减少时间,但是会增加空间。这是一种取舍。类似的取舍在实际工程中很多,这里不展开。 如果你了解过跳表, 实际上,上面的算法就是跳表的基本思想。 如果对每一道题你都能按照上面的流程走一遍,并且基于增加适当扩展,我相信你的刷题效率会高得可怕。 每道题都想这么多么?强烈建议新手都按照上面的逻辑进行思考,做题,并写题解总结。这样随着做题数的增加,量变引起质变,你会发现上面的几个步骤做下来很可能就是几秒钟的事情。如果你擅长图解,或者你经常看别人的图解(比如我的),那么这种图解能够帮你更快地检索大脑中的信息,这个时间会更短。 图解就是大脑检索信息的哈希表?哈哈,Maybe。 题解的水很深我看了很多人的题解直接就是两句话,然后跟上代码: 1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] 这种题解说实话,只针对那些”自己会, 然后去题解区看看有没有新的更好的解法的人“。但是大多数看题解的人是那种自己没思路,不会做的人。那么这种题解就没什么用了。 我认为好的题解应该是新手友好的,并且能够将解题人思路完整展现的题解。比如看到这个题目,我首先想到了什么(对错没有关系),然后头脑中经过怎么样的筛选将算法筛选到具体某一个或某几个。我的最终算法是如何想到的,有没有一些先行知识。 当然我也承认自己有很多题解也是直接给的答案,这对很多人来说用处不大,甚至有可能有反作用,给他们一种”我已经会了“的假象。实际上他们根本不懂解题人本身原本的想法, 也许是写题解的人觉得”这很自然“,也可能”只是为了秀技“。 刷题顺序最后给小白一个刷题顺序,帮助大家最大化利用自己的时间。 基础篇(30 天)基础永远是最重要的,先把最最基础的这些搞熟,磨刀不误砍柴工。 数组,队列,栈 链表 树与递归 哈希表 双指针 思想篇(30 天)这些思想是投资回报率极高的,强烈推荐每一个小的专题花一定的时间掌握。 二分 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 提高篇(31 天)这部分收益没那么明显,并且往往需要一定的技术积累。出现的频率相对而言比较低。但是有的题目需要你使用这些技巧。又或者可以使用这些技巧可以实现降维打击。 贪心 分治 位运算 KMP & RK 并查集 前缀树 线段树 堆 最后目前,我本人也在写一本题解方面的书包括近期组织的 91 算法 ,其目标受众正是“阶段一到阶段二”。为了真正帮助刷题小白成长,我打算画三个月的时间对数据结构和算法进行系统总结,帮助大家跨过阶段一。当然我还会不断更新题解,通过清晰直白的方式来让大家跨越阶段二。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/categories/经验分享/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/tags/经验分享/"}]},{"title":"你不知道的 TypeScript 泛型(万字长文,建议收藏)","slug":"ts-generics","date":"2020-06-15T16:00:00.000Z","updated":"2023-01-07T12:34:34.555Z","comments":true,"path":"2020/06/16/ts-generics/","link":"","permalink":"https://lucifer.ren/blog/2020/06/16/ts-generics/","excerpt":"泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。 相信大家都经历过,看到过,或者正在写一些应用,这些应用充斥着各种重复类型定义, any 类型层出不穷,鼠标移到变量上面的提示只有 any,不要说类型操作了,类型能写对都是个问题。我也经历过这样的阶段,那个时候我对 TS 还比较陌生。 随着在 TS 方面学习的深入,越来越认识到 真正的 TS 高手都是在玩类型,对类型进行各种运算生成新的类型。这也好理解,毕竟 TS 提供的其实就是类型系统。你去看那些 TS 高手的代码,会各种花式使用泛型。 可以说泛型是一道坎,只有真正掌握它,你才知道原来 TS 还可以这么玩。怪不得面试的时候大家都愿意问泛型,尽管面试官很可能也不怎么懂。 只有理解事物的内在逻辑,才算真正掌握了,不然永远只是皮毛,不得其法。 本文就带你走进泛型,带你从另一个角度看看究竟什么是泛型,为什么要有它,它给 TS 带来了什么样的不同。 注意:不同语言泛型略有不同,知识迁移虽然可以,但是不能生搬硬套,本文所讲的泛型都指的是 TS 下的泛型。","text":"泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。 相信大家都经历过,看到过,或者正在写一些应用,这些应用充斥着各种重复类型定义, any 类型层出不穷,鼠标移到变量上面的提示只有 any,不要说类型操作了,类型能写对都是个问题。我也经历过这样的阶段,那个时候我对 TS 还比较陌生。 随着在 TS 方面学习的深入,越来越认识到 真正的 TS 高手都是在玩类型,对类型进行各种运算生成新的类型。这也好理解,毕竟 TS 提供的其实就是类型系统。你去看那些 TS 高手的代码,会各种花式使用泛型。 可以说泛型是一道坎,只有真正掌握它,你才知道原来 TS 还可以这么玩。怪不得面试的时候大家都愿意问泛型,尽管面试官很可能也不怎么懂。 只有理解事物的内在逻辑,才算真正掌握了,不然永远只是皮毛,不得其法。 本文就带你走进泛型,带你从另一个角度看看究竟什么是泛型,为什么要有它,它给 TS 带来了什么样的不同。 注意:不同语言泛型略有不同,知识迁移虽然可以,但是不能生搬硬套,本文所讲的泛型都指的是 TS 下的泛型。 引言我总结了一下,学习 TS 有两个难点。第一个是TS 和 JS 中容易混淆的写法,第二个是TS中特有的一些东西。 TS 中容易引起大家的混淆的写法 比如: (容易混淆的箭头函数) 再比如: (容易混淆的 interface 内的小括号) TS 中特有的一些东西 比如 typeof,keyof, infer 以及本文要讲的泛型。 把这些和 JS 中容易混淆的东西分清楚,然后搞懂 TS 特有的东西,尤其是泛型(其他基本上相对简单),TS 就入门了。 泛型初体验在强类型语言中,一般而言需要给变量指定类型才能使用该变量。如下代码: 12const name: string = \"lucifer\";console.log(name); 我们需要给 name 声明 string 类型,然后才能在后面使用 name 变量,当我们执行以下操作的时候会报错。 给 name 赋其他类型的值 使用其他类型值特有的方法(比如 Number 类型特有的 toFixed) 将 name 以参数传给不支持 string 的函数。 比如 divide(1, name),其中 divide 就是功能就是将第一个数(number 类型)除以第二个数(number 类型),并将结果返回。 TS 除了提供一些基本类型(比如上面的 string)供我们直接使用。还: 提供了 inteface 和 type 关键字供我们定义自己的类型,之后就能像使用基本类型一样使用自己定义的类型了。 提供了各种逻辑运算符,比如 &, | 等 ,供我们对类型进行操作,从而生成新的类型。 提供泛型,允许我们在定义的时候不具体指定类型,而是泛泛地说一种类型,并在函数调用的时候再指定具体的参数类型。 。。。 也就是说泛型也是一种类型,只不过不同于 string, number 等具体的类型,它是一种抽象的类型,我们不能直接定义一个变量类型为泛型。 简单来说,区别于平时我们对值进行编程,泛型是对类型进行编程。这个听起来比较抽象。之后我们会通过若干实例带你理解这句话,你先留一个印象就好。 为了明白上面这句话,·首先要区分“值”和“类型”。 值和类型我们平时写代码基本都是对值编程。比如: 123456789if (person.isVIP) { console.log('VIP')}if (cnt > 5) { // do something}const personNames = persons.map(p => p.name)... 可以看出这都是对具体的值进行编程,这符合我们对现实世界的抽象。从集合论的角度上来说, 值的集合就是类型,在 TS 中最简单的用法是对值限定类型,从根本上来说是限定值的集合。这个集合可以是一个具体的集合,也可以是多个集合通过集合运算(交叉并)生成的新集合。 (值和类型) 再来看一个更具体的例子: 1234function t(name: string) { return `hello, ${name}`;}t(\"lucifer\"); 字符串 “lucifer” 是 string 类型的一个具体值。 在这里 “lucifer” 就是值,而 string 就是类型。 TS 明白 “lucifer” 是 string 集合中的一个元素,因此上面代码不会有问题,但是如果是这样就会报错: 1t(123); 因为 123 并不是 string 集合中的一个元素。 对于 t(“lucifer”)而言,TS 判断逻辑的伪代码: 123456v = getValue(); // will return 'lucifer' by astif (typeof v === \"string\") { // ok} else { throw \"type error\";} 由于是静态类型分析工具,因此 TS 并不会执行 JS 代码,但并不是说 TS 内部没有执行逻辑。 简单来总结一下就是: 值的集合就是类型,平时写代码基本都是对值编程,TS 提供了很多类型(也可以自定义)以及很多类型操作帮助我们限定值以及对值的操作。 什么是泛型上面已经铺垫了一番,大家已经知道了值和类型的区别,以及 TS 究竟帮我们做了什么事情。但是直接理解泛型仍然会比较吃力,接下来我会通过若干实例,慢慢带大家走进泛型。 首先来思考一个问题:为什么要有泛型呢?这个原因实际上有很多,在这里我选择大家普遍认同的一个切入点来解释。如果你明白了这个点,其他点相对而言理解起来会比较轻松。还是通过一个例子来进行说明。 不容小觑的 id 函数假如让你实现一个函数 id,函数的参数可以是任何值,返回值就是将参数原样返回,并且其只能接受一个参数,你会怎么做? 你会觉得这很简单,顺手就写出这样的代码: 1const id = (arg) => arg; 有的人可能觉得 id 函数没有什么实际作用。其实不然, id 函数在函数式编程中应用非常广泛。 由于其可以接受任意值,也就是说你的函数的入参和返回值都应该可以是任意类型。 现在让我们给代码增加类型声明: 1234type idBoolean = (arg: boolean) => boolean;type idNumber = (arg: number) => number;type idString = (arg: string) => string;... 一个笨的方法就像上面那样,也就是说 JS 提供多少种类型,就需要复制多少份代码,然后改下类型签名。这对程序员来说是致命的。这种复制粘贴增加了出错的概率,使得代码难以维护,牵一发而动全身。并且将来 JS 新增新的类型,你仍然需要修改代码,也就是说你的代码对修改开放,这样不好。还有一种方式是使用 any 这种“万能语法”。缺点是什么呢?我举个例子: 1234id(\"string\").length; // okid(\"string\").toFixed(2); // okid(null).toString(); // ok... 如果你使用 any 的话,怎么写都是 ok 的, 这就丧失了类型检查的效果。实际上我知道我传给你的是 string,返回来的也一定是 string,而 string 上没有 toFixed 方法,因此需要报错才是我想要的。也就是说我真正想要的效果是:当我用到id的时候,你根据我传给你的类型进行推导。比如我传入的是 string,但是使用了 number 上的方法,你就应该报错。 为了解决上面的这些问题,我们使用泛型对上面的代码进行重构。和我们的定义不同,这里用了一个 类型 T,这个 T 是一个抽象类型,只有在调用的时候才确定它的值,这就不用我们复制粘贴无数份代码了。 123function id<T>(arg: T): T { return arg;} 为什么这样就可以了? 为什么要用这种写法?这个尖括号什么鬼?万物必有因果,之所以这么设计泛型也是有原因的。那么就让我来给大家解释一下,相信很多人都没有从这个角度思考过这个问题。 泛型就是对类型编程上面提到了一个重要的点 平时我们都是对值进行编程,泛型是对类型进行编程。上面我没有给大家解释这句话。现在铺垫足够了,那就让我们开始吧! 继续举一个例子:假如我们定义了一个 Person 类,这个 Person 类有三个属性,并且都是必填的。这个 Person 类会被用于用户提交表单的时候限定表单数据。 12345678910enum Sex { Man, Woman, UnKnow,}interface Person { name: string; sex: Sex; age: number;} 突然有一天,公司运营想搞一个促销活动,也需要用到 Person 这个 shape,但是这三个属性都可以选填,同时要求用户必须填写手机号以便标记用户和接受短信。一个很笨的方法是重新写一个新的类: 123456interface MarketPerson { name?: string; sex?: Sex; age?: number; phone: string;} 还记得我开头讲的重复类型定义么? 这就是! 这明显不够优雅。如果 Person 字段很多呢?这种重复代码会异常多,不利于维护。 TS 的设计者当然不允许这么丑陋的设计存在。那么是否可以根据已有类型,生成新的类型呢?当然可以!答案就是前面我提到了两种对类型的操作:一种是集合操作,另一种是今天要讲的泛型。 先来看下集合操作: 1type MarketPerson = Person & { phone: string }; 这个时候我们虽然添加了一个必填字段 phone,但是没有做到name, sex, age 选填,似乎集合操作做不到这一点呀。我们脑洞一下,假如我们可以像操作函数那样操作类型,是不是有可能呢?比如我定义了一个函数 Partial,这个函数的功能入参是一个类型,返回值是新的类型,这个类型里的属性全部变成可选的。 伪代码: 12345678910function Partial(Type) { type ans = 空类型 for(k in Type) { 空类型[k] = makeOptional(Type, k) } return ans}type PartialedPerson = Partial(Person) 可惜的是上面代码不能运行,也不可能运行。不可能运行的原因有: 这里使用函数 Partial 操作类型,可以看出上面的函数我是没有添加签名的,我是故意的。如果让你给这个函数添加签名你怎么加?没办法加! 这里使用 JS 的语法对类型进行操作,这是不恰当的。首先这种操作依赖了 JS 运行时,而 TS 是静态分析工具,不应该依赖 JS 运行时。其次如果要支持这种操作是否意味者 TS 对 JS 妥协,JS 出了新的语法(比如早几年出的 async await),TS 都要支持其对 TS 进行操作。 因此迫切需要一种不依赖 JS 行为,特别是运行时行为的方式,并且逻辑其实和上面类似的,且不会和现有语法体系冲突的语法。 我们看下 TS 团队是怎么做的: 1234// 可以看成是上面的函数定义,可以接受任意类型。由于是这里的 “Type” 形参,因此理论上你叫什么名字都是无所谓的,就好像函数定义的形参一样。type Partial<Type> = { do something }// 可以看成是上面的函数调用,调用的时候传入了具体的类型 Persontype PartialedPerson = Partial<Person> 先不管功能,我们来看下这两种写法有多像: (定义) (运行) 再来看下上面泛型的功能。上面代码的意思是对 T 进行处理,是返回一个 T 的子集,具体来说就是将 T 的所有属性变成可选。这时 PartialedPerson 就等于 : 12345interface Person { name?: string; sex?: Sex; age?: number;} 功能和上面新建一个新的 interface 一样,但是更优雅。 最后来看下泛型 Partial 的具体实现,可以看出其没有直接使用 JS 的语法,而是自己定义了一套语法,比如这里的 keyof,至此完全应证了我上面的观点。 1type Partial<T> = { [P in keyof T]?: T[P] }; 刚才说了“由于是形参,因此起什么名字无所谓” 。因此这里就起了 T 而不是 Type,更短了。这也算是一种约定俗称的规范,大家一般习惯叫 T, U 等表示泛型的形参。 我们来看下完整的泛型和函数有多像! (定义) (使用) 从外表看只不过是 function 变成了 type,() 变成了 <>而已。 从语法规则上来看, 函数内部对标的是 ES 标准。而泛型对应的是 TS 实现的一套标准。 简单来说,将类型看成值,然后对类型进行编程,这就是泛型的基本思想。泛型类似我们平时使用的函数,只不过其是作用在类型上,思想上和我们平时使用的函数并没有什么太多不同,泛型产生的具体类型也支持类型的操作。比如: 1type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>; 有了上面的知识,我们通过几个例子来巩固一下。 123function id<T, U>(arg1: T, arg2: U): T { return arg1;} 上面定义了泛型 id,其入参分别是 T 和 U,和函数参数一样,使用逗号分隔。定义了形参就可以在函数体内使用形参了。如上我们在函数的参数列表和返回值中使用了形参 T 和 U。 返回值也可以是复杂类型: 123function ids<T, U>(arg1: T, arg2: U): [T, U] { return [arg1, arg2];} (泛型的形参) 和上面类似, 只不过返回值变成了数组而已。 需要注意的是,思想上我们可以这样去理解。但是具体的实现过程会有一些细微差别,比如: 1234type P = [number, string, boolean];type Q = Date;type R = [Q, ...P]; // A rest element type must be an array type. 再比如: 1234567type Lucifer = LeetCode;type LeetCode<T = {}> = { name: T;};const a: LeetCode<string>; //okconst a: Lucifer<string>; // Type 'Lucifer' is not generic. 改成这样是 ok 的: 1type Lucifer<T> = LeetCode<T>; 泛型为什么使用尖括号为什么泛型要用尖括号(<>),而不是别的? 我猜是因为它和 () 长得最像,且在现在的 JS 中不会有语法歧义。但是,它和 JSX 不兼容!比如: 1234567function Form() { // ... return ( <Select<string> options={targets} value={target} onChange={setTarget} /> );} 这是因为 TS 发明这个语法的时候,还没想过有 JSX 这种东西。后来 TS 团队在 TypeScript 2.9 版本修复了这个问题。也就是说现在你可以直接在 TS 中使用带有泛型参数的 JSX 啦(比如上面的代码)。 泛型的种类实际上除了上面讲到的函数泛型,还有接口泛型和类泛型。不过语法和含义基本同函数泛型一样: 1234interface id<T, U> { id1: T; id2: U;} (接口泛型) 123class MyComponent extends React.Component<Props, State> { ...} (类泛型) 总结下就是: 泛型的写法就是在标志符后面添加尖括号(<>),然后在尖括号里写形参,并在 body(函数体, 接口体或类体) 里用这些形参做一些逻辑处理。 泛型的参数类型 - “泛型约束”正如文章开头那样,我们可以对函数的参数进行限定。 1234function t(name: string) { return `hello, ${name}`;}t(\"lucifer\"); 如上代码对函数的形参进行了类型限定,使得函数仅可以接受 string 类型的值。那么泛型如何达到类似的效果呢? 1type MyType = (T: constrain) => { do something }; 还是以 id 函数为例,我们给 id 函数增加功能,使其不仅可以返回参数,还会打印出参数。熟悉函数式编程的人可能知道了,这就是 trace 函数,用于调试程序。 1234function trace<T>(arg: T): T { console.log(arg); return arg;} 假如我想打印出参数的 size 属性呢?如果完全不进行约束 TS 是会报错的: 注意:不同 TS 版本可能提示信息不完全一致,我的版本是 3.9.5。下文的所有测试结果均是使用该版本,不再赘述。 1234function trace<T>(arg: T): T { console.log(arg.size); // Error: Property 'size doesn't exist on type 'T' return arg;} 报错的原因在于 T 理论上是可以是任何类型的,不同于 any,你不管使用它的什么属性或者方法都会报错(除非这个属性和方法是所有集合共有的)。那么直观的想法是限定传给 trace 函数的参数类型应该有 size 类型,这样就不会报错了。如何去表达这个类型约束的点呢?实现这个需求的关键在于使用类型约束。 使用 extends 关键字可以做到这一点。简单来说就是你定义一个类型,然后让 T 实现这个接口即可。 1234567interface Sizeable { size: number;}function trace<T extends Sizeable>(arg: T): T { console.log(arg.size); return arg;} 这个时候 T 就不再是任意类型,而是被实现接口的 shape,当然你也可以继承多个接口。类型约束是非常常见的操作,大家一定要掌握。 有的人可能说我直接将 Trace 的参数限定为 Sizeable 类型可以么?如果你这么做,会有类型丢失的风险,详情可以参考这篇文章A use case for TypeScript Generics。 常见的泛型集合类大家平时写 TS 一定见过类似 Array<String> 这种写法吧? 这其实是集合类,也是一种泛型。 本质上数组就是一系列值的集合,这些值可以可以是任意类型,数组只是一个容器而已。然而平时开发的时候通常数组的项目类型都是相同的,如果不加约束的话会有很多问题。 比如我应该是一个字符串数组,然是却不小心用到了 number 的方法,这个时候类型系统应该帮我识别出这种类型问题。 由于数组理论可以存放任意类型,因此需要使用者动态决定你想存储的数据类型,并且这些类型只有在被调用的时候才能去确定。 Array<String> 就是调用,经过这个调用会产生一个具体集合,这个集合只能存放 string 类型的值。 不调用直接把 Array 是不被允许的: 1const a: Array = [\"1\"]; 如上代码会被错:Generic type 'Array<T>' requires 1 type argument(s).ts 。 有没有觉得和函数调用没传递参数报错很像?像就对了。 这个时候你再去看 Set, Promise,是不是很快就知道啥意思了?它们本质上都是包装类型,并且支持多种参数类型,因此可以用泛型来约束。 React.FC大家如果开发过 React 的 TS 应用,一定知道 React.FC 这个类型。我们来看下它是如何定义的: 123456789type FC<P = {}> = FunctionComponent<P>;interface FunctionComponent<P = {}> { (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null; propTypes?: WeakValidationMap<P>; contextTypes?: ValidationMap<any>; defaultProps?: Partial<P>; displayName?: string;} 可以看出其大量使用了泛型。你如果不懂泛型怎么看得懂呢?不管它多复杂,我们从头一点点分析就行,记住我刚才讲的类比方法,将泛型类比到函数进行理解。· 首先定义了一个泛型类型 FC,这个 FC 就是我们平时用的 React.FC。它是通过另外一个泛型 FunctionComponent 产生的。 因此,实际上第一行代码的作用就是起了一个别名 FunctionComponent 实际上是就是一个接口泛型,它定义了五个属性,其中四个是可选的,并且是静态类属性。 displayName 比较简单,而 propTypes,contextTypes,defaultProps 又是通过其他泛型生成的类型。我们仍然可以采用我的这个分析方法继续分析。由于篇幅原因,这里就不一一分析,读者可以看完我的分析过程之后,自己尝试分析一波。 (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null; 的含义是 FunctionComponent 是一个函数,接受两个参数(props 和 context )返回 ReactElement 或者 null。ReactElement 大家应该比较熟悉了。PropsWithChildren 实际上就是往 props 中插入 children,源码也很简单,代码如下: 1type PropsWithChildren<P> = P & { children?: ReactNode }; 这不就是我们上面讲的集合操作和 可选属性么?至此,React.FC 的全貌我们已经清楚了。读者可以试着分析别的源码检测下自己的学习效果,比如 React.useState 类型的签名。 类型推导与默认参数类型推导和默认参数是 TS 两个重要功能,其依然可以作用到泛型上,我们来看下。 类型推导我们一般常见的类型推导是这样的: 123const a = \"lucifer\"; // 我们没有给 a 声明类型, a 被推导为 stringa.toFixed(); // Property 'toFixed' does not exist on type 'string'.a.includes(\"1\"); // ok 需要注意的是,类型推导是仅仅在初始化的时候进行推导,如下是无法正确推导的: 12345let a = \"lucifer\"; // 我们没有给 a 声明类型, a 被推导为stringa.toFixed(); // Property 'toFixed' does not exist on type 'string'.a.includes(\"1\"); // oka = 1;a.toFixed(); // 依然报错, a 不会被推导 为 number 而泛型也支持类型推导,以上面的 id 函数为例: 12345function id<T>(arg: T): T { return arg;}id<string>(\"lucifer\"); // 这是ok的,也是最完整的写法id(\"lucifer\"); // 基于类型推导,我们可以这样简写 这也就是为什么 useState 有如下两种写法的原因。 12const [name, setName] = useState(\"lucifer\");const [name, setName] = useState<string>(\"lucifer\"); 实际的类型推导要更加复杂和智能。相信随着时间的推进,TS 的类型推导会更加智能。 默认参数和类型推导相同的点是,默认参数也可以减少代码量,让你少些代码。前提是你要懂,不然伴随你的永远是大大的问号。其实你完全可以将其类比到函数的默认参数来理解。 举个例子: 1234type A<T = string> = Array<T>;const aa: A = [1]; // type 'number' is not assignable to type 'string'.const bb: A = [\"1\"]; // okconst cc: A<number> = [1]; // ok 上面的 A 类型默认是 string 类型的数组。你可以不指定,等价于 Array,当然你也可以显式指定数组类型。有一点需要注意:在 JS 中,函数也是值的一种,因此: 1const fn = () => null; // ok 但是泛型这样是不行的,这是和函数不一样的地方(设计缺陷?Maybe): 1type A = Array; // error: Generic type 'Array<T>' requires 1 type argument(s). 其原因在与 Array 的定义是: 123interface Array<T> { ...} 而如果 Array 的类型也支持默认参数的话,比如: 123interface Array<T = string> { ...} 那么 type A = Array; 就是成立的,如果不指定的话,会默认为 string 类型。 什么时候用泛型如果你认真看完本文,相信应该知道什么时候使用泛型了,我这里简单总结一下。 当你的函数,接口或者类: 需要作用到很多类型的时候,比如我们介绍的 id 函数的泛型声明。 需要被用到很多地方的时候,比如我们介绍的 Partial 泛型。 进阶上面说了泛型和普通的函数有着很多相似的地方。普通的函数可以嵌套其他函数,甚至嵌套自己从而形成递归。泛型也是一样! 泛型支持函数嵌套比如: 1type CutTail<Tuple extends any[]> = Reverse<CutHead<Reverse<Tuple>>>; 如上代码中, Reverse 是将参数列表反转,CutHead 是将数组第一项切掉。因此 CutTail 的意思就是将传递进来的参数列表反转,切掉第一个参数,然后反转回来。换句话说就是切掉参数列表的最后一项。 比如,一个函数是 function fn (a: string, b: number, c: boolean):boolean {},那么经过操作type cutTailFn = CutTail<typeof fn>,可以返回(a: string, b:number) => boolean。 具体实现可以参考Typescript 复杂泛型实践:如何切掉函数参数表的最后一个参数?。 在这里,你知道泛型支持嵌套就够了。 泛型支持递归泛型甚至可以嵌套自己从而形成递归,比如我们最熟悉的单链表的定义就是递归的。 1234type ListNode<T> = { data: T; next: ListNode<T> | null;}; (单链表) 再比如 HTMLElement 的定义。 1234declare var HTMLElement: { prototype: HTMLElement; new(): HTMLElement;};。 (HTMLElement) 上面是递归声明,我们再来看一个更复杂一点的递归形式 - 递归调用,这个递归调用的功能是:递归地将类型中所有的属性都变成可选。类似于深拷贝那样,只不过这不是拷贝操作,而是变成可选,并且是作用在类型,而不是值。 1234567type DeepPartial<T> = T extends Function ? T : T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;type PartialedWindow = DeepPartial<Window>; // 现在window 上所有属性都变成了可选啦 TS 泛型工具及实现虽然泛型支持函数的嵌套,甚至递归,但是其语法能力肯定和 JS 没法比, 想要实现一个泛型功能真的不是一件容易的事情。这里提供几个例子,看完这几个例子,相信你至少可以达到比葫芦画瓢的水平。这样多看多练,慢慢水平就上来了。 截止目前(2020-06-21),TS 提供了 16 种工具类型。 (官方提供的工具类型) 除了官方的工具类型,还有一些社区的工具类型,比如type-fest,你可以直接用或者去看看源码看看高手是怎么玩类型的。 我挑选几个工具类,给大家讲一下实现原理。 Partial功能是将类型的属性变成可选。注意这是浅 Partial,DeepPartial 上面我讲过了,只要配合递归调用使用即可。 1type Partial<T> = { [P in keyof T]?: T[P] }; Required功能和Partial 相反,是将类型的属性变成必填, 这里的 -指的是去除。 -? 意思就是去除可选,也就是必填啦。 1type Required<T> = { [P in keyof T]-?: T[P] }; Mutable功能是将类型的属性变成可修改,这里的 -指的是去除。 -readonly 意思就是去除只读,也就是可修改啦。 123type Mutable<T> = { -readonly [P in keyof T]: T[P];}; Readonly功能和Mutable 相反,功能是将类型的属性变成只读, 在属性前面增加 readonly 意思会将其变成只读。 1type Readonly<T> = { readonly [P in keyof T]: T[P] }; ReturnType功能是用来得到一个函数的返回值类型。 12345type ReturnType<T extends (...args: any[]) => any> = T extends ( ...args: any[]) => infer R ? R : any; 下面的示例用 ReturnType 获取到 Func 的返回值类型为 string,所以,foo 也就只能被赋值为字符串了。 123type Func = (value: number) => string;const foo: ReturnType<Func> = \"1\"; 更多参考TS - es5.d.ts 这些泛型可以极大减少大家的冗余代码,大家可以在自己的项目中自定义一些工具类泛型。 Bonus - 接口智能提示最后介绍一个实用的小技巧。如下是一个接口的类型定义: 1234567891011interface Seal { name: string; url: string;}interface API { \"/user\": { name: string; age: number; phone: string }; \"/seals\": { seal: Seal[] };}const api = <URL extends keyof API>(url: URL): Promise<API[URL]> => { return fetch(url).then((res) => res.json());}; 我们通过泛型以及泛型约束,实现了智能提示的功能。使用效果: (接口名智能提示) (接口返回智能提示) 原理很简单,当你仅输入 api 的时候,其会将 API interface 下的所有 key 提示给你,当你输入某一个 key 的时候,其会根据 key 命中 interface 定义的类型,然后给予类型提示。 总结学习 Typescript 并不是一件简单的事情,尤其是没有其他语言背景的情况。而 TS 中最为困难的内容之一恐怕就是泛型了。 泛型和我们平时使用的函数是很像的,如果将两者进行横向对比,会很容易理解,很多函数的都关系可以迁移到泛型,比如函数嵌套,递归,默认参数等等。泛型是对类型进行编程,参数是类型,返回值是一个新的类型。我们甚至可以对泛型的参数进行约束,就类似于函数的类型约束。 最后通过几个高级的泛型用法以及若干使用的泛型工具类帮助大家理解和消化上面的知识。要知道真正的 TS 高手都是玩类型的,高手才不会满足于类型的交叉并操作。 泛型用的好确实可以极大减少代码量,提高代码维护性。如果用的太深入,也可能会团队成员面面相觑,一脸茫然。因此抽象层次一定要合理,不仅仅是泛型,整个软件工程都是如此。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"},{"name":"泛型","slug":"前端/TypeScript/泛型","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/泛型/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"},{"name":"泛型","slug":"泛型","permalink":"https://lucifer.ren/blog/tags/泛型/"}]},{"title":"immutablejs 是如何优化我们的代码的?","slug":"immutable-js","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-07T12:34:34.258Z","comments":true,"path":"2020/06/13/immutable-js/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/immutable-js/","excerpt":"前一段时间群里有小伙伴问 lucifer 我一个问题:”immutablejs 是什么?有什么用?“。我当时的回答是:immutablejs 就是 tree + sharing,解决了数据可变性带来的问题,并顺便优化了性能。今天给大家来详细解释一下这句话。","text":"前一段时间群里有小伙伴问 lucifer 我一个问题:”immutablejs 是什么?有什么用?“。我当时的回答是:immutablejs 就是 tree + sharing,解决了数据可变性带来的问题,并顺便优化了性能。今天给大家来详细解释一下这句话。 背景我们还是通过一个例子来进行说明。如下是几个普通地不能再普通的赋值语句: 123456789a = 1;b = 2;c = 3;d = { name: \"lucifer\", age: 17, location: \"西湖\",};e = [\"脑洞前端\", \"力扣加加\"]; 上面代码的内存结构大概是这样的: lucifer 小提示:可以看出,变量名( a,b,c,d,e )只是内存地址的别名而已 由于 d 和 e 的值是引用类型,数据长度不确定,因此实际上数据区域会指向堆上的一块区域。而 a,b,c 由于长度是编译时确定的,因此可以方便地在栈上存储。 lucifer 小提示:d 和 e 的数据长度不确定, 但指针的长度是确定的,因此可以在栈上存储指针,指针指向堆上内存即可。 实际开发我们经常会进行各种赋值操作,比如: 12345const ca = a;const cb = b;const cc = c;const cd = d;const ce = e; 经过上面的操作,此时的内存结构图: 可以看出,ca,cb,cc,cd,ce 的内存地址都变了,但是值都没变。原因在于变量名只是内存的别名而已,而赋值操作传递的是 value。 由于目前 JS 对象操作都是 mutable 的, 因此就有可能会发生这样的 “bug”: 123cd.name = \"azl397985856\";console.log(cd.name); // azl397985856console.log(d.name); // azl397985856 上面的 cd.name 原地修改了 cd 的 name 值,这会影响所有指向 ta 的引用。 比如有一个对象被三个指针引用,如果对象被修改了,那么三个指针都会有影响。 你可以把指针看成线程,对象看成进程资源,资源会被线程共享。 多指针就是多线程,当多个线程同时对一个对象进行读写操作就可能会有问题。 于是很多人的做法是 copy(shallow or deep)。这样多个指针的对象都是不同的,可以看成多进程。 接下来我们进行一次 copy 操作。 12345678const sa = a;const sb = b;const sc = c;const sd = { ...d };const se = [...e];// 有的人还觉得不过瘾const sxbk = JSON.parse(JSON.stringify(e)); 旁观者: 为啥你代码那么多 copy 啊?当事人: 我也不知道为啥要 copy 一下,不过这样做使我安心。 此时引用类型的 value 全部发生了变化,此时内存图是这样的: 上面的 ”bug“ 成功解决。 lucifer 小提示: 如果你使用的是 shallow copy, 其内层的对象 value 是不会变化的。如果此时你对内层对象进行诸如 a.b.c 的操作,也会有”bug“。 完整内存图: (看不清可以尝试放大) 问题如果是 shallow copy 还好, 因为你只 copy 一层,但是随着 key 的增加,性能下降还是比较明显的。 据测量: shallow copy 包含 1w 个 属性的对象大概要 10 ms。 deep copy 一个三层的 1w 个属性的对象大概要 50 ms。 而 immutablejs 可以帮助我们减少这种时间(和内存)开销,这个我们稍后会讲。 数据仅供参考,大家也可以用自己的项目测量一下。 由于普通项目很难达到这个量级,因此基本结论是:如果你的项目对象不会很大, 完全没必要考虑诸如 immutablejs 进行优化,直接手动 copy 实现 immutable 即可。 如果我的项目真的很大呢?那么你可以考虑使用 immutable 库来帮你。 immutablejs 是无数 immutable 库中的一个。我们来看下 immutablejs 是如何解决这个性能难题的。 immutablejs 是什么使用 immutablejs 提供的 API 操作数据,每一次操作都会返回一个新的引用,效果类似 deep copy,但是性能更好。 开头我说了,immutablejs 就是 tree + sharing,解决了数据可变带来的问题,并顺便提供了性能。 其中这里的 tree 就是类似 trie 的一棵树。如果对 trie 不熟悉的,可以看下我之前写的一篇前缀树专题。 immutablejs 就是通过树实现的结构共享。举个例子: 1const words = [\"lucif\", \"luck\"]; 我根据 words 构建了一个前缀树,节点不存储数据, 数据存储在路径上。其中头节点表示的是对象的引用地址。 这样我们就将两个单词 lucif 和 luck存到了树上: 现在我想要将 lucif 改成 lucie,普通的做法是完全 copy 一份,之后修改即可。 12newWords = [...words];newWords[1] = \"lucie\"; (注意这里整棵树都是新的,你看根节点的内存地址已经变了) 而所谓的状态共享是: (注意这里整棵树除了新增的一个节点, 其他都是旧的,你看根节点的内存地址没有变) 可以看出,我们只是增加了一个节点,并改变了一个指针而已,其他都没有变化,这就是所谓的结构共享。 还是有问题仔细观察会发现:使用我们的方法,会造成 words 和 newWords 引用相等(都是 1fe2ab),即 words === newWords。 因此我们需要沿着路径回溯到根节点,并修改沿路的所有节点(绿色部分)。在这个例子,我们仅仅少修改两个节点。但是随着树的节点增加,公共前缀也会随着增加,那时性能提升会很明显。 整个过程类似下面的动图所示: 这个过程非常类似线段树的更新区间信息的过程 取舍之间前面提到了 沿着路径回溯到根节点,并修改沿路的所有节点。由于树的总节点数是固定的,因此当树很高的时候,某一个节点的子节点数目会很少,节点的复用率会很低。想象一个极端的情况,树中所有的节点只有一个子节点,此时退化到链表,每次修改的时间复杂度为 O(P),其中 P 为其祖先节点的个数。如果此时修改的是叶子节点,那么 P 就等于 N,其中 N 为 树的节点总数。 树很矮的情况,树的子节点数目会增加,因此每次回溯需要修改的指针增加。如图是有四个子节点的情况,相比于上面的两个子节点,需要多创建两个指针。 想象一种极端的情况,树只有一层。还是将 lucif 改成 lucie。我们此时只能重新建立一个全新的 lucie 节点,无法利用已有节点,此时和 deep copy 相比没有一点优化。 因此合理选择树的叉数是一个难点,绝对不是简单的二叉树就行了。这个选择往往需要做很多实验才能得出一个相对合理的值。 ReactReact 和 Vue 最大的区别之一就是 React 更加 “immutable”。React 更倾向于数据不可变,而 Vue 则相反。如果你恰好两个框架都使用过,应该明白我的意思。 使用 immutable 的一个好处是未来的操作不会影响之前创建的对象。因此你可以很轻松地将应用的数据进行持久化,以便发送给后端做调试分析或者实现时光旅行(感谢可预测的单向数据流)。 结合 Redux 等状态管理框架,immutablejs 可以发挥更大的作用。这个时候,你的整个 state tree 应该是 immutablejs 对象,不需要使用普通的 JavaScript 对象,并且操作也需要使用 immutablejs 提供的 API 来进行。 并且由于有了 immutablejs,我们可以很方便的使用全等 === 判断。写 SCU 也方便多了。 SCU 是 shouldComponentUpdate 的缩写。 通过我的几年使用经验来看,使用类似 immutablejs 的库,会使得性能有不稳定的提升。并且由于多了一个库,调试成本或多或少有所增加,并且有一定的理解和上手成本。因此我的建议是技术咱先学着,如果项目确实需要使用,团队成员技术也可以 Cover的话,再接入也不迟,不可过早优化。 总结由于数据可变性,当多个指针指向同一个引用,其中一个指针修改了数据可能引发”不可思议“的效果。随着项目规模的增大,这种情况会更加普遍。并且由于未来的操作可能会修改之前创建的对象,因此无法获取中间某一时刻的状态,这样就缺少了中间的链路,很难进行调试 。数据不可变则是未来的操作不会影响之前创建的对象,这就减少了”不可思议“的现象,并且由于我们可以知道任何中间状态,因此调试也会变得轻松。 手动实现”数据不可变“可以应付大多数情况。在极端情况下,才会有性能问题。immutablejs 就是 tree + sharing,解决了数据可变带来的问题,并顺便优化了性能。它不但解决了手动 copy 的性能问题,而且可以在 $O(1)$ 的时间比较一个对象是否发生了变化。因此搭配 React 的 SCU 优化 React 应用会很香。 最后推荐我个人感觉不错的另外两个 immutable 库 seamless-immutable 和 Immer。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"React","slug":"React","permalink":"https://lucifer.ren/blog/categories/React/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"React","slug":"React","permalink":"https://lucifer.ren/blog/tags/React/"},{"name":"immutablejs","slug":"immutablejs","permalink":"https://lucifer.ren/blog/tags/immutablejs/"},{"name":"immutable","slug":"immutable","permalink":"https://lucifer.ren/blog/tags/immutable/"}]},{"title":"【LeetCode题解】1186.删除一次得到子数组最大和","slug":"leetcode-1186","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.895Z","comments":true,"path":"2020/06/13/leetcode-1186/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-1186/","excerpt":"LeetCode 1186 题,最大子数组的进阶版本。","text":"LeetCode 1186 题,最大子数组的进阶版本。 题目地址https://leetcode.com/problems/maximum-subarray-sum-with-one-deletion/ 题目描述12345678910111213141516171819202122232425262728293031给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。注意,删除一个元素后,子数组 不能为空。请看示例:示例 1:输入:arr = [1,-2,0,3]输出:4解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。示例 2:输入:arr = [1,-2,-2,3]输出:3解释:我们直接选出 [3],这就是最大和。示例 3:输入:arr = [-1,-1,-1,-1]输出:-1解释:最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。 我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。 提示:1 <= arr.length <= 10^5-10^4 <= arr[i] <= 10^4 思路暴力法符合知觉的做法是求出所有的情况,然后取出最大的。 我们只需要两层循环接口,外循环用于确定我们丢弃的元素,内循环用于计算 subArraySum。 12345678910111213141516class Solution: def maximumSum(self, arr: List[int]) -> int: res = arr[0] def maxSubSum(arr, skip): res = maxSub = float(\"-inf\") for i in range(len(arr)): if i == skip: continue maxSub = max(arr[i], maxSub + arr[i]) res = max(res, maxSub) return res# 这里循环到了len(arr)项,表示的是一个都不删除的情况 for i in range(len(arr) + 1): res = max(res, maxSubSum(arr, i)) return res 空间换时间上面的做法在 LC 上会 TLE, 因此我们需要换一种思路,既然超时了,我们是否可以从空间换时间的角度思考呢?我们可以分别从头尾遍历,建立两个 subArraySub 的数组 l 和 r。 其实这个不难想到,很多题目都用到了这个技巧。 具体做法: 一层遍历, 建立 l 数组,l[i]表示从左边开始的以 arr[i]结尾的 subArraySum 的最大值 一层遍历, 建立 r 数组,r[i]表示从右边开始的以 arr[i]结尾的 subArraySum 的最大值 一层遍历, 计算 l[i - 1] + r[i + 1] 的最大值 l[i - 1] + r[i + 1]的含义就是删除 arr[i]的子数组最大值 上面的这个步骤得到了删除一个的子数组最大值, 不删除的只需要在上面循环顺便计算一下即可 123456789101112131415161718class Solution: def maximumSum(self, arr: List[int]) -> int: n = len(arr) l = [arr[0]] * n r = [arr[n - 1]] * n if n == 1: return arr[0] res = arr[0] for i in range(1, n): l[i] = max(l[i - 1] + arr[i], arr[i]) res = max(res, l[i]) for i in range(n - 2, -1, -1): r[i] = max(r[i + 1] + arr[i], arr[i]) res = max(res, r[i]) for i in range(1, n - 1): res = max(res, l[i - 1] + r[i + 1]) return res 动态规划上面的算法虽然时间上有所改善,但是正如标题所说,空间复杂度是 O(n),有没有办法改进呢?答案是使用动态规划。 具体过程: 定义 max0,表示以 arr[i]结尾且一个都不漏的最大子数组和 定义 max1,表示以 arr[i]或者 arr[i - 1]结尾,可以漏一个的最大子数组和 遍历数组,更新 max1 和 max0(注意先更新 max1,因为 max1 用到了上一个 max0) 其中max1 = max(max1 + arr[i], max0), 即删除 arr[i - 1]或者删除 arr[i] 其中max0 = max(max0 + arr[i], arr[i]), 一个都不删除 12345678910111213141516171819202122232425262728## @lc app=leetcode.cn id=1186 lang=python3## [1186] 删除一次得到子数组最大和## @lc code=startclass Solution: def maximumSum(self, arr: List[int]) -> int: # DP max0 = arr[0] max1 = arr[0] res = arr[0] n = len(arr) if n == 1: return max0 for i in range(1, n): # 先更新max1,再更新max0,因为max1用到了上一个max0 max1 = max(max1 + arr[i], max0) max0 = max(max0 + arr[i], arr[i]) res = max(res, max0, max1) return res# @lc code=end 关键点解析 空间换时间 头尾双数组 动态规划 相关题目 42.trapping-rain-water","categories":[],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"阿里面试题:如何寻找「两个数组」的中位数?","slug":"leetcode-median","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.956Z","comments":true,"path":"2020/06/13/leetcode-median/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-median/","excerpt":"一个数组的中位数很容易求,那两个数组呢? ​","text":"一个数组的中位数很容易求,那两个数组呢? ​ 题目地址(4. 寻找两个正序数组的中位数)https://leetcode-cn.com/problems/median-of-two-sorted-arrays/ 题目描述1234567891011121314151617181920给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。你可以假设 nums1 和 nums2 不会同时为空。 示例 1:nums1 = [1, 3]nums2 = [2]则中位数是 2.0示例 2:nums1 = [1, 2]nums2 = [3, 4]则中位数是 (2 + 3)/2 = 2.5 前置知识 中位数 分治法 二分查找 公司 阿里 百度 腾讯 暴力法思路首先了解一下 Median 的概念,一个数组中 median 就是把数组分成左右等分的中位数。 如下图: 知道了概念,我们先来看下如何使用暴力法来解决。 试了一下,暴力解法也是可以被 Leetcode Accept 的。 暴力解主要是要 merge 两个排序的数组(A,B)成一个排序的数组。 用两个pointer(i,j),i 从数组A起始位置开始,即i=0开始,j 从数组B起始位置, 即j=0开始.一一比较 A[i] 和 B[j], 如果A[i] <= B[j], 则把A[i] 放入新的数组中,i 往后移一位,即 i+1. 如果A[i] > B[j], 则把B[j] 放入新的数组中,j 往后移一位,即 j+1. 重复步骤#1 和 #2,直到i移到A最后,或者j移到B最后。 如果j移动到B数组最后,那么直接把剩下的所有A依次放入新的数组中. 如果i移动到A数组最后,那么直接把剩下的所有B依次放入新的数组中. 整个过程类似归并排序的合并过程 Merge 的过程如下图。 时间复杂度和空间复杂度都是O(m+n), 不符合题中给出O(log(m+n))时间复杂度的要求。 代码代码支持: Java,JS: Java Code: 1234567891011121314151617181920212223242526272829303132333435class MedianTwoSortedArrayBruteForce { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int[] newArr = mergeTwoSortedArray(nums1, nums2); int n = newArr.length; if (n % 2 == 0) { // even return (double) (newArr[n / 2] + newArr[n / 2 - 1]) / 2; } else { // odd return (double) newArr[n / 2]; } } private int[] mergeTwoSortedArray(int[] nums1, int[] nums2) { int m = nums1.length; int n = nums2.length; int[] res = new int[m + n]; int i = 0; int j = 0; int idx = 0; while (i < m && j < n) { if (nums1[i] <= nums2[j]) { res[idx++] = nums1[i++]; } else { res[idx++] = nums2[j++]; } } while (i < m) { res[idx++] = nums1[i++]; } while (j < n) { res[idx++] = nums2[j++]; } return res; }} JS Code: 1234567891011121314151617181920212223242526272829/** * @param {number[]} nums1 * @param {number[]} nums2 * @return {number} */var findMedianSortedArrays = function (nums1, nums2) { // 归并排序 const merged = []; let i = 0; let j = 0; while (i < nums1.length && j < nums2.length) { if (nums1[i] < nums2[j]) { merged.push(nums1[i++]); } else { merged.push(nums2[j++]); } } while (i < nums1.length) { merged.push(nums1[i++]); } while (j < nums2.length) { merged.push(nums2[j++]); } const { length } = merged; return length % 2 === 1 ? merged[Math.floor(length / 2)] : (merged[length / 2] + merged[length / 2 - 1]) / 2;}; 复杂度分析 时间复杂度:$O(max(m, n))$ 空间复杂度:$O(m + n)$ 二分查找思路如果我们把上一种方法的最终结果拿出来单独看的话,不难发现最终结果就是 nums1 和 nums 两个数组交错形成的新数组,也就是说 nums1 和 nums2 的相对位置并不会发生变化,这是本题的关键信息之一。 为了方便描述,不妨假设最终分割后,数组 nums1 左侧部分是 A,数组 nums2 左侧部分是 B。由于题中给出的数组都是排好序的,在排好序的数组中查找很容易想到可以用二分查找(Binary Search)·, 这里对数组长度小的做二分以减少时间复杂度。对较小的数组做二分可行的原因在于如果一个数组的索引 i 确定了,那么另一个数组的索引位置 j 也是确定的,因为 (i+1) + (j+1) 等于 (m + n + 1) / 2,其中 m 是数组 A 的长度, n 是数组 B 的长度。具体来说,我们可以保证数组 A 和 数组 B 做 partition 之后,len(Aleft)+len(Bleft)=(m+n+1)/2 接下来需要特别注意四个指针:leftp1, rightp1, leftp2, rightp2,分别表示 A 数组分割点,A 数组分割点右侧数,B 数组分割点,B 数组分割点右侧数。不过这里有两个临界点需要特殊处理: 如果分割点左侧没有数,即分割点索引是 0,那么其左侧应该设置为无限小。 如果分割点右侧没有数,即分割点索引是数组长度-1,那么其左侧应该设置为无限大。 如果我们二分之后满足:leftp1 < rightp2 and leftp2 < rightp1,那么说明分割是正确的,直接返回max(leftp1, leftp2)+min(rightp1, rightp2) 即可。否则,说明分割无效,我们需要调整分割点。 如何调整呢?实际上只需要判断 leftp1 > rightp2 的大小关系即可。如果 leftp1 > rightp2,那么说明 leftp1 太大了,我们可以通过缩小上界来降低 leftp1,否则我们需要扩大下界。 核心代码: 1234if leftp1 > rightp2: hi = mid1 - 1else: lo = mid1 + 1 上面的调整上下界的代码是建立在对数组 nums1 进行二分的基础上的,如果我们对数组 nums2 进行二分,那么相应地需要改为: 1234if leftp2 > rightp1: hi = mid2 - 1else: lo = mid2 + 1 下面我们通过一个具体的例子来说明。 比如对数组 A 的做 partition 的位置是区间[0,m] 如图: 下图给出几种不同情况的例子(注意但左边或者右边没有元素的时候,左边用INF_MIN,右边用INF_MAX表示左右的元素: 下图给出具体做的 partition 解题的例子步骤, 这个算法关键在于: 要 partition 两个排好序的数组成左右两等份,partition 需要满足len(Aleft)+len(Bleft)=(m+n+1)/2 - m是数组A的长度, n是数组B的长度, 且 partition 后 A 左边最大(maxLeftA), A 右边最小(minRightA), B 左边最大(maxLeftB), B 右边最小(minRightB) 满足(maxLeftA <= minRightB && maxLeftB <= minRightA) 关键点分析 有序数组容易想到二分查找 对小的数组进行二分可降低时间复杂度 根据 leftp1,rightp2,leftp2 和 rightp1 的大小关系确定结束点和收缩方向 代码代码支持:JS,CPP, Python3, JS Code: 1234567891011121314151617181920212223242526272829303132333435/** * 二分解法 * @param {number[]} nums1 * @param {number[]} nums2 * @return {number} */var findMedianSortedArrays = function (nums1, nums2) { // make sure to do binary search for shorten array if (nums1.length > nums2.length) { [nums1, nums2] = [nums2, nums1]; } const m = nums1.length; const n = nums2.length; let low = 0; let high = m; while (low <= high) { const i = low + Math.floor((high - low) / 2); const j = Math.floor((m + n + 1) / 2) - i; const maxLeftA = i === 0 ? -Infinity : nums1[i - 1]; const minRightA = i === m ? Infinity : nums1[i]; const maxLeftB = j === 0 ? -Infinity : nums2[j - 1]; const minRightB = j === n ? Infinity : nums2[j]; if (maxLeftA <= minRightB && minRightA >= maxLeftB) { return (m + n) % 2 === 1 ? Math.max(maxLeftA, maxLeftB) : (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2; } else if (maxLeftA > minRightB) { high = i - 1; } else { low = low + 1; } }}; Java Code: 1234567891011121314151617181920212223242526272829303132333435363738394041class MedianSortedTwoArrayBinarySearch { public static double findMedianSortedArraysBinarySearch(int[] nums1, int[] nums2) { // do binary search for shorter length array, make sure time complexity log(min(m,n)). if (nums1.length > nums2.length) { return findMedianSortedArraysBinarySearch(nums2, nums1); } int m = nums1.length; int n = nums2.length; int lo = 0; int hi = m; while (lo <= hi) { // partition A position i int i = lo + (hi - lo) / 2; // partition B position j int j = (m + n + 1) / 2 - i; int maxLeftA = i == 0 ? Integer.MIN_VALUE : nums1[i - 1]; int minRightA = i == m ? Integer.MAX_VALUE : nums1[i]; int maxLeftB = j == 0 ? Integer.MIN_VALUE : nums2[j - 1]; int minRightB = j == n ? Integer.MAX_VALUE : nums2[j]; if (maxLeftA <= minRightB && maxLeftB <= minRightA) { // total length is even if ((m + n) % 2 == 0) { return (double) (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2; } else { // total length is odd return (double) Math.max(maxLeftA, maxLeftB); } } else if (maxLeftA > minRightB) { // binary search left half hi = i - 1; } else { // binary search right half lo = i + 1; } } return 0.0; }} CPP Code: 123456789101112131415161718class Solution {public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { if (nums1.size() > nums2.size()) swap(nums1, nums2); int M = nums1.size(), N = nums2.size(), L = 0, R = M, K = (M + N + 1) / 2; while (true) { int i = (L + R) / 2, j = K - i; if (i < M && nums2[j - 1] > nums1[i]) L = i + 1; else if (i > L && nums1[i - 1] > nums2[j]) R = i - 1; else { int maxLeft = max(i ? nums1[i - 1] : INT_MIN, j ? nums2[j - 1] : INT_MIN); if ((M + N) % 2) return maxLeft; int minRight = min(i == M ? INT_MAX : nums1[i], j == N ? INT_MAX : nums2[j]); return (maxLeft + minRight) / 2.0; } } }}; Python3 Code: 123456789101112131415161718192021222324252627282930313233class Solution: def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: N = len(nums1) M = len(nums2) if N > M: return self.findMedianSortedArrays(nums2, nums1) lo = 0 hi = N combined = N + M while lo <= hi: mid1 = lo + hi >> 1 mid2 = ((combined + 1) >> 1) - mid1 leftp1 = -float(\"inf\") if mid1 == 0 else nums1[mid1 - 1] rightp1 = float(\"inf\") if mid1 == N else nums1[mid1] leftp2 = -float(\"inf\") if mid2 == 0 else nums2[mid2 - 1] rightp2 = float(\"inf\") if mid2 == M else nums2[mid2] # Check if the partition is valid for the case of if leftp1 <= rightp2 and leftp2 <= rightp1: if combined % 2 == 0: return (max(leftp1, leftp2)+min(rightp1, rightp2)) / 2.0 return max(leftp1, leftp2) else: if leftp1 > rightp2: hi = mid1 - 1 else: lo = mid1 + 1 return -1 复杂度分析 时间复杂度:$O(log(min(m, n)))$ 空间复杂度:$O(log(min(m, n)))$ 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"二分法","slug":"算法/二分法","permalink":"https://lucifer.ren/blog/categories/算法/二分法/"}],"tags":[{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"中位数","slug":"中位数","permalink":"https://lucifer.ren/blog/tags/中位数/"},{"name":"二分法","slug":"二分法","permalink":"https://lucifer.ren/blog/tags/二分法/"}]},{"title":"【LeetCode日记】 1162. 地图分析","slug":"leetcode-island","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:50.002Z","comments":true,"path":"2020/06/13/leetcode-island/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-island/","excerpt":"LeetCode 上有很多小岛题,虽然官方没有这个标签, 但是在我这里都差不多。不管是思路还是套路都比较类似,大家可以结合起来练习。 200.number-of-islands 695.max-area-of-island ​","text":"LeetCode 上有很多小岛题,虽然官方没有这个标签, 但是在我这里都差不多。不管是思路还是套路都比较类似,大家可以结合起来练习。 200.number-of-islands 695.max-area-of-island ​ 原题地址:https://leetcode-cn.com/problems/as-far-from-land-as-possible/ 思路这里我们继续使用上面两道题的套路,即不用 visited,而是原地修改。由于这道题求解的是最远的距离,而距离我们可以使用 BFS 来做。算法: 对于每一个海洋,我们都向四周扩展,寻找最近的陆地,每次扩展 steps 加 1。 如果找到了陆地,我们返回 steps。 我们的目标就是所有 steps 中的最大值。 实际上面算法有很多重复计算,如图中间绿色的区域,向外扩展的时候,如果其周边四个海洋的距离已经计算出来了,那么没必要扩展到陆地。实际上只需要扩展到周边的四个海洋格子就好了,其距离陆地的最近距离就是 1 + 周边四个格子中到达陆地的最小距离。 我们考虑优化。 将所有陆地加入队列,而不是海洋。 陆地不断扩展到海洋,每扩展一次就 steps 加 1,直到无法扩展位置。 最终返回 steps 即可。 图解: 代码12345678910111213141516class Solution: def maxDistance(self, grid: List[List[int]]) -> int: n = len(grid) steps = -1 queue = [(i, j) for i in range(n) for j in range(n) if grid[i][j] == 1] if len(queue) == 0 or len(queue) == n ** 2: return steps while len(queue) > 0: for _ in range(len(queue)): x, y = queue.pop(0) for xi, yj in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]: if xi >= 0 and xi < n and yj >= 0 and yj < n and grid[xi][yj] == 0: queue.append((xi, yj)) grid[xi][yj] = -1 steps += 1 return steps 由于没有 early return,steps 其实会多算一次。 我们可以返回值减去 1,也可以 steps 初始化为-1。这里我选择是 steps 初始化为-1 _复杂度分析_ 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N ^ 2)$ 优化由于数组删除第一个元素(上面代码的 queue.pop(0))是$O(N)$的时间复杂度,我们可以使用 deque 优化,代码如下: 12345678910111213141516171819def maxDistance(self, grid: List[List[int]]) -> int: from collections import deque N = len(grid) steps = -1 q = deque([(i, j) for i in range(N) for j in range(N) if grid[i][j] == 1]) if len(q) == 0 or len(q) == N ** 2: return steps move = [(-1, 0), (1, 0), (0, -1), (0, 1)] while len(q) > 0: for _ in range(len(q)): x, y = q.popleft() for dx, dy in move: nx, ny = x + dx, y + dy if 0 <= nx < N and 0 <= ny < N and grid[nx][ny] == 0: q.append((nx, ny)) grid[nx][ny] = -1 steps += 1 return steps 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经接近 30K star 啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"BFS","slug":"算法/BFS","permalink":"https://lucifer.ren/blog/categories/算法/BFS/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"力扣加加闪亮登场~","slug":"leetcode-pp","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-07T12:34:55.832Z","comments":true,"path":"2020/06/13/leetcode-pp/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-pp/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。 ​ 12脑洞前端:你已经长大了,是时候自己写题解了。力扣加加:。。。 独立的原因是让公众号的定位更加清晰,喜欢我的题解的朋友可以关注力扣加加,喜欢我的前端架构剖析,徒手造框架的朋友可以关注脑洞前端。当然如果你同时关注两个,那我会感动到哭。 虽然是新的公众号,但是我的初心不变,依然是力求用清晰直白的方式还原解题过程,努力做西湖区最好的算法题解。最后感谢大家一路以来的支持,我一定不负众望,越做越好。 规划预计力扣加加会推出五个板块。 91 算法 通过在 91 天的集中训练,帮助大家摆脱困境,征服算法。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。我会在结束之后将讲义和题解放到公众号号。 (仓库部分内容) 算法题解就是对力扣中的题目进行讲解,尤其是经典的题目。并且尽量从多个角度思路,帮助大家纵向打开解题思路。目前差不多有几百的题解了。 专题讲解对于共性比较强的,我会抽离成专题形式,帮助大家横向打开解题思路。 (之后会陆续加入更多专题) 视频题解对于一些题目采用视频的方式可能更加直观方便,之后还会考虑直播。 刷题工具初步规划了两个小工具,之后出测试版本会第一时间告诉大家,如果你对开发小工具感兴趣,也可以点击公众号的联系我来和我取得联系。 (力扣加加刷题小助手) 悬赏令大家在日常生活或是在刷题过程中有时一定会产生一些疑问,百度谷歌翻来覆去的找也找不到心中所期待的答案,因此,大家可以通过力扣加加来将这些疑问提出来,不定期从大家的问题中挑选出比较经典的几个问题来供大家一起讨论,大致的问题类型如下: 日常生活中有个问题想用数据结构+算法解决,但苦于思路不清晰 问题本身比较纠结,网上众说纷纭,找不到明确的答案 脑洞问题等等 (比如这种问题) 被抽中的问题对应的朋友及问题回答出色的朋友可以获得准备的小礼物哦! 最后,每周我们会根据公众号后台的阅读量及分享次数最多的小伙伴给予现金奖励奖励哦~。分享最多 1 人 8.88 红包,阅读最多的 1 人 6.66 红包。\u001c 不是第一也没有关系,我们会从分享排名 2-10撒 名的小伙伴中随机抽取2个 8.88 元红包,阅读排名 2-10 名的小伙伴中随机抽取2个 6.88 元红包, 每周日下午,我们会对本周的数据进行统计,并随机抽取幸运的小伙伴,并对中奖结果在力扣加加公众号进行公布,中奖的小伙伴请主动联系我哦。领奖时间截止到每周日的 24:00。 关注我公众号点关注,不迷路。如果再给 ➕ 个星标就更棒啦! 关注加加,星标加加~ 官网 力扣加加官网","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"复个盘~","slug":"91algo-4-basic","date":"2020-06-10T16:00:00.000Z","updated":"2023-01-07T12:34:56.098Z","comments":true,"path":"2020/06/11/91algo-4-basic/","link":"","permalink":"https://lucifer.ren/blog/2020/06/11/91algo-4-basic/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 91 天学算法基础篇已经结束了,一共七个小节的内容,这部分是将来的基础,大家务必掌握好。 这里给各位做一个简单的小结,帮助大家理清思路,建立强大的知识体系。 知识快览基础篇我们对几种数据结构进行了讲解,他们分别是: 数组,栈,队列。分别讲了各种操作的复杂度,使用场景。 链表。分别讲了各种操作的复杂度和常见题型。 树。介绍了遍历树的几种方法,实际树的核心指标,这些都是高级算法的基础。 哈希表。注意介绍了哈希表的几种基本题型。 图。图的内容有点多,核心有单源最短距离和多源最短距离以及后面的联通域问题(并查集应用)。 除此之外,还讲了一些基础算法,他们分别是: 双指针。一种非常有用,非常常见的优化技巧。 枚举,模拟与递推(不是递归哦)。 高级的料理往往采用最朴素的烹饪方式。 这些都是后面学习专题篇和进阶篇的基础。万丈高楼平地起,不打好基础,后面的学习自然无从谈起。 知识梳理基础篇我给大家画了重点,并用了一句简单的话进行了描述,希望能帮助大家更好地记住它们。 知识点是有很多联系的。我希望大家学习知识的时候自己去整理一下知识网络,这样不仅理解起来更加顺畅,而且更不容易忘记。 比如针对基础篇的数组,链表和树,我就可以总结出如下知识图。 可踹看出知识之间其实是有关联的,都不是孤立存在的。掌握好知识的内在联系是学习任何知识的捷径。 通过这样的认真归纳,再辅以我们提供的每日一题相信你的算法技能一定会有所提升。另外我也会在讲义或者题解中给出一些类似的题目,如果你做的并不顺利,那么可以把类似题目都尝试一下。这其实和准备高考有点类似。 最后祝大家坚持下来, 91 天后遇见不一样的自己。 专题篇的第一篇《二分》也接近尾声。很多学员都可以拳打二分题目了,给你们点个赞。 专题篇,我来了! 想参与 91 天学算法的可以用浏览器(不要微信内打开)访问 https://leetcode-solution.cn/91?tab=agenda 查看时间和课程安排以及具体的报名方法哦。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"《丢鸡蛋问题》重制版来袭~","slug":"887.super-egg-drop","date":"2020-06-07T16:00:00.000Z","updated":"2023-01-05T12:24:49.488Z","comments":true,"path":"2020/06/08/887.super-egg-drop/","link":"","permalink":"https://lucifer.ren/blog/2020/06/08/887.super-egg-drop/","excerpt":"这是一道 LeetCode 难度为 Hard 的题目,很多大公司都会考,来看看。 ​","text":"这是一道 LeetCode 难度为 Hard 的题目,很多大公司都会考,来看看。 ​ 原题地址:https://leetcode-cn.com/problems/super-egg-drop/ 题目描述你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。 每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。 你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。 每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。 你的目标是确切地知道 F 的值是多少。 无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少? 示例 1: 输入:K = 1, N = 2输出:2解释:鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。如果它没碎,那么我们肯定知道 F = 2 。因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。示例 2: 输入:K = 2, N = 6输出:3示例 3: 输入:K = 3, N = 14输出:4 提示: 1 <= K <= 1001 <= N <= 10000 前置知识 递归 动态规划 思路本题也是 vivo 2020 年提前批的一个笔试题。时间一个小时,一共三道题,分别是本题,合并 k 个链表,以及种花问题。 这道题我在很早的时候做过,也写了题解。现在看来,思路没有讲清楚。没有讲当时的思考过程还原出来,导致大家看的不太明白。今天给大家带来的是 887.super-egg-drop 题解的重制版。思路更清晰,讲解更透彻,如果觉得有用,那就转发在看支持一下?OK,我们来看下这道题吧。 这道题乍一看很复杂,我们不妨从几个简单的例子入手,尝试打开思路。 假如有 2 个鸡蛋,6 层楼。 我们应该先从哪层楼开始扔呢?想了一会,没有什么好的办法。我们来考虑使用暴力的手段。 (图 1. 这种思路是不对的) 既然我不知道先从哪层楼开始扔是最优的,那我就依次模拟从第 1,第 2。。。第 6 层扔。每一层楼丢鸡蛋,都有两种可能,碎或者不碎。由于是最坏的情况,因此我们需要模拟两种情况,并取两种情况中的扔次数的较大值(较大值就是最坏情况)。 然后我们从六种扔法中选择最少次数的即可。 (图 2. 应该是这样的) 而每一次选择从第几层楼扔之后,剩下的问题似乎是一个规模变小的同样问题。嗯哼?递归? 为了方便描述,我将 f(i, j) 表示有 i 个鸡蛋, j 层楼,在最坏情况下,最少的次数。 伪代码: 123456def superEggDrop(K, N): ans = N # 暴力枚举从第 i 层开始扔 for i in range(1, N + 1): ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1)) return ans 如上代码: self.superEggDrop(K - 1, i - 1) 指的是鸡蛋破碎的情况,我们就只剩下 K - 1 个鸡蛋, 并且 i - 1 个楼层需要 check。 self.superEggDrop(K, N - i) + 1 指的是鸡蛋没有破碎的情况,我们仍然有 K 个鸡蛋, 并且剩下 N - i 个楼层需要 check。 接下来,我们增加两行递归的终止条件,这道题就完成了。 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: if K == 1: return N if N == 0 or N == 1: return N ans = N # 暴力枚举从第 i 层开始扔 for i in range(1, N + 1): ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1)) return ans 可是如何这就结束的话,这道题也不能是 hard,而且这道题是公认难度较大的 hard 之一。 上面的代码会 TLE,我们尝试使用记忆化递归来试一下,看能不能 AC。 1234567891011class Solution: @lru_cache() def superEggDrop(self, K: int, N: int) -> int: if K == 1: return N if N == 0 or N == 1: return N ans = N # 暴力枚举从第 i 层开始扔 for i in range(1, N + 1): ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1)) return ans 性能比刚才稍微好一点,但是还是很容易挂。 那只好 bottom-up(动态规划)啦。 (图 3) 我将上面的过程简写成如下形式: (图 4) 与其递归地进行这个过程,我们可以使用迭代的方式。 相比于上面的递归式,减少了栈开销。然而两者有着很多的相似之处。 如果说递归是用函数调用来模拟所有情况, 那么动态规划就是用表来模拟。我们知道所有的情况,无非就是 N 和 K 的所有组合,我们怎么去枚举 K 和 N 的所有组合? 当然是套两层循环啦! (图 5. 递归 vs 迭代) 如上,你将 dp[i][j] 看成 superEggDrop(i, j),是不是和递归是一摸一样? 来看下迭代的代码: 123456789101112class Solution: def superEggDrop(self, K: int, N: int) -> int: for i in range(K + 1): for j in range(N + 1): if i == 1: dp[i][j] = j if j == 1 or j == 0: dp[i][j] == j dp[i][j] = j for k in range(1, j + 1): dp[i][j] = min(dp[i][j], max(dp[i - 1][k - 1] + 1, dp[i][j - k] + 1)) return dp[K][N] 值得注意的是,在这里内外循环的顺序无关紧要,并且内外循坏的顺序对我们写代码来说复杂程度也是类似的,各位客官可以随意调整内外循环的顺序。比如这样也是可以的: 123456789101112131415class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] for i in range(N + 1): for j in range( K + 1): if j == 1: dp[i][j] = i if i == 1 or i == 0: dp[i][j] == i dp[i][j] = i for k in range(1, i + 1): dp[i][j] = min(dp[i][j], max(dp[k - 1][j - 1] + 1, dp[i - k][j] + 1)) return dp[N][K] dp = [[0] * (N + 1) for _ in range(K + 1)] 总结一下,上面的解题方法思路是: 然而这样还是不能 AC。这正是这道题困难的地方。 一道题目往往有不止一种状态转移方程,而不同的状态转移方程往往性能是不同的。 那么这道题有没有性能更好的其他的状态转移方程呢? 把思路逆转! 这是《逆转裁判》 中经典的台词, 主角在深处绝境的时候,会突然冒出这句话,从而逆转思维,寻求突破口。 我们这样来思考这个问题。 既然题目要求最少的扔的次数,假设有一个函数 f(k, i),他的功能是求出 k 个鸡蛋,扔 i 次所能检测的最高楼层。 我们只需要不断进行发问: ”f 函数啊 f 函数,我扔一次可以么?“, 也就是判断 f(k, 1) >= N 的返回值 ”f 函数啊 f 函数,我扔两次呢?“, 也就是判断 f(k, 2) >= N 的返回值 … ”f 函数啊 f 函数,我扔 m 次呢?“, 也就是判断 f(k, m) >= N 的返回值 我们只需要返回第一个返回值为 true 的 m 即可。 想到这里,我条件发射地想到了二分法。 聪明的小朋友们,你们觉得二分可以么?为什么?欢迎评论区留言讨论。 那么这个神奇的 f 函数怎么实现呢?其实很简单。 摔碎的情况,可以检测的最高楼层是f(m - 1, k - 1) + 1。因为碎了嘛,我们多检测了摔碎的这一层。 没有摔碎的情况,可以检测的最高楼层是f(m - 1, k)。因为没有碎,也就是说我们啥都没检测出来(对能检测的最高楼层无贡献)。 我们来看下代码: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: def f(m, k): if k == 0 or m == 0: return 0 return f(m - 1, k - 1) + 1 + f(m - 1, k) m = 0 while f(m, K) < N: m += 1 return m 上面的代码可以 AC。我们来顺手优化成迭代式。 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m 代码代码支持:JavaSCript,Python Python: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m JavaSCript: 12345678910111213var superEggDrop = function (K, N) { // 不选择dp[K][M]的原因是dp[M][K]可以简化操作 const dp = Array(N + 1) .fill(0) .map((_) => Array(K + 1).fill(0)); let m = 0; while (dp[m][K] < N) { m++; for (let k = 1; k <= K; ++k) dp[m][k] = dp[m - 1][k - 1] + 1 + dp[m - 1][k]; } return m;}; 复杂度分析 时间复杂度:$O(m * K)$,其中 m 为答案。 空间复杂度:$O(K * N)$ 对为什么用加法的同学有疑问的可以看我写的《对《丢鸡蛋问题》的一点补充》。 总结 对于困难,先举几个简单例子帮助你思考。 递归和迭代的关系,以及如何从容地在两者间穿梭。 如果你还不熟悉动态规划,可以先从递归做起。多画图,当你做多了题之后,就会越来越从容。 对于动态规划问题,往往有不止一种状态转移方程,而不同的状态转移方程往往性能是不同的。 友情提示: 大家不要为了这个题目高空抛物哦。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"【LeetCode日记】 874. 模拟行走机器人","slug":"874.walking-robot-simulation","date":"2020-06-03T16:00:00.000Z","updated":"2023-01-07T12:20:39.930Z","comments":true,"path":"2020/06/04/874.walking-robot-simulation/","link":"","permalink":"https://lucifer.ren/blog/2020/06/04/874.walking-robot-simulation/","excerpt":"这是一道 LeetCode 难度为 easy 的题目,没有高深的算法,有的只是套路,我们来看下。 ​","text":"这是一道 LeetCode 难度为 easy 的题目,没有高深的算法,有的只是套路,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/walking-robot-simulation/submissions/ 题目描述12345678910111213141516171819202122232425262728293031323334机器人在一个无限大小的网格上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令:-2:向左转 90 度-1:向右转 90 度1 <= x <= 9:向前移动 x 个单位长度在网格上有一些格子被视为障碍物。第 i 个障碍物位于网格点 (obstacles[i][0], obstacles[i][1])如果机器人试图走到障碍物上方,那么它将停留在障碍物的前一个网格方块上,但仍然可以继续该路线的其余部分。返回从原点到机器人的最大欧式距离的平方。 示例 1:输入: commands = [4,-1,3], obstacles = []输出: 25解释: 机器人将会到达 (3, 4)示例 2:输入: commands = [4,-1,4,-2,4], obstacles = [[2,4]]输出: 65解释: 机器人在左转走到 (1, 8) 之前将被困在 (1, 4) 处 提示:0 <= commands.length <= 100000 <= obstacles.length <= 10000-30000 <= obstacle[i][0] <= 30000-30000 <= obstacle[i][1] <= 30000答案保证小于 2 ^ 31 思路这道题之所以是简单难度,是因为其没有什么技巧。你只需要看懂题目描述,然后把题目描述转化为代码即可。 唯一需要注意的是查找障碍物的时候如果你采用的是线形查找会很慢,很可能会超时。 我实际测试了一下,确实会超时 一种方式是使用排序,然后二分查找,如果采用基于比较的排序算法,那么这种算法的瓶颈在于排序本身,也就是$O(NlogN)$。 另一种方式是使用集合,将 obstacles 放入集合,然后需要的时候进行查询,查询的时候的时间复杂度为$O(1)$。 这里我们采用第二种方式。 接下来我们来“翻译”一下题目。 由于机器人只能往前走。因此机器人往东西南北哪个方向走取决于它的朝向。 我们使用枚举来表示当前机器人的朝向。 题目只有两种方式改变朝向,一种是左转(-2),另一种是右转(-1)。 题目要求的是机器人在运动过程中距离原点的最大值,而不是最终位置距离原点的距离。 为了代码书写简单,我建立了一个直角坐标系。用机器人的朝向和 x 轴正方向的夹角度数来作为枚举值,并且这个度数是 0 <= deg < 360。我们不难知道,其实这个取值就是0, 90,180,270 四个值。那么当 0 度的时候,我们只需要不断地 x+1,90 度的时候我们不断地 y + 1 等等。 关键点解析 理解题意,这道题容易理解错题意,求解为最终位置距离原点的距离 建立坐标系 使用集合简化线形查找的时间复杂度。 代码代码支持: Python3 Python3 Code: 1234567891011121314151617181920212223242526272829303132333435class Solution: def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int: pos = [0, 0] deg = 90 ans = 0 obstaclesSet = set(map(tuple, obstacles)) for command in commands: if command == -1: deg = (deg + 270) % 360 elif command == -2: deg = (deg + 90) % 360 else: if deg == 0: i = 0 while i < command and not (pos[0] + 1, pos[1]) in obstaclesSet: pos[0] += 1 i += 1 if deg == 90: i = 0 while i < command and not (pos[0], pos[1] + 1) in obstaclesSet: pos[1] += 1 i += 1 if deg == 180: i = 0 while i < command and not (pos[0] - 1, pos[1]) in obstaclesSet: pos[0] -= 1 i += 1 if deg == 270: i = 0 while i < command and not (pos[0], pos[1] - 1) in obstaclesSet: pos[1] -= 1 i += 1 ans = max(ans, pos[0] ** 2 + pos[1] ** 2) return ans","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"刷题效率低?或许你就差这么一个插件","slug":"algo-chrome-extension","date":"2020-06-03T16:00:00.000Z","updated":"2023-01-07T12:20:39.937Z","comments":true,"path":"2020/06/04/algo-chrome-extension/","link":"","permalink":"https://lucifer.ren/blog/2020/06/04/algo-chrome-extension/","excerpt":"这两天我写了一个浏览器插件,这个插件的定位就是帮助大家提高刷题效率。 获取方式关注公众号《力扣加加》回复 刷题插件 即可。","text":"这两天我写了一个浏览器插件,这个插件的定位就是帮助大家提高刷题效率。 获取方式关注公众号《力扣加加》回复 刷题插件 即可。 功能介绍目前主要有四个部分组成: 前置知识。 如果你想刷这道题,你需要掌握的知识是什么?如果没掌握这个前置知识,你是刷不出来的。提醒你去复习什么东西。 关键点。有了前置知识,并不代表你就能想到,并不代表你就能做出来。有一些关键点你是要想到,不然要么就是做不出来,要么就是解法效率很低。 题解。 这里精选一些好的题解,帮助大家少走弯路。目前只放了我的题解,后续可能会陆续增加其他优秀题解。 代码。 大家可以直接复制进行调试。 计划中的功能: 国内哪个公司出过这道题, 这对于想进某一家公司的人很有用 可视化调试 复杂度分析小工具 具体做什么,等出来之后再和大家同步 如何使用 下载插件 用 chrome 浏览器,访问 chrome://extensions/ 点击 load unpackd,选择刚刚下载好的插件 现在去 leetcode 就随便找一个题看看吧。 PS: 对于收录的题,展示效果类似上面的截图。对于未收录的题,展示效果如下图 视频我这里录制了一个视频,关于这个插件的。https://www.bilibili.com/video/BV1UK4y1x7zj/ 如何贡献 关注公众号《力扣加加》,点击更多 - 联系我,添加我为好友,备注插件开发。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"扩展程序","slug":"扩展程序","permalink":"https://lucifer.ren/blog/tags/扩展程序/"},{"name":"Chrome","slug":"Chrome","permalink":"https://lucifer.ren/blog/tags/Chrome/"}]},{"title":"【异议!】第一期 《这个🦅题的复杂度怎么分析?》","slug":"over-fancy01","date":"2020-06-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.790Z","comments":true,"path":"2020/06/03/over-fancy01/","link":"","permalink":"https://lucifer.ren/blog/2020/06/03/over-fancy01/","excerpt":"力扣加加,努力做西湖区最好的算法题解。 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 \b 于是【异议!】系列应运而生。它是个什么东西呢? 我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,力扣加加 来回答你。 我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列第一篇。","text":"力扣加加,努力做西湖区最好的算法题解。 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 \b 于是【异议!】系列应运而生。它是个什么东西呢? 我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,力扣加加 来回答你。 我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列第一篇。 事情的起源昨天有人在我的力扣题解下留言,说我的时间复杂度解释有问题。思考再三,决定将这个问题抛出来大家一起讨论一下,我会在明天的公众号给大家公布参考答案。对于回答正确且点赞数最高的,我会送出 8.88 的现金红包,参与方式以及要求在文末。 其实这是一道前几天的力扣官方每日一题,我们先来看一下题目描述: 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。 示例: 输入: [2,1,5,6,2,3] 输出: 10 那么问题来了这个题目我给出了四个解法,其中前两个是超时的,后两个是可以 AC 的。 而后两个可以 AC 的有一个是单调栈的解法,这个单调栈的解法有两个精妙的地方。第一是哨兵元素的选取,第二是是用了一个栈而不是两个。 另外一个可以 AC 的解法,也就是今天我们要讨论的解法,这个解法使用了两个数组,相对于单调栈的复杂度,其常系数更大,但是其思路同样巧妙。 为了大家更好的理解这个解法,我这里贴一下它的未被优化版本。思路为: 暴力尝试所有可能的矩形。从中心向两边进行扩展。对于每一个 i,我们计算出其左边第一个高度小于它的索引 p,同样地,计算出右边第一个高度小于它的索引 q。那么以 i 为最低点能够构成的面积就是(q - p - 1) * heights[i]。 这种算法毫无疑问也是正确的。 假设 f(i) 表示求以 i 为最低点的情况下,所能形成的最大矩阵面积。那么原问题转化为max(f(0), f(1), f(2), ..., f(n - 1))。 具体算法如下: 我们使用 l 和 r 数组。l[i] 表示 左边第一个高度小于它的索引,r[i] 表示 右边第一个高度小于它的索引。 我们从前往后求出 l,再从后往前计算出 r。 再次遍历求出所有的可能面积,并取出最大的。 代码: 1234567891011121314151617class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j -= 1 l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j += 1 r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 其实 while 循环内部是没有必要一格一格移动的。 举例来说,对于数组[1,2,3,4,5],我们要建立 r 数组。我们从 4 开始,4 的右侧第一个小于它索引的是 n(表示不存在)。同样 3 的右侧第一个小于它索引的也是 n(表示不存在),以此类推。如果用上面的解法的话,我们每次都需要从当前位置遍历到尾部,时间复杂度为$O(N^2)$。 实际上,比如遍历到 2 的时候,我们拿 2 和前面的 3 比较,发现 3 比 2 大,并且我们之前计算出了比 3 大的右侧第一个小于它索引的是 n,也就是说我们可以直接移动到 n 继续搜索,因为这中间的都比 3 大,自然比 2 大了,没有比较的意义。 这样看来时间复杂度就被优化到了$O(N)$。 代码: 123456789101112131415161718class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j = l[j] l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j = r[j] r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 这位读者看到这里产生了一个疑问,这个疑问就是我开篇所讲的。我们来看下他是怎么说的。 这位读者提到交替的这种情况时间复杂度会退化到$O(N^2)$,那么实际情况真的是这样么? 悬赏大家对上面的优化后的算法复杂度是怎么看的?请留言告诉我! 需要注意的是: 我们所说的复杂度是渐进复杂度,也就是说是忽略常数项的。而这里我要求你带上常系数。 这里要求计算的是整个完整算法的复杂度。 请分别说出该算法在最差情况,最好情况下的复杂度。 参与方式:复制链接,并在浏览器打开,然后在里面评论即可。链接地址:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/84-zhu-zhuang-tu-zhong-zui-da-de-ju-xing-duo-chong/ 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解。","categories":[{"name":"异议!","slug":"异议!","permalink":"https://lucifer.ren/blog/categories/异议!/"}],"tags":[{"name":"异议!","slug":"异议!","permalink":"https://lucifer.ren/blog/tags/异议!/"}]},{"title":"《黑客与画家》摘抄","slug":"hacker-drawer","date":"2020-06-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.820Z","comments":true,"path":"2020/06/02/hacker-drawer/","link":"","permalink":"https://lucifer.ren/blog/2020/06/02/hacker-drawer/","excerpt":"","text":"14. 梦寐以求的编程语言让我们试着描述黑客心中梦寐以求的语言来为以上内容做个小结。 这种语言干净简练,具有最高层次的抽象和互动性,而且很容易装备,可以只用很少的代码就解决常见的问题。不管是什么程序,你真正要写的代码几乎都与你自己的特定设置有关,其他具有普遍性的问题都有现成的函数库可以调用。 这种语言的句法短到令人生疑。你输入的命令中,没有任何一个字是多余的,甚至用到 shift 键的机会也很少。 这种语言的抽象程度很高,甚至你可以快速写出一个程序的原型。然后,等到你开始优化的时候,它还提供一个真正出色的性能分析器,告诉你应该重点关注什么地方。你能让多重循环快得难以置信,并且在需要的地方还能直接嵌入字节码。 这种语言有大量优秀的范例可供学习,并且非常符合直觉,你只需要花几分钟阅读范例就能领会应该如何使用此种语言。你偶尔才需要查阅操作手册, 它很薄,里面关于限定条件和例外情况的警告寥寥无几。 这种语言内核很小,但很强大。各个函数库高度独立,并且和内核一样经过精心设计,它们都能很好地协同工作。语言的每个部分就像精密照相机的各种零件一样完美契合,不需要为了兼容性问题放弃或者保留某些功能。所有的函数库的源码都能很容易得到。这种语言能很轻松地与操作系统和其他语言开发的应用程序对话。 这种语言以层的方式构建。较高的抽象层透明地构建在较低的抽象层上。如果有需要的话,你可以直接使用较低的抽象层。 除了一些绝对必要隐藏的东西。这种语言的所有细节对使用者都是透明的。它提供的抽象能力只是为了方便你开发,而不是强迫你按照它的方式行事。事实上,它鼓励你参与它的设计,给你提供与语言创作者平等的权利。你能够对它的任何部分加以改变, 甚至包括它的语法。它尽可能让你自己定义的部分与它本身定义的部分处于同等地位,这种梦幻般的编程语言不仅开放源码,更开放自身的设计。","categories":[{"name":"书摘","slug":"书摘","permalink":"https://lucifer.ren/blog/categories/书摘/"}],"tags":[{"name":"书摘","slug":"书摘","permalink":"https://lucifer.ren/blog/tags/书摘/"}]},{"title":"【LeetCode日记】 312. 戳气球","slug":"312.burst-balloons","date":"2020-05-31T16:00:00.000Z","updated":"2023-01-05T12:24:49.391Z","comments":true,"path":"2020/06/01/312.burst-balloons/","link":"","permalink":"https://lucifer.ren/blog/2020/06/01/312.burst-balloons/","excerpt":"​","text":"​ 312. 戳气球 作者:dp 加加 这是一道比较难且巧妙的动态规划题目;这道题目并不适合初学者看,比较适合 dp 进阶选手研究。好了,废话不多说,直接上菜。 题目描述12345678910111213141516有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。求所能获得硬币的最大数量。说明:你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100示例:输入: [3,1,5,8]输出: 167解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 思路回溯法分析一下这道题,就是要截破所有的气球,获得硬币的最大数量,然后左右两边的气球相邻了。那就截呗,我的第一反应就是暴力,回溯法;但是肯定会超时,为什么呢?因为题目给的气球数量有点多,最多 500 个;500 的阶乘,会超时爆栈;但是我们依然写一下代码,找下突破口,小伙伴们千万不要看不起暴力,暴力是优化的突破口;如果小伙伴对回溯法不太熟悉,我建议你记住下面的模版,也可以看我之前写的文章,回溯法基本可以使用以下的模版写。回溯法省心省力,0 智商负担,懂的朋友都懂,QAQ。 代码12345678910111213141516171819202122232425var maxCoins = function (nums) { let res = Number.MIN_VALUE; backtrack(nums, 0); return res; // 回溯法,状态树很大 function backtrack(nums, score) { if (nums.length == 0) { res = Math.max(res, score); return; } for (let i = 0, n = nums.length; i < n; i++) { let point = (i - 1 < 0 ? 1 : nums[i - 1]) * nums[i] * (i + 1 >= n ? 1 : nums[i + 1]); let tempNums = [].concat(nums); // 做选择 在 nums 中删除元素 nums[i] nums.splice(i, 1); // 递归回溯 backtrack(nums, score + point); // 撤销选择 nums = [...tempNums]; } }}; 动态规划回溯法的缺点也很明显,复杂度很高,对应本题截气球;小伙伴们可以脑补一下执行过程的状态树,这里我偷个懒就不画了;通过仔细观察这个状态树,我们会发现这个状态树的【选择】上,会有一些重复的选择分支;很明显存在了重复子问题;自然我就想到了能不能用动态规划来解决; 判读能不能用动态规划解决,还有一个问题,就是必须存在最优子结构;什么意思呢?其实就是根据局部最优,推导出答案;假设我们截破第 k 个气球是最优策略的最后一步,和上一步有没有联系呢?根据题目意思,截破第 k 个,前一个和后一个就变成相邻的了,看似是会有联系,其实是没有的。因为截破第 k 个和 k-1 个是没有联系的,脑补一下回溯法的状态树就更加明确了; 既然用动态规划,那就老套路了,把动态规划的三个问题想清楚定义好;然后找出题目的【状态】和【选择】,然后根据【状态】枚举,枚举的过程中根据【选择】计算递推就能得到答案了。 那本题的【选择】是什么呢?就是截哪一个气球。那【状态】呢?就是题目给的气球数量。 定义状态 这里有个细节,就是题目说明有两个虚拟气球,nums[-1] = nums[n] = 1;如果当前截破的气球是最后一个或者第一个,前面/后面没有气球了,不能乘以 0,而是乘以 1。 定义状态的最关键两个点,往子问题(问题规模变小)想,最后一步最优策略是什么;我们假设最后截破的气球是 k,截破 k 获得最大数量的银币就是 nums[i] _ nums[k] _ nums[j] 再加上前面截破的最大数量和后面的最大数量,即:nums[i] _ nums[k] _ nums[j] + 前面最大数量 + 后面最大数量,就是答案。 而如果我们不考虑两个虚拟气球而直接定义状态,截到最后两个气球的时候又该怎么定义状态来避免和前面的产生联系呢?这两个虚拟气球就恰到好处了,太细节了;这也是本题的一个难点之一。 那我们可以这样来定义状态,dp[i][j] = x 表示,戳破气球 i 和气球 j 之间(开区间,不包括 i 和 j)的所有气球,可以获得的最大硬币数为 x。为什么开区间?因为不能和已经计算过的产生联系,我们这样定义之后,利用两个虚拟气球,截到最后两个气球的时候就完美的避开了所有状态的联系,太细节了。 状态转移方程 而对于 dp[i][j],i 和 j 之间会有很多气球,到底该截哪个先呢?我们直接设为 k,枚举选择最优的 k 就可以了。 所以,最终的状态转移方程为:dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[k] + nums[i] + nums[j]) 初始值和边界 由于我们利用了两个虚拟气球,边界就是气球数 n + 2 初始值,当 i == j 时,很明显两个之间没有气球,所有为 0; 如何枚举状态 因为我们最终要求的答案是 dp[0][n + 1],就是截破虚拟气球之间的所有气球获得的最大值; 当 i == j 时,i 和 j 之间是没有气球的,所以枚举的状态很明显是 dp table 的左上部分,也就是 j 大于 i,如下图所示,只给出一部分方便思考。 从上图可以看出,我们需要从下到上,从左到右进行遍历。 代码12345678910111213141516171819var maxCoins = function (nums) { let n = nums.length; // 添加两侧的虚拟气球 let points = [1, ...nums, 1]; let dp = Array.from(Array(n + 2), () => Array(n + 2).fill(0)); // 最后一行开始遍历,从下往上 for (let i = n; i >= 0; i--) { // 从左往右 for (let j = i + 1; j < n + 2; j++) { for (let k = i + 1; k < j; k++) { dp[i][j] = Math.max( dp[i][j], points[j] * points[k] * points[i] + dp[i][k] + dp[k][j] ); } } } return dp[0][n + 1];}; 总结简单的 dp 题目会直接告诉你怎么定义状态,告诉你怎么选择计算,你只需要根据套路判断一下能不能用 dp 解题即可,而判断能不能,往往暴力就是突破口。而困难点的 dp,我觉的都是细节问题了,要注意的细节太多了。感觉力扣加加,路西法大佬,把我领进了动态规划的大门,共勉。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"回溯","slug":"算法/回溯","permalink":"https://lucifer.ren/blog/categories/算法/回溯/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"【LeetCode日记】 101. 对称二叉树","slug":"101.symmetric-tree","date":"2020-05-30T16:00:00.000Z","updated":"2023-01-05T12:24:50.000Z","comments":true,"path":"2020/05/31/101.symmetric-tree/","link":"","permalink":"https://lucifer.ren/blog/2020/05/31/101.symmetric-tree/","excerpt":"​","text":"​ 题目地址(101. 对称二叉树)https://leetcode-cn.com/problems/symmetric-tree/ 题目描述12345678910111213141516171819202122232425给定一个二叉树,检查它是否是镜像对称的。 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \\ 2 2 / \\ / \\3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 1 / \\ 2 2 \\ \\ 3 3 进阶:你可以运用递归和迭代两种方法解决这个问题吗? 思路看到这题的时候,我的第一直觉是 DFS。然后我就想:如果左子树是镜像,并且右子树也是镜像,是不是就说明整体是镜像?。经过几秒的思考, 这显然是不对的,不符合题意。 很明显其中左子树中的节点会和右子树中的节点进行比较,我把比较的元素进行了颜色区分,方便大家看。 这里我的想法是:遍历每一个节点的时候,我都可以通过某种方法知道它对应的对称节点是谁。这样的话我直接比较两者是否一致就行了。 最初我的想法是两次遍历,第一次遍历的同时将遍历结果存储到哈希表中,然后第二次遍历去哈希表取。 这种方法可行,但是需要 N 的空间(N 为节点总数)。我想到如果两者可以同时进行遍历,是不是就省去了哈希表的开销。 如果不明白的话,我举个简单例子: 1给定一个数组,检查它是否是镜像对称的。例如,数组 [1,2,2,3,2,2,1] 是对称的。 如果用哈希表的话大概是: 1234567seen = dict()for i, num in enumerate(nums): seen[i] = numfor i, num in enumerate(nums): if seen[len(nums) - 1 - i] != num: return Falsereturn True 而同时遍历的话大概是这样的: 12345678l = 0r = len(nums) - 1while l < r: if nums[l] != nums[r]: return False l += 1 r -= 1return True 其实更像本题一点的话应该是从中间分别向两边扩展 😂 代码12345678910class Solution: def isSymmetric(self, root: TreeNode) -> bool: def dfs(root1, root2): if root1 == root2: return True if not root1 or not root2: return False if root1.val != root2.val: return False return dfs(root1.left, root2.right) and dfs(root1.right, root2.left) if not root: return True return dfs(root.left, root.right) _复杂度分析_ 时间复杂度:$O(N)$,其中 N 为节点数。 空间复杂度:递归的深度最高为节点数,因此空间复杂度是 $O(N)$,其中 N 为节点数。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"DFS","slug":"算法/DFS","permalink":"https://lucifer.ren/blog/categories/算法/DFS/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"倒计时两天~91算法,儿童节发车!","slug":"91algo-05-30","date":"2020-05-29T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/05/30/91algo-05-30/","link":"","permalink":"https://lucifer.ren/blog/2020/05/30/91algo-05-30/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 91 算法第一期,从2020-06-01 到 2020-08-30。 马上就要发车了哦,大家准备好了么?儿童节,我们来啦。如果还有没参与的小伙伴,想要参与的可以在下方更多部分寻找参与方式,目前支持 QQ 和微信加入。 简介共分为三篇,基础篇,进阶篇和专题篇。 让你: 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 第一阶段基础篇(30 天)。预计五个子栏目,每个子栏目 6 天。到时候发讲义给大家,题目的话天一道。 讲义的内容大概是我在下方讲义部分放出的链接那样哦。 规则大家的问题,打卡题目,讲义都在这里更新哦,冲鸭 🦆 。91 天见证更好的自己!不过要注意一周不打卡会被强制清退。 需要提前准备些什么? 数据结构与算法的基础知识。 推荐看一下大学里面的教材讲义,或者看一些入门的图书,视频等,比如《图解算法》,邓俊辉的《数据结构与算法》免费视频课程。总之, 至少你要知道有哪些常见的数据结构与算法以及他们各自的特点。 有 Github 账号,且会使用 Github 常用操作。 比如提 issue,留言等。 有 LeetCode 账号,且会用其提交代码。 语言不限,大家可以用自己喜欢的任何语言。同时我也希望你不要纠结于语言本身。 具体形式是什么样的? 总共三个大的阶段 每个大阶段划分为几个小阶段 每个小阶段前会将这个小阶段的资料发到群里 每个小阶段的时间内,每天都会出关于这个阶段的题目,第二天进行解答 比如: 第一个大阶段是基础 基础中第一个小阶段是数组,栈和队列。 数组,栈和队列正式开始前,会将资料发到群里,大家可以提前预习。 之后的每天都会围绕数组,栈和队列出一道题,第二天进行解答。大家可以在出题当天上 Github 上打卡。 大家遇到问题可以在群里回答,对于比较好的问题,会记录到 github issue 中,让更多的人看到。Github 仓库地址届时会在群里公布。 奖励对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书等连续打卡七天可以获得补签卡一张哦 讲义 【91 算法-基础篇】05.双指针 课程列表 回炉重铸, 91 天见证不一样的自己 更多 如何加入微信群?","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"【91算法-基础篇】05.双指针","slug":"91algo-basic-05.two-pointer","date":"2020-05-25T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/05/26/91algo-basic-05.two-pointer/","link":"","permalink":"https://lucifer.ren/blog/2020/05/26/91algo-basic-05.two-pointer/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 什么是双指针顾名思议,双指针就是两个指针,但是不同于 C,C++中的指针, 其是一种算法思想。 如果说,我们迭代一个数组,并输出数组每一项,我们需要一个指针来记录当前遍历的项,这个过程我们叫单指针(index)的话。 123for(int i = 0;i < nums.size(); i++) { 输出(nums[i]);} (图 1) 那么双指针实际上就是有两个这样的指针,最为经典的就是二分法中的左右双指针啦。 12345678910int l = 0;int r = nums.size() - 1;while (l < r) { if(一定条件) return 合适的值,一般是 l 和 r 的中点 if(一定条件) l++ if(一定条件) r--}// 因为 l == r,因此返回 l 和 r 都是一样的return l (图 2) 读到这里,你发现双指针是一个很宽泛的概念,就好像数组,链表一样,其类型会有很多很多, 比如二分法经常用到左右端点双指针。滑动窗口会用到快慢指针和固定间距指针。 因此双指针其实是一种综合性很强的类型,类似于数组,栈等。 但是我们这里所讲述的双指针,往往指的是某几种类型的双指针,而不是“只要有两个指针就是双指针了”。 有了这样一个算法框架,或者算法思维,有很大的好处。它能帮助你理清思路,当你碰到新的问题,在脑海里进行搜索的时候,双指针这个词就会在你脑海里闪过,闪过的同时你可以根据双指针的所有套路和这道题进行穷举匹配,这个思考解题过程本来就像是算法,我会在进阶篇《搜索算法》中详细阐述。 那么究竟我们算法中提到的双指针指的是什么呢?我们一起来看下算法中双指针的常见题型吧。 常见题型有哪些?这里我将其分为三种类类型,分别是: 快慢指针(两个指针步长不同) 左右端点指针(两个指针分别指向头尾,并往中间移动,步长不确定) 固定间距指针(两个指针间距相同,步长相同) 上面是我自己的分类,没有参考别人。可以发现我的分类标准已经覆盖了几乎所有常见的情况。 大家在平时做题的时候一定要养成这样的习惯,将题目类型进行总结,当然这个总结可以是别人总结好的,也可以是自己独立总结的。不管是哪一种,都要进行一定的消化吸收,把它们变成真正属于自己的知识。 不管是哪一种双指针,只考虑双指针部分的话 ,由于最多还是会遍历整个数组一次,因此时间复杂度取决于步长,如果步长是 1,2 这种常数的话,那么时间复杂度就是 O(N),如果步长是和数据规模有关(比如二分法),其时间复杂度就是 O(logN)。并且由于不管规模多大,我们都只需要最多两个指针,因此空间复杂度是 O(1)。下面我们就来看看双指针的常见套路有哪些。 常见套路快慢指针 判断链表是否有环 这里给大家推荐两个非常经典的题目,一个是力扣 287 题,一个是 142 题。其中 142 题我在我的 LeetCode 题解仓库中的每日一题板块出过,并且给了很详细的证明和解答。而 287 题相对不直观,比较难以想到,这道题曾被官方选定为每日一题,也是相当经典的。 287. 寻找重复数 【每日一题】- 2020-01-14 - 142. 环形链表 II · Issue #274 · azl397985856/leetcode 读写指针。典型的是删除重复元素 这里推荐我仓库中的一道题, 我这里写了一个题解,横向对比了几个相似题目,并剖析了这种题目的本质是什么,让你看透题目本质,推荐阅读。 80.删除排序数组中的重复项 II 左右端点指针 二分查找。 二分查找会在专题篇展开,这里不多说,大家先知道就行了。 暴力枚举中“从大到小枚举”(剪枝) 一个典型的题目是我之前参加官方每日一题的时候给的一个解法,大家可以看下。这种解法是可以 AC 的。同样地,这道题我也给出了三种方法,帮助大家从多个纬度看清这个题目。强烈推荐大家做到一题多解。这对于你做题很多帮助。除了一题多解,还有一个大招是多题同解,这部分我们放在专题篇介绍。 find-the-longest-substring-containing-vowels-in-even 有序数组。 区别于上面的二分查找,这种算法指针移动是连续的,而不是跳跃性的,典型的是 LeetCode 的两数和,以及N数和系列问题。 固定间距指针 一次遍历(One Pass)求链表的中点 一次遍历(One Pass)求链表的倒数第 k 个元素 固定窗口大小的滑动窗口 模板(伪代码)我们来看下上面三种题目的算法框架是什么样的。这个时候我们没必要纠结具体的语言,这里我直接使用了伪代码,就是防止你掉进细节。 当你掌握了这种算法的细节,就应该找几个题目试试。一方面是检测自己是否真的掌握了,另一方面是“细节”,”细节“是人类,尤其是软件工程师最大的敌人,毕竟我们都是差不多先生。 快慢指针 1234567l = 0r = 0while 没有遍历完 if 一定条件 l += 1 r += 1return 合适的值 左右端点指针 12345678910l = 0r = n - 1while l < r if 找到了 return 找到的值 if 一定条件1 l += 1 else if 一定条件2 r -= 1return 没找到 固定间距指针 1234567l = 0r = kwhile 没有遍历完 自定义逻辑 l += 1 r += 1return 合适的值 题目推荐如果你差不多理解了上面的东西,那么可以拿下面的题练练手。Let’s Go! 左右端点指针 16.3Sum Closest (Medium) 713.Subarray Product Less Than K (Medium) 977.Squares of a Sorted Array (Easy) Dutch National Flag Problem 下面是二分类型 33.Search in Rotated Sorted Array (Medium) 875.Koko Eating Bananas(Medium) 881.Boats to Save People(Medium) 快慢指针 26.Remove Duplicates from Sorted Array(Easy) 141.Linked List Cycle (Easy) 142.Linked List Cycle II(Medium) 287.Find the Duplicate Number(Medium) 202.Happy Number (Easy) 固定间距指针 1456.Maximum Number of Vowels in a Substring of Given Length(Medium) 固定窗口大小的滑动窗口见专题篇的滑动窗口专题(暂未发布) 其他有时候也不能太思维定式,比如 https://leetcode-cn.com/problems/consecutive-characters/ 这道题根本就没必要双指针什么的。 再比如:https://lucifer.ren/blog/2020/05/31/101.symmetric-tree/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"【LeetCode日记】 343. 整数拆分","slug":"343.integer-break","date":"2020-05-15T16:00:00.000Z","updated":"2023-01-07T12:20:39.929Z","comments":true,"path":"2020/05/16/343.integer-break/","link":"","permalink":"https://lucifer.ren/blog/2020/05/16/343.integer-break/","excerpt":"希望通过这篇题解让大家知道“题解区的水有多深”,让大家知道“什么才是好的题解”。 ​","text":"希望通过这篇题解让大家知道“题解区的水有多深”,让大家知道“什么才是好的题解”。 ​ 原题地址: https://leetcode-cn.com/problems/integer-break/ 题目描述给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 示例 1: 输入: 2输出: 1解释: 2 = 1 + 1, 1 × 1 = 1。示例 2: 输入: 10输出: 36解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。说明: 你可以假设 n 不小于 2 且不大于 58。 思路我看了很多人的题解直接就是两句话,然后跟上代码: 1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] 这种题解说实话,只针对那些”自己会, 然后去题解区看看有没有新的更好的解法的人“。但是大多数看题解的人是那种自己没思路,不会做的人。那么这种题解就没什么用了。 我认为好的题解应该是新手友好的,并且能够将解题人思路完整展现的题解。比如看到这个题目,我首先想到了什么(对错没有关系),然后头脑中经过怎么样的筛选将算法筛选到具体某一个或某几个。我的最终算法是如何想到的,有没有一些先行知识。 当然我也承认自己有很多题解也是直接给的答案,这对很多人来说用处不大,甚至有可能有反作用,给他们一种”我已经会了“的假象。实际上他们根本不懂解题人本身原本的想法, 也许是写题解的人觉得”这很自然“,也可能”只是为了秀技“。 Ok,下面来讲下我是如何解这道题的。 抽象首先看到这道题,自然而然地先对问题进行抽象,这种抽象能力是必须的。LeetCode 实际上有很多这种穿着华丽外表的题,当你把这个衣服扒开的时候,会发现都是差不多的,甚至两个是一样的,这样的例子实际上有很多。 就本题来说,就有一个剑指 Offer 的原题《剪绳子》和其本质一样,只是换了描述方式。类似的有力扣 137 和 645 等等,大家可以自己去归纳总结。 137 和 645 我贴个之前写的题解 https://leetcode-cn.com/problems/single-number/solution/zhi-chu-xian-yi-ci-de-shu-xi-lie-wei-yun-suan-by-3/ 培养自己抽象问题的能力,不管是在算法上还是工程上。 务必记住这句话! 数学是一门非常抽象的学科,同时也很方便我们抽象问题。为了显得我的题解比较高级,引入一些你们看不懂的数学符号也是很有必要的(开玩笑,没有什么高级数学符号啦)。 实际上这道题可以用纯数学角度来解,但是我相信大多数人并不想看。即使你看了,大多人的感受也是“好 nb,然而并没有什么用”。 这道题抽象一下就是: 令:(图 1)求:(图 2) 第一直觉经过上面的抽象,我的第一直觉这可能是一个数学题,我回想了下数学知识,然后用数学法 AC 了。 数学就是这么简单平凡且枯燥。 然而如果没有数学的加持的情况下,我继续思考怎么做。我想是否可以枚举所有的情况(如图 1),然后对其求最大值(如图 2)。 问题转化为如何枚举所有的情况。经过了几秒钟的思考,我发现这是一个很明显的递归问题。 具体思考过程如下: 我们将原问题抽象为 f(n) 那么 f(n) 等价于 max(1 * fn(n - 1), 2 * f(n - 2), …, (n - 1) * f(1))。 用数学公式表示就是: (图 3) 截止目前,是一点点数学 + 一点点递归,我们继续往下看。现在问题是不是就很简单啦?直接翻译图三为代码即可,我们来看下这个时候的代码: 1234567class Solution: def integerBreak(self, n: int) -> int: if n == 2: return 1 res = 0 for i in range(1, n): res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) return res 毫无疑问,超时了。原因很简单,就是算法中包含了太多的重复计算。如果经常看我的题解的话,这句话应该不陌生。我随便截一个我之前讲过这个知识点的图。 (图 4) 原文链接:https://github.com/azl397985856/leetcode/blob/master/thinkings/dynamic-programming.md 大家可以尝试自己画图理解一下。 看到这里,有没有种殊途同归的感觉呢? 考虑优化如上,我们可以考虑使用记忆化递归的方式来解决。只是用一个 hashtable 存储计算过的值即可。 12345678class Solution: @lru_cache() def integerBreak(self, n: int) -> int: if n == 2: return 1 res = 0 for i in range(1, n): res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) return res 为了简单起见(偷懒起见),我直接用了 lru_cache 注解, 上面的代码是可以 AC 的。 动态规划看到这里的同学应该发现了,这个套路是不是很熟悉?下一步就是将其改造成动态规划了。 如图 4,我们的思考方式是从顶向下,这符合人们思考问题的方式。将其改造成如下图的自底向上方式就是动态规划。 (图 5) 现在再来看下文章开头的代码: 1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] dp table 存储的是图 3 中 f(n)的值。一个自然的想法是令 dp[i] 等价于 f(i)。而由于上面分析了原问题等价于 f(n),那么很自然的原问题也等价于 dp[n]。 而 dp[i]等价于 f(i),那么上面针对 f(i) 写的递归公式对 dp[i] 也是适用的,我们拿来试试。 12// 关键语句res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) 翻译过来就是: 1dp[i] = max(dp[i], max(i * dp(n - i),i * (n - i))) 而这里的 n 是什么呢?我们说了dp是自底向下的思考方式,那么在达到 n 之前是看不到整体的n 的。因此这里的 n 实际上是 1,2,3,4… n。 自然地,我们用一层循环来生成上面一系列的 n 值。接着我们还要生成一系列的 i 值,注意到 n - i 是要大于 0 的,因此 i 只需要循环到 n - 1 即可。 思考到这里,我相信上面的代码真的是不难得出了。 关键点 数学抽象 递归分析 记忆化递归 动态规划 代码1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] 总结培养自己的解题思维很重要, 不要直接看别人的答案。而是要将别人的东西变成自己的, 而要做到这一点,你就要知道“他们是怎么想到的”,“想到这点是不是有什么前置知识”,“类似题目有哪些”。 最优解通常不是一下子就想到了,这需要你在不那么优的解上摔了很多次跟头之后才能记住的。因此在你没有掌握之前,不要直接去看最优解。 在你掌握了之后,我不仅鼓励你去写最优解,还鼓励去一题多解,从多个解决思考问题。 到了那个时候, 萌新也会惊讶地呼喊“哇塞, 这题还可以这么解啊?”。 你也会低调地发出“害,解题就是这么简单平凡且枯燥。”的声音。 扩展正如我开头所说,这种套路实在是太常见了。希望大家能够识别这种问题的本质,彻底掌握这种套路。另外我对这个套路也在我的新书《LeetCode 题解》中做了介绍,本书目前刚完成草稿的编写,如果你想要第一时间获取到我们的题解新书,那么请发送邮件到 azl397985856@gmail.com,标题著明“书籍《LeetCode 题解》预定”字样。。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/categories/Medium/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/tags/Medium/"}]},{"title":"为什么eslint没有 no-magic-string?","slug":"why-no-magic-string","date":"2020-05-04T16:00:00.000Z","updated":"2023-01-05T12:24:49.978Z","comments":true,"path":"2020/05/05/why-no-magic-string/","link":"","permalink":"https://lucifer.ren/blog/2020/05/05/why-no-magic-string/","excerpt":"最近参加了几次公司组内的 Code Review, 发现了一些问题。其中一些问题可以通过工具(比如 eslint)解决。 我们就想着通过工具自动化的方式进行解决。 而这些工具中有一些是现成的,比如 魔法数。 大家对魔法数的看法也是莫衷一是。本文通过讲解什么是魔法数,eslint 是怎么检查魔法数的,以及思考为什么eslint 偏爱数字,而不是偏爱字符串来 来深入剖析一下魔法数。","text":"最近参加了几次公司组内的 Code Review, 发现了一些问题。其中一些问题可以通过工具(比如 eslint)解决。 我们就想着通过工具自动化的方式进行解决。 而这些工具中有一些是现成的,比如 魔法数。 大家对魔法数的看法也是莫衷一是。本文通过讲解什么是魔法数,eslint 是怎么检查魔法数的,以及思考为什么eslint 偏爱数字,而不是偏爱字符串来 来深入剖析一下魔法数。 计算机科学中的魔法数什么是魔法数? 这里我摘取了维基百科的解释: 程序设计中所谓的魔术数字(magic number)可能有以下含意: • 缺乏解释或命名的独特数值 • 用于识别一个文件格式或协议的一段常量或字符串,例如UNIX的特征签名 • 防止误作他用的一段数值,例如UUID 实际上魔法数真的是一个很古老的话题了。从我刚从业开始,就不停听到这样的词语。大家都对此深恶痛绝,不想看到别人的魔法数,但是时不时自己也会写一些魔法数。 如果你经常做算法题,肯定对此深有感受。 很多人(或许包括我自己)喜欢通过魔法数来炫技。当然我的心理倒不仅仅是炫技这么简单,也掺杂了诸如“这个很显然易见”,“这个我也不知道怎么起名字”的想法。 eslint 中的魔法数eslint 有一个 rule 是 no-magic-number. 为什么没有类似的比如 no-magic-string? 我们首先来看下什么是”no-magic-number”。 根据 eslint 官方描述来看,其是通过确保数字被显式赋予一个常量,从而增加代码可读性和可维护性。 如下代码被认为是不好的: 123/*eslint no-magic-numbers: “error”*/var dutyFreePrice = 100, finalPrice = dutyFreePrice + dutyFreePrice * 0.25; 而这段代码被认为是好的: 12345/*eslint no-magic-numbers: “error”*/var TAX = 0.25;var dutyFreePrice = 100, finalPrice = dutyFreePrice + dutyFreePrice * TAX; 这两段代码有什么不同?为什么下面的代码被认为是好的? 一窥 eslint 源码通过阅读源码,我发现代码有这样一句: 1234567891011// utils/ast-utils.jsfunction isNumericLiteral(node) { return ( node.type === \"Literal\" && (typeof node.value === \"number\" || Boolean(node.bigint)) );}// no-magic-numbers.jsif (!isNumericLiteral(node)) { return;} 也就是说如果字面量不是数字会被忽略。这和我们的想法这条规则只会检查魔法数字,而不会检查诸如魔法字符串等。 让我们时光倒流,将代码回退到 eslint 官方首次关于”no-magic-rule”的提交。 代码大概意思是: 如果是变量声明语句,就去检查是否强制使用 const。 如果是则观察语句是否为 const 声明。 对于其他情况,直接检查父节点的类型。2.1. 如果检查对象内部的魔法数字,则直接报错。2.2. 如果不需要检查对象类型,则进行规则过滤,即如果是[“ObjectExpression”,“Property”,“AssignmentExpression”] 中的一种的话也是没问题的。其他情况报错。 那么上面的三种类型是什么呢? 从名字不难发现,其中 AssignmentExpression 和 ObjectExpression 属于表达式。 而 Property 则是对象属性。 ObjectExpression 的例子: 1a = {} 思考题:为什么 ObjectExpression 也是不被允许的?AssignmentExpression 的例子: 1a = b = 2; Property 的例子: 1a = { number: 1 } 也就是说,如果设置了检查对象,那么上面三种情况都会报错。否则不进行报错处理。 AST-Explorer大家使用AST explorer 来可视化 AST。 由于 eslint 使用的 ast 转化工具是 espree, 推荐大家使用 espree。 如果大家写 babel 插件的话,相应的引擎最好修改一下。 值得注意的是,上面我们用的 parent 是不会在 ast-explorer 进行显示的。原因在于其不是 ast 结构的一部分,而如果要做到,也非常容易。 前提是你懂得递归,尤其是树的递归。 如果你还不太懂树的递归, 可以关注我的 B 站LeetCode 加加的个人空间 - 哔哩哔哩 ( ゜- ゜)つロ 乾杯~ Bilibili 通过观察 eslint/espree 的源码也可以发现,其过程是多么的自然和简单。 Magic String实际上一个第三方的 tslint 仓库的一个issue 明确指出了想要增加”no-magic-string” 的想法,只不过被仓库成员给否掉了,原因是其不够通用,除非能证明大家都需要这样一个规则。 那么问题是“为什么 no-magic-string 没有 no-magic-number 通用呢”?【每日一题】- 2020-05-05 - no-magic-string · Issue #122 · azl397985856/fe-interview · GitHub 有一个回答,小漾给出了很好的回答。但是还没有解决疑问“为什么没有 no-magic-string”这样的规则呢? 我的观点是魔法字符串也是不好的,只不过没有不好的那么明显。 我们来看个例子: 12345name = \"lucifer\";a = \"hello, \" + name;if (type === \"add\") {} else if (type == \"edit\") {} 如上代码,就可以被认定为 magic string。但是其在现实代码中是非常普遍的,并且不对我们造成很大的困扰。如果对其改写会是这样的: 12345678name = \"lucifer\";const PRFIX = \"hello, \";const TYPE_ADD = \"add\";const TYPE_EDIT = \"edit\";a = prefix + name;if (type === TYPE_ADD) {} else if (type == TYPE_EDIT) {} 再来看看数字: 1234567if (type === 1) { //}if (total > 5) { //} 如上是我实际工作中见到过的例子,还算有代表性。 上面的代码,如果不通读代码或者事先有一些背景知识,我们根本无从知晓代码的准确含义。 还有一个地方,是数字不同于字符串的。 那就是数字可能是一个无限小数。计算机无法精确表示。 那么程序就不得不进行合理近似,而如果同一个程序不同地方采用的近似手段不同,那么就会有问题。而不使用魔法数就可以就可以避免这个问题。 举个例子: 我们需要计算一个圆的面积,可能会这样做: 1area = 3.1415 * R ** 2 1area = 3.141516 * R ** 2 这样就会有问题了。 而如果我们将 PI 的值抽离到一个变量去维护,任何代码都取这个变量的值就不会有问题。那么有人可能有这样的疑问字符串如果拼写错了,是不是也是一样的么? 比如: 1234// a.jsif (type == 'add') {...}// b.jsif (type == 'addd') {...} 事实上,这样的事情很有可能发生。 只不过这种问题相比于数字来说更容易被发现而已。 这么看来魔法数字确实给大家带来了很大的困扰,那么我们是否应该全面杜绝魔法数呢? 取舍之间真的魔法数字(字符串吧)就是不好的么?其实也不见得。 下面再来看一个我实际工作中碰到的例子: 1234567MS = 0;if (type === \"day\") { MS = 24 * 60 * 60 * 1000;}if (type === \"week\") { MS = 7 * 24 * 60 * 60 * 1000;} 这种代码我不知道看了多少遍了。 或许这在大家眼中已然成为了一种共识,那么这种所谓的魔法数字代码的不可读问题就不存在了。 我们仍可以轻易知道代码的含义。 如果将其进行改造: 1234567891011121314151617MS = 0;const HOURS_PER_DAY = 24;const MINUTES_PER_HOUR = 60;const SECONDs_PER_MINUTE = 60;const MS_PER_SECOND = 1000;const DAYS_PER_WEEK = 7;if (type === \"day\") { MS = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDs_PER_MINUTE * MS_PER_SECOND;}if (type === \"week\") { MS = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDs_PER_MINUTE * MS_PER_SECOND;} 上面的代码不见的要比上面的好到哪里。 《程序设计实践》提到了一点“变量命令不是越长越好,越具体越好,而是根据具体的限定范围”。 比如你在 queue 的 class 中定义的 size 字段可以直接叫 size ,而不是 queue_size。 那么一些社会或者编码常识何尝不是一种限定呢? 如果是的话, 我们是否可以不用特殊限定,而回归到“魔法数”呢?","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"eslint","slug":"前端/eslint","permalink":"https://lucifer.ren/blog/categories/前端/eslint/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"eslint","slug":"eslint","permalink":"https://lucifer.ren/blog/tags/eslint/"}]},{"title":"【LeetCode日记】 1371. 每个元音包含偶数次的最长子字符串","slug":"1371.find-the-longest-substring-containing-vowels-in-even-count","date":"2020-05-03T16:00:00.000Z","updated":"2023-01-05T12:24:50.003Z","comments":true,"path":"2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/","link":"","permalink":"https://lucifer.ren/blog/2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/","excerpt":"这道题还是蛮有意思的,我用了多种方法来解决,干货满满,点进来看看?","text":"这道题还是蛮有意思的,我用了多种方法来解决,干货满满,点进来看看? 题目地址(1371. 每个元音包含偶数次的最长子字符串)https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/ 题目描述12345678910111213141516171819202122232425给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。 示例 1:输入:s = "eleetminicoworoep"输出:13解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。示例 2:输入:s = "leetcodeisgreat"输出:5解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。示例 3:输入:s = "bcbcbc"输出:6解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。 提示:1 <= s.length <= 5 x 10^5s 只包含小写英文字母。 暴力法 + 剪枝思路首先拿到这道题的时候,我想到第一反应是滑动窗口行不行。 但是很快这个想法就被我否定了,因为滑动窗口(这里是可变滑动窗口)我们需要扩张和收缩窗口大小,而这里不那么容易。因为题目要求的是奇偶性,而不是类似“元音出现最多的子串”等。 突然一下子没了思路。那就试试暴力法吧。暴力法的思路比较朴素和直观。 那就是双层循环找到所有子串,然后对于每一个子串,统计元音个数,如果子串的元音个数都是偶数,则更新答案,最后返回最大的满足条件的子串长度即可。 这里我用了一个小的 trick。枚举所有子串的时候,我是从最长的子串开始枚举的,这样我找到一个满足条件的直接返回就行了(early return),不必维护最大值。这样不仅减少了代码量,还提高了效率。 代码代码支持:Python3 Python3 Code: 12345678910111213class Solution: def findTheLongestSubstring(self, s: str) -> int: for i in range(len(s), 0, -1): for j in range(len(s) - i + 1): sub = s[j:j + i] has_odd_vowel = False for vowel in ['a', 'e', 'i', 'o', 'u']: if sub.count(vowel) % 2 != 0: has_odd_vowel = True break if not has_odd_vowel: return i return 0 复杂度分析 时间复杂度:双层循环找出所有子串的复杂度是$O(n^2)$,统计元音个数复杂度也是$O(n)$,因此这种算法的时间复杂度为$O(n^3)$。 空间复杂度:$O(1)$ 前缀和 + 剪枝思路上面思路中对于每一个子串,统计元音个数,我们仔细观察的话,会发现有很多重复的统计。那么优化这部分的内容就可以获得更好的效率。 对于这种连续的数字问题,这里我们考虑使用前缀和来优化。 经过这种空间换时间的策略之后,我们的时间复杂度会降低到$O(n ^ 2)$,但是相应空间复杂度会上升到$O(n)$,这种取舍在很多情况下是值得的。 代码代码支持:Python3,Java Python3 Code: 12345678910111213141516171819202122232425262728293031class Solution: i_mapper = { \"a\": 0, \"e\": 1, \"i\": 2, \"o\": 3, \"u\": 4 } def check(self, s, pre, l, r): for i in range(5): if s[l] in self.i_mapper and i == self.i_mapper[s[l]]: cnt = 1 else: cnt = 0 if (pre[r][i] - pre[l][i] + cnt) % 2 != 0: return False return True def findTheLongestSubstring(self, s: str) -> int: n = len(s) pre = [[0] * 5 for _ in range(n)] # pre for i in range(n): for j in range(5): if s[i] in self.i_mapper and self.i_mapper[s[i]] == j: pre[i][j] = pre[i - 1][j] + 1 else: pre[i][j] = pre[i - 1][j] for i in range(n - 1, -1, -1): for j in range(n - i): if self.check(s, pre, j, i + j): return i + 1 return 0 Java Code: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364class Solution { public int findTheLongestSubstring(String s) { int len = s.length(); if (len == 0) return 0; int[][] preSum = new int[len][5]; int start = getIndex(s.charAt(0)); if (start != -1) preSum[0][start]++; // preSum for (int i = 1; i < len; i++) { int idx = getIndex(s.charAt(i)); for (int j = 0; j < 5; j++) { if (idx == j) preSum[i][j] = preSum[i - 1][j] + 1; else preSum[i][j] = preSum[i - 1][j]; } } for (int i = len - 1; i >= 0; i--) { for (int j = 0; j < len - i; j++) { if (checkValid(preSum, s, i, i + j)) return i + 1 } } return 0 } public boolean checkValid(int[][] preSum, String s, int left, int right) { int idx = getIndex(s.charAt(left)); for (int i = 0; i < 5; i++) if (((preSum[right][i] - preSum[left][i] + (idx == i ? 1 : 0)) & 1) == 1) return false; return true; } public int getIndex(char ch) { if (ch == 'a') return 0; else if (ch == 'e') return 1; else if (ch == 'i') return 2; else if (ch == 'o') return 3; else if (ch == 'u') return 4; else return -1; }} 复杂度分析 时间复杂度:$O(n^2)$。 空间复杂度:$O(n)$ 前缀和 + 状态压缩思路前面的前缀和思路,我们通过空间(prefix)换取时间的方式降低了时间复杂度。但是时间复杂度仍然是平方,我们是否可以继续优化呢? 实际上由于我们只关心奇偶性,并不关心每一个元音字母具体出现的次数。因此我们可以使用是奇数,是偶数两个状态来表示,由于只有两个状态,我们考虑使用位运算。 我们使用 5 位的二进制来表示以 i 结尾的字符串中包含各个元音的奇偶性,其中 0 表示偶数,1 表示奇数,并且最低位表示 a,然后依次是 e,i,o,u。比如 10110 则表示的是包含偶数个 a 和 o,奇数个 e,i,u,我们用变量 cur 来表示。 为什么用 0 表示偶数?1 表示奇数? 回答这个问题,你需要继续往下看。 其实这个解法还用到了一个性质,这个性质是小学数学知识: 如果两个数字奇偶性相同,那么其相减一定是偶数。 如果两个数字奇偶性不同,那么其相减一定是奇数。 看到这里,我们再来看上面抛出的问题为什么用 0 表示偶数?1 表示奇数?。因为这里我们打算用异或运算,而异或的性质是: 如果对两个二进制做异或,会对其每一位进行位运算,如果相同则位 0,否则位 1。这和上面的性质非常相似。上面说奇偶性相同则位偶数,否则为奇数。因此很自然地用 0 表示偶数?1 表示奇数会更加方便。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122class Solution: def findTheLongestSubstring(self, s: str) -> int: mapper = { \"a\": 1, \"e\": 2, \"i\": 4, \"o\": 8, \"u\": 16 } seen = {0: -1} res = cur = 0 for i in range(len(s)): if s[i] in mapper: cur ^= mapper.get(s[i]) # 全部奇偶性都相同,相减一定都是偶数 if cur in seen: res = max(res, i - seen.get(cur)) else: seen[cur] = i return res 复杂度分析 时间复杂度:$O(n)$。 空间复杂度:$O(n)$ 关键点解析 前缀和 状态压缩 相关题目 掌握前缀表达式真的可以为所欲为!","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"前缀和","slug":"算法/前缀和","permalink":"https://lucifer.ren/blog/categories/算法/前缀和/"},{"name":"状态压缩","slug":"算法/状态压缩","permalink":"https://lucifer.ren/blog/categories/算法/状态压缩/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"},{"name":"状态压缩","slug":"状态压缩","permalink":"https://lucifer.ren/blog/tags/状态压缩/"}]},{"title":"零基础的前端开发初学者应如何系统地学习?","slug":"how-am-I-learn-fe","date":"2020-04-30T16:00:00.000Z","updated":"2023-01-07T12:34:34.209Z","comments":true,"path":"2020/05/01/how-am-I-learn-fe/","link":"","permalink":"https://lucifer.ren/blog/2020/05/01/how-am-I-learn-fe/","excerpt":"回想四年前我刚入行的时候,那时候很多人对于前端的看法是“切图,画页面,有个编辑器+浏览器就能干,门槛低”,现在已经完全不是那样了,可以说现在的前端这个职业的门槛虽然还是没怎么变,但是整个行业的门槛提升了,换句话说就是整个行业对于前端这个职位要求更高了,对于前端小白的需求量降低,对于高级前端的需求量还在上升,甚至是供小于求的局面。从市场经济学角度上讲你只有进入到高级级别,才能真正吃到行业的红利。 因此想要入行的朋友要先想清楚,不要头脑发热,如果你想清楚了,那么请继续往下看。说实话,现在的前端大环境对初学者来说实在有点不友好,学习资料鱼龙混杂,良莠不齐,有质量很高的学习资料,也有谬论,前后不一,观点错误,或者讲述不清晰的。 更可怕的是质量低下的文章有时候更受欢迎,因此需要大家有很好的甄别能力,但这对于初学者来说实在有些困难,我在这里就来谈一下 初学者如何少走弯路,并且系统性地学习前端。","text":"回想四年前我刚入行的时候,那时候很多人对于前端的看法是“切图,画页面,有个编辑器+浏览器就能干,门槛低”,现在已经完全不是那样了,可以说现在的前端这个职业的门槛虽然还是没怎么变,但是整个行业的门槛提升了,换句话说就是整个行业对于前端这个职位要求更高了,对于前端小白的需求量降低,对于高级前端的需求量还在上升,甚至是供小于求的局面。从市场经济学角度上讲你只有进入到高级级别,才能真正吃到行业的红利。 因此想要入行的朋友要先想清楚,不要头脑发热,如果你想清楚了,那么请继续往下看。说实话,现在的前端大环境对初学者来说实在有点不友好,学习资料鱼龙混杂,良莠不齐,有质量很高的学习资料,也有谬论,前后不一,观点错误,或者讲述不清晰的。 更可怕的是质量低下的文章有时候更受欢迎,因此需要大家有很好的甄别能力,但这对于初学者来说实在有些困难,我在这里就来谈一下 初学者如何少走弯路,并且系统性地学习前端。 兴趣是最好的老师兴趣不管对于学习什么来说都是最好的老师。当然前端也不例外,如果你对这一门感兴趣,绝对会对你有很大的帮助。 关于如何培养兴趣,我提一点,你可以尝试去做一些小的“发明创造”,从而激发自己内心的“成就感”。这些小发明可以是一些小工具,小页面。你可以从开源社区,比如 Github 或者一些论坛,甚至自己的生活中收集一些创作素材。对于我来说,我就做过一个“前端开发工作流”的软件,“siri”, “小门神”等,从而带来成就感,提升自己的兴趣。 权威,权威,还是权威其实技术越往上走,越会关注标准,关注协议等更上层和抽象的东西。而制定这些协议和标准的人往往都是世界上的“殿堂级”程序员,因此关注这些东西对于他们来说就是权威,对他们来说就非常很重要,但是这对于初学者来说似乎还比较遥远。那么初学者如何对接“权威”呢?刚才提到了网上的学习资料参差不齐,这其实对于入门学习来说是很不利的,就像童年时期对于整个人生的影响一样,入门阶段对于整个前端开发生涯的影响也是巨大的。关于如何初学者如何对接权威,我这里总结了以下三点:看一些权威的书籍,包括前端基础,软件工程以及算法等。这里不太建议看太老的,毕竟技术的发展是很快的,以前非常经典的书并一定适合看了,尤其是初学者而言。这里前端方面我重点推荐两本书,一本是《你不知道的 JS》,一本是《JavaScript 语言精粹》。除了前端,你还可以看一些软工类的书,我个人比较喜欢的有《程序员修炼之道》等,算法类的有《图解算法》,《编程之美》等。其他的我就不一一赘述了,想要更多书单的可以私信我。 查权威资料。 这里我推荐两个,一个是MDN的文章,真的是又全面又专业,绝对是前端开发必备神器,哪里不会点哪里。 另外推荐一个Google 开发者 , 里面干货很多,绝对权威。这里顺便再安利一个软件,用来查文档什么的,简直如虎添翼,这个软件的名字是 Dash,大家可以把自己常用的框架,类库等导进去,想用的时候直接查询即可,比去网上搜更快更高效,这个软件对于定制的支持度也是蛮高的,谁用谁知道。 (大家可以看到我下载了很多 documentation) (你可以直达某一个 documentation 搜索,也可以全局搜索,甚至可以搜 goole 和 stackoverflow,是不是很贴心?) 关注一些圈内权威人士。 我一般会关注几个圈内比较知名的人的知乎,微博和 twitter。这是我关注的Github 的权威人士列表。其实这些都是公开的,你也可以点开我的知乎,微博资料看我或者大佬们关注了谁。 做一些完整的简单项目大家可以尝试做一些简单的项目,不要嫌简单。 在做的过程往往能发现很多问题,如果有问题那这正好是自己提高的机会。 如果你觉得很简单,也没有关系,你可以思考一下,我有没有可能做的更好?我能不能把这些东西封装起来,建立更高一层的抽象(A New Level of Abstraction),做到 DRY(Don’t Repeat Yourself)。接下来就是关于怎么找项目。 你可以找个正式工作或者实习来做,也可以自己找一些小项目来练手, 比较常见的练手项目有模仿某个网站,APP 或者搭建自己的个人主页,博客系统等。做好了不仅可以当敲门砖,说不定会收益很长时间呢。实在没有什么项目练手,这里再推荐一个网站,你可以再上面打怪升级。freecodecamp现在你已经掌握了前端开发的基本概念和技能,那么如何做到更进一步,持续成长,做到高级呢?我相信这是很多人的疑问,下面我们就来看一下。 学习路线你可能已经听过过大前端这个词,我这里不是劝退你哦。以下内容很高能,不过很多知识点不知道没关系,因为就算是工作了很多年了老手也很难了解其中的大半知识点。我个人为了让自己巩固知识,同时也为了帮助他人,总结了大前端下的 30 多个主题内容,内容覆盖大前端的方方面面,虽然是从面试角度出发,但是你用来提升自己,查缺补漏也是很有用的。 多图预警: 拿《编程题》主题来说,我总结了各大公司常考的几十道题目。 对于其他主题也是一样,我都会尽可能地深度讲解和剖析,并且从多方面理解,我相信这是在市面上很少见的。 而且我还画了很多图,来帮助大家理解一些抽象的知识点。 项目地址: https://lucifer.ren/fe-interview/#/ 欢迎围观。 开源项目 实话实说,很多优秀的思想,规范,写法我都是从优秀的开源项目中学来的。 我会不定期阅读一些优秀的开源项目源码,也会参与到开源的工作中去,这给我自己带来了很大的提升。 不仅技术得到了提升,团队协作,规范化等方面也有了质的提高,另外还认识了一些优秀的人。四年来,我阅读了很多优秀的源码,也尝试自己去造一些轮子,并开源出去,回馈社区。 输入 + 输出前面重点讲述的是输入。 其实学习的过程不仅仅是输入,输出也是很好的学习方法。 输出的形式有很多,比如写博客,讲给别人,开源出去让别人用等。 这其实是很好的学习机会,这种学习方法可以让你的成长速度呈指数型增长,因此千万不要小看它。 我会通过以练代学的方式来学习,比如我学习 React,我会迅速看文档,然后写 demo,最后我会自己《从零实现 React》来内化它。 我还会定期做总结,写文章,写开源项目,做分享等,目的一方面是影响他人,另一方面是成长自己。 持续学习选择了技术这条路,就要做好持续学习,每日精进的准备,跟上时代潮流是很有必要的。 日报周报。 大家可以订阅一些前端方面的日报周报,这方面其实有很多,这里只推荐一个我常看的一个JavaScript 周刊。我自己也出了一款《每日一荐》, 每天推荐一个优秀的开源项目,优秀文章, 周一到周五我们不见不散。 深度参与开源项目。 关于如何参与开源项目其实可以另起一篇文章了,这里不再赘述,感性的话,我会再写一篇文章,大家记得关注我就好。 定期总结,技术输出。 我的习惯是对于学习的内容定期和不定期地进行总结。 比如最近我在总结的[《leetcode 题解》](现在有 18k+ ✨ 了)(https://github.com/azl397985856/leetcode),[《大前端面试宝典》](https://github.com/azl397985856/fe-interview) 千万不要觉得算法对前端不重要,算法,网络,操作系统属于基础,从事相关工作的都应该认真学习,打好基础才行。 关注我大家可以关注我的公众号《脑洞前端》,公众号后台回复“大前端”,拉你进《大前端面试宝典 - 图解前端群》。回复“leetcode”,拉你进《leetcode 题解交流群》 最后祝大家在前端的路上越走越远。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/categories/学习方法/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/tags/学习方法/"}]},{"title":"纪念LeetCode项目Star突破3W","slug":"thanksGiving3","date":"2020-04-13T16:00:00.000Z","updated":"2023-01-05T12:24:50.001Z","comments":true,"path":"2020/04/14/thanksGiving3/","link":"","permalink":"https://lucifer.ren/blog/2020/04/14/thanksGiving3/","excerpt":"差不多一年的时间,项目收获了第 3W 个 Star,平均差不多一天 100 左右的 star,非常感谢大家的关注和支持。","text":"差不多一年的时间,项目收获了第 3W 个 Star,平均差不多一天 100 左右的 star,非常感谢大家的关注和支持。 30k 截图 Star 曲线Start 曲线上来看,有一点放缓。但是整体仍然是明显的上升趋势。 (star 增长曲线图) 在力扣宣传当力扣官方也开始做每日一题的时候,我的心情是复杂的。怎么官方也开始学我搞每日一题了么?为了信仰(蹭热度),我也毅然决然参加了每日一题活动,贡献了几十篇题解。 三月份是满勤奖,四月份有一次忘记了,缺卡一天。 新书即将上线新书详情戳这里:《或许是一本可以彻底改变你刷 LeetCode 效率的题解书》,目前正在申请书号。 点名感谢各位作者,审阅,以及行政小姐姐。 视频题解最近开始做视频题解了,目前更新了五个视频。和文字题解不同,视频题解可以承载的内容会更多。 https://space.bilibili.com/519510412 我计划更新一些文字题解很难表述的内容,当然还会提供 PPT,如果你喜欢文字,直接看 PPT 即可。 视频题解部分,我会带你拆解 LeetCode 题目,识别常见问题,掌握常见套路。 注意:这不是教你解决某一道题的题解,而是掌握解题方法和思路的题解。 《力扣加加》上线啦我们的官网力扣加加上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/ 点名感谢@三天 @CYL @Josephjinn 朋友的支持很多朋友也在关注我的项目,非常开心。点名感谢 @被单-加加 @童欧巴。 交流群交流群人数也有了很大的提升。 粉丝人数也扩充到了 7000+。交流群数目也增加到了 10 个。其中 QQ 群人数最多,有将近 1800 人。为了限制人数,我开启了收费模式,希望大家不要打我 😂。 非常感谢大家一直以来的陪伴和支持,Fighting 💪。","categories":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/categories/日记/"},{"name":"技术","slug":"日记/技术","permalink":"https://lucifer.ren/blog/categories/日记/技术/"}],"tags":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/tags/日记/"}]},{"title":"提前批算法工程师面试之路","slug":"interview-log-tqp","date":"2020-04-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.853Z","comments":true,"path":"2020/04/13/interview-log-tqp/","link":"","permalink":"https://lucifer.ren/blog/2020/04/13/interview-log-tqp/","excerpt":"作者:宝石 校对&整理:lucifer","text":"作者:宝石 校对&整理:lucifer vivo(已拿 offer)技术面(30min) 自我介绍 讲实习 讲比赛 问了些大数据的问题 spark transform 和 action 的区别 手撕了个归并排序 hr 面(30min,技术只有一面) 自我介绍 家庭情况 讲一下实习亮点 有女朋友么 父母同意你去深圳么 讲一下优缺点 常规问题等等 昆仑万维(已拿 offer)一面(1h): 上来自我介绍 讲一下判断回文链表的思路,多方法 用纸写一下反转链表 说说 python shuffle 怎么实现的,O(N)时间,O(1)空间 看你计算机专业,知道哈夫曼树吧,w2v 有用,你能说说么(我就记得分层 softmax 了,实在是不会) 说说传统机器学习你都了解哪些?推一下 LR,说一下原理 知道 kmeans 吧,说下迭代过程,簇心随机不好,怎么才能更稳定(类 kmeans++) 说说深度学习你都知道哪些模型?我说了 LSTM RNN,还没等说推荐的模型。。。 讲一下 LSTM 吧,门都是怎么迭代的 各个激活函数的优缺点,sigmoid relu leaklyrelu tanh,又说了下梯度消失 Sigmoid 求导是什么? 了解推荐么,说说都知道啥,嘴贱说了个 CF 讲一下 Item-CF,怎么计算相似度(用交并,也就是 Jaccard 距离,速度比 cos 快),用什么优化呢(倒排索引) 讲讲数据倾斜怎么处理吧?(用 key+随机前后缀) 聊一下你这个项目里 LDA 吧?我直接说这个我用过没细研究(他也认同,说数学原理很复杂) 聊一下你这个项目 query title 统计特征都提了啥,跟他聊了一下,他和我说了一下他的业务理解 反问 做什么的(啥都做,业务很广。。。) 用哪些技术(啥都用,技术栈很广。。。) 昆仑万维二面(就是确认时间 不算面试) 和一面面试官聊的如何 知道他们部门做什么的么 接下来约一下 boss 面,确认时间 结束 昆仑万维三面(不到二十分钟,压力面): 上来就问比赛,两个比赛都问了,和第一差距在哪 下面问题都是快问快答,都没深问,问题可能记不全了: 说下你实习吧,没说几句。。 你怎么解决样本不均衡的 kmeans 适用于什么情况 python dict 怎么用 为什么会产生哈希冲突 python set dict list 啥场景用 过拟合有哪些解决方法 牛顿法和拟牛顿法 200w 不重复单词一口气放内存中,怎么放最省内存(不会) 你除了学习之外还做什么项目 平常刷算法题么,刷多少了 另一个面试官的问题不是压力测试 你希望做什么种类的工作(大概就是这个意思) 没得反问 京东一面(40min) 很好的年轻女面试官 自我介绍 跟我聊了一下,然后说看你挺直率,我就直接说了,你想找推荐,我们是机器学习+组合优化,偏向运筹学,考虑么,(我说只要不是 cvnlp,我全要) 考虑那就开始问些问题吧: 你讲讲你的实习,最亮点,给他分析了一波我采样策略。 你知道 gbdt 和 xgboost 吧,给我讲讲(疯狂吹逼 xgboost) 你知道最大似然估计和最大后验概率估计么,或者挑一个你熟悉的说下(闭着眼睛推 LR,啥伯努利分布,似然函数,疯狂扯) 来做个题吧,1000w 个数,数范围[-1000, 1000],写个排序(闭着眼睛桶排) 你能提前来实习么 反问(京东零售部的,技术栈 balabala) 复试待安排 二面(30min) 自我介绍 找个比赛说说创新点,你主要做了哪些创新,最后模型为什么用 CNN 系不用 RNN 系 由于上面我说我工作有训练词向量了,让我讲 word2vec 的架构,和一些细节问题 为什么 w2v 向量在语义空间内有很好的数学性质,比如相加减 数学问题:M 个样本有放回采样 N 次,问某条样本一次没被采中概率 给你均值方差,让你利用正态分布随机生成 1000 个点(不能用库,说的不是很好) 反问:哪个部门哪个组(零售-技术中台下) 为什么选择京东,京东有什么核心竞争力(疯狂扯,我说我不太看好那些不能落地的,因为 jd 是电商,整个算法流程系统化工程化一定很健全,也有实际落地,带来实际效益,面试官非常赞同) 最后手里有啥 offer 啊 没了 hr 面(20min) 小姐姐人很好,迟到了四分钟,上来道歉一波 自我介绍 遇到压力大的时候,比如在实习问题解决不了了,你会怎么办 与 mentor 产生意见分歧要怎么做 未来如果入职京东,对领导有什么要求呢 你平常有什么学习习惯么 你平常会定期做总结么 反问 当时问应届生入职需要做出啥改变,给小姐姐问懵了,我又补充说比如思想啥的需要有啥改变么,她给我讲了五六分钟,说的很直白,没啥官腔,说在学校如何如何,你来公司了又会如何如何 啥时候有结果:她也不知道,她拿到的是乱序的面试名单,可能后面会统一安排通知 一点资讯一面(不到 40min) 自我介绍 讲你的论文,这块一直问,问得特别细节,也问了好久,估计面试官比较清楚这块东西。 讲实习,都怎么做的,遇到啥问题,怎么解决。 讲一下 FM,DeepFM 这一系列(我从 LR,POLY2,FM,FFM,Wide&Deep,DeepFm 说了个遍) 做了个算法题,A 了 反问: 部门:算法部信息流,和我微博实习的比较类似 技术:做推荐 Java 和 Scala 用的多一些 个人感觉像不招人。。。 大华一面 - 大数据工程师(数据挖掘)(不到 40min) 面试官很有礼貌 自我介绍 着重问了好久实习 着重问了好久比赛 linux 查指定文件前缀有哪些命令 讲一下 hive 和 hadoop 关系 hadoop mapreduce 执行流程 java 类和对象关系 百度一面(1h) 自我介绍 介绍实习,然后疯狂挖实习,问的很深 问如果模型区分不开一些样本,要怎么采样?业界有哪些常用采样策略。我真是懵了。。 问了一堆 fm,比如表达式,复杂度,改进复杂度的推导 了解深度学习么,从 wide&deep 出发疯狂问,有的是真不会,要再复习一下 面试官说了个 DCN(深度交叉网络),我还以为深度卷积神经网络。。。,结果深度交叉网络的细节也给忘了 我主动给介绍了下阿里的 DIN 和 DIEN,他问这模型可以用在新闻推荐场景么(答不可以,因为新闻类实时性比较强 balabala。。。不知道对不对) 如果想让你加入一个用户短期兴趣,比如刚发布的新闻打分低,要怎么改,(我记得在 YouTube 有个说了关于这个,我说加了个时间维度特征,或者时间衰减) 让我讲 BN,为什么提出 BN(好久没看 nn 的东西了,直说了个表象,容易收敛,面试官说为了解决输入分布不一致,bn 可以拉回来分布,我把这个忘了) 从 LR 出发问了我 sgd,如何改进,说了个 momentum,再怎么改进,我说我了解个 FTRL 说一下 boosting bagging ,lgb 为什么并行化效率高(答单边梯度抽样+直方图计算+互斥特征捆绑) 怎么分析并解过拟合问题的 算法题:三数之和 反问 部门是推荐策略部 主要场景是百度直播和贴吧推荐 用 Python 和 C++,不用 Java 触宝一面(1h) 自我介绍 数据结构基础 数组和链表区别,应用场景 疯狂问排序算法,最优最坏平均复杂度,稳定排序有哪些(好长时间没复习这个了,答得比较差) 一个剪枝题,口述算法过程,分析时空复杂度 说说面向过程、对象、切片编程的区别(我。。。。。。) 机器学习基础 讲一下你了解哪些分类模型 说说 SVM 讲讲 id3 和 c4.5 讲讲 xgboost 和 gbdt 讲讲怎么判断 kmeans 的 k,聚类效果的好坏 k 可以用肘部法则 SSE(误差平方和)和轮廓系数 讲讲分类,回归,推荐,搜索的评价指标 讲讲 lr 和 fm,fm 的后续(ffm) 讲讲你知道的各种损失函数 讲讲 l1 l2 正则,各自的特点 深度学习基础 说说 deepfm,说说 fm 在 nn 中还有哪些(FNN,NFM,AFM) 说说类似 l1,l2 正则化降低模型过拟合,还有什么别的方法 说一下 sgd→adam 的过程(下面是面试后简单复盘,本身答的一般) sgd momentum 利用了历史信息,意味着参数更新方向不仅由当前的梯度决定,也与此前累积的下降方向有关。这使得参数中那些梯度方向变化不大的维度可以加速更新,并减少梯度方向变化较大的维度上的更新幅度。由此产生了加速收敛和减小震荡的效果。 rmsprop 在 Adagrad 中, 问题是学习率逐渐递减至 0,可能导致训练过程提前结束。为了改进这一缺点,可以考虑在计算二阶动量时不累积全部历史梯度,而只关注最近某一时间窗口内的下降梯度。根据此思想有了 RMSprop,采用的指数移动平均公式计算 adam 可以认为是 RMSprop 和 Momentum 结合并加了偏差校正,因为初始化是 0,有一个向初值的偏移(过多的偏向了 0)。因此,可以对一阶和二阶动量做偏置校正 (bias correction), 介绍下梯度消失和梯度爆炸 都有哪些方法解决这两个问题 你了解多目标优化,迁移学习么(不知道) 场景问题 让你加一个兴趣类型特征 你要怎么做 如何处理年龄类特征 你了解相似向量快速计算的方法吗(就记得个啥哈希分桶,没做过) 局部哈希计算,高维相近的点低维也相近,但是高维较远的点低维可能也相近,将 embedding 应设成 1 维,若担心把远的也算进来可以多设置几个 hash 函数等等。 如何判断你模型上线的好坏 给你个 nn 模型,如何调参,如何修改架构 如何解决冷启动问题 用户侧,物品侧 推荐系统的整体架构 线上推断这部分再具体点怎么实现的 反问 触宝内容推荐(小说) 主要用 python 等后续 hr 通知吧 二面(45min) 面试官人很好,和善可亲 自我介绍 讲下实习做了哪些优化,问了些问题(我都没介绍实习,面试官已经直接点破我每一点实际都在做什么) 讨论了一下抽样,作为一个算法工程师如何将抽样导致的得分分布变化给拉回来? 因为实习模型是 FM,详细讲了下 FM,讨论了下 FM 的泛化性 用的什么优化算法,顺便介绍下 sgd 后续的优化,sgd→momentun→rmsprop→adam,一面问过的,复盘过 实习有没有除错过导致线上有点问题(还真有。。。) hadoop shuffle 干啥的,为啥 key 相同的要放在一起 python 深拷贝和浅拷贝的区别 linux 替换文件中所有的 a,我说的 awk 或者 tr 算法题:给两个字符串 S 和 T,计算 S 的子序列中 T 出现的次数(dfs A 了) 反问:竟然和一面面试官不是一个部门。。。二面面试官给我介绍了算法在他们公司都有哪些应用。。。 总之要有工程师顶层思维,不能局限于模型优化啥的。 大家可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"},{"name":"百度","slug":"百度","permalink":"https://lucifer.ren/blog/categories/百度/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"},{"name":"百度","slug":"百度","permalink":"https://lucifer.ren/blog/tags/百度/"}]},{"title":"贪婪策略系列 - 覆盖篇","slug":"leetcode-greedy","date":"2020-04-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.770Z","comments":true,"path":"2020/04/13/leetcode-greedy/","link":"","permalink":"https://lucifer.ren/blog/2020/04/13/leetcode-greedy/","excerpt":"贪婪策略是一种常见的算法思想,具体是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这点和动态规划一样。 LeetCode 上对于贪婪策略有 73 道题目。我们将其分成几个类型来讲解,截止目前我们暂时只提供覆盖问题,其他的可以期待我的新书或者之后的题解文章。","text":"贪婪策略是一种常见的算法思想,具体是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这点和动态规划一样。 LeetCode 上对于贪婪策略有 73 道题目。我们将其分成几个类型来讲解,截止目前我们暂时只提供覆盖问题,其他的可以期待我的新书或者之后的题解文章。 覆盖我们挑选三道来讲解,这三道题除了使用贪婪法,你也可以尝试动态规划来解决。 45. 跳跃游戏 II,困难 1024. 视频拼接,中等 1326. 灌溉花园的最少水龙头数目,困难 覆盖问题的一大特征,我们可以将其抽象为给定数轴上的一个大区间 I 和 n 个小区间 i[0], i[1], ..., i[n - 1],问最少选择多少个小区间,使得这些小区间的并集可以覆盖整个大区间。 我们来看下这三道题吧。 45. 跳跃游戏 II题目描述给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 示例: 输入: [2,3,1,1,4]输出: 2解释: 跳到最后一个位置的最小跳跃数是 2。 从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。说明: 假设你总是可以到达数组的最后一个位置。 思路贪婪策略,即我们每次在可跳范围内选择可以使得跳的更远的位置,由于题目保证了你总是可以到达数组的最后一个位置,因此这种算法是完备的。 如下图,开始的位置是 2,可跳的范围是橙色的。然后因为 3 可以跳的更远,所以跳到 3 的位置。 如下图,然后现在的位置就是 3 了,能跳的范围是橙色的,然后因为 4 可以跳的更远,所以下次跳到 4 的位置。 写代码的话,我们用 end 表示当前能跳的边界,对于上边第一个图的橙色 1,第二个图中就是橙色的 4,遍历数组的时候,到了边界,我们就重新更新新的边界。 图来自 https://leetcode-cn.com/u/windliang/ 代码代码支持:Python3 Python3 Code: 12345678910class Solution: def jump(self, nums: List[int]) -> int: n, cnt, furthest, end = len(nums), 0, 0, 0 for i in range(n - 1): furthest = max(furthest, nums[i] + i) if i == end: cnt += 1 end = furthest return cnt 复杂度分析 时间复杂度:$O(N)$。 空间复杂度:$O(1)$。 1024. 视频拼接题目描述你将会获得一系列视频片段,这些片段来自于一项持续时长为 T 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。 视频片段 clips[i] 都用区间进行表示:开始于 clips[i][0] 并于 clips[i][1] 结束。我们甚至可以对这些片段自由地再剪辑,例如片段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。 我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, T])。返回所需片段的最小数目,如果无法完成该任务,则返回 -1 。 示例 1: 输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 10输出:3解释:我们选中 [0,2], [8,10], [1,9] 这三个片段。然后,按下面的方案重制比赛片段:将 [1,9] 再剪辑为 [1,2] + [2,8] + [8,9] 。现在我们手上有 [0,2] + [2,8] + [8,10],而这些涵盖了整场比赛 [0, 10]。示例 2: 输入:clips = [[0,1],[1,2]], T = 5输出:-1解释:我们无法只用 [0,1] 和 [0,2] 覆盖 [0,5] 的整个过程。示例 3: 输入:clips = [[0,1],[6,8],[0,2],[5,6],[0,4],[0,3],[6,7],[1,3],[4,7],[1,4],[2,5],[2,6],[3,4],[4,5],[5,7],[6,9]], T = 9输出:3解释:我们选取片段 [0,4], [4,7] 和 [6,9] 。示例 4: 输入:clips = [[0,4],[2,8]], T = 5输出:2解释:注意,你可能录制超过比赛结束时间的视频。 提示: 1 <= clips.length <= 1000 <= clips[i][0], clips[i][1] <= 1000 <= T <= 100 思路贪婪策略,我们选择满足条件的最大值。和上面的不同,这次我们需要手动进行一次排序,实际上贪婪策略经常伴随着排序,我们按照 clip[0]从小到大进行排序。 如图: 1 不可以,因此存在断层 2 可以 3 不行,因为不到 T 我们当前的 clip 开始结束时间分别为 s,e。 上一段 clip 的结束时间是 t1,上上一段 clip 结束时间是 t2。 那么这种情况下 t1 实际上是不需要的,因为 t2 完全可以覆盖它: 那什么样 t1 才是需要的呢?如图: 用代码来说的话就是s > t2 and t2 <= t1 代码代码支持:Python3 Python3 Code: 12345678910111213141516class Solution: def videoStitching(self, clips: List[List[int]], T: int) -> int: # t1 表示选取的上一个clip的结束时间 # t2 表示选取的上上一个clip的结束时间 t2, t1, cnt = -1, 0, 0 clips.sort(key=lambda a: a[0]) for s, e in clips: # s > t1 已经确定不可以了, t1 >= T 已经可以了 if s > t1 or t1 >= T: break if s > t2 and t2 <= t1: cnt += 1 t2 = t1 t1 = max(t1,e) return cnt if t1 >= T else - 1 复杂度分析 时间复杂度:由于使用了排序(假设是基于比较的排序),因此时间复杂度为 $O(NlogN)$。 空间复杂度:$O(1)$。 1326. 灌溉花园的最少水龙头数目题目描述在 x 轴上有一个一维的花园。花园长度为 n,从点 0 开始,到点 n 结束。 花园里总共有 n + 1 个水龙头,分别位于 [0, 1, …, n] 。 给你一个整数 n 和一个长度为 n + 1 的整数数组 ranges ,其中 ranges[i] (下标从 0 开始)表示:如果打开点 i 处的水龙头,可以灌溉的区域为 [i - ranges[i], i + ranges[i]] 。 请你返回可以灌溉整个花园的 最少水龙头数目 。如果花园始终存在无法灌溉到的地方,请你返回 -1 。 示例 1: 输入:n = 5, ranges = [3,4,1,1,0,0]输出:1解释:点 0 处的水龙头可以灌溉区间 [-3,3]点 1 处的水龙头可以灌溉区间 [-3,5]点 2 处的水龙头可以灌溉区间 [1,3]点 3 处的水龙头可以灌溉区间 [2,4]点 4 处的水龙头可以灌溉区间 [4,4]点 5 处的水龙头可以灌溉区间 [5,5]只需要打开点 1 处的水龙头即可灌溉整个花园 [0,5] 。示例 2: 输入:n = 3, ranges = [0,0,0,0]输出:-1解释:即使打开所有水龙头,你也无法灌溉整个花园。示例 3: 输入:n = 7, ranges = [1,2,1,0,2,1,0,1]输出:3示例 4: 输入:n = 8, ranges = [4,0,0,0,0,0,0,0,4]输出:2示例 5: 输入:n = 8, ranges = [4,0,0,0,4,0,0,0,4]输出:1 提示: 1 <= n <= 10^4ranges.length == n + 10 <= ranges[i] <= 100 思路贪心策略,我们尽量找到能够覆盖最远(右边)位置的水龙头,并记录它最右覆盖的土地。 我们使用 furthest[i] 来记录经过每一个水龙头 i 能够覆盖的最右侧土地。 一共有 n+1 个水龙头,我们遍历 n + 1 次。 对于每次我们计算水龙头的左右边界,[i - ranges[i], i + ranges[i]] 我们更新左右边界范围内的水龙头的 furthest 最后从土地 0 开始,一直到土地 n ,记录水龙头数目 代码代码支持:Python3 Python3 Code: 123456789101112131415class Solution: def minTaps(self, n: int, ranges: List[int]) -> int: furthest, cnt, cur = [0] * n, 0, 0 for i in range(n + 1): l = max(0, i - ranges[i]) r = min(n, i + ranges[i]) for j in range(l, r): furthest[j] = max(furthest[j], r) while cur < n: if furthest[cur] == 0: return -1 cur = furthest[cur] cnt += 1 return cnt 复杂度分析 时间复杂度:时间复杂度取决 l 和 r,也就是说取决于 ranges 数组的值,假设 ranges 的平均大小为 Size 的话,那么时间复杂度为 $O(N * Size)$。 空间复杂度:我们使用了 furthest 数组, 因此空间复杂度为 $O(N)$。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"贪婪","slug":"贪婪","permalink":"https://lucifer.ren/blog/categories/贪婪/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"},{"name":"贪婪","slug":"贪婪","permalink":"https://lucifer.ren/blog/tags/贪婪/"}]},{"title":"『不要再问我头像如何变灰了,试试这几种滤镜吧!』","slug":"canvas-filter","date":"2020-04-11T16:00:00.000Z","updated":"2023-01-07T12:20:39.957Z","comments":true,"path":"2020/04/12/canvas-filter/","link":"","permalink":"https://lucifer.ren/blog/2020/04/12/canvas-filter/","excerpt":"在实际的工作中,有时候会有一些需求,让你做一些图片的滤镜效果,比如将图片变成黑白,调整图片亮度等。本文手把手教你如何实现五种滤镜效果,核心代码总共不到 70 行。 笔者所在的公司就有一个需求需要用到图片处理的知识,大概场景我来描述一下: 用户可以手动上传印章,并且支持给印章设置不同的显示效果,这里的效果具体指的是“线条的清晰程度”,如下图所示: 这里我们使用 Canvas 来实现。如果你对 Canvas 不熟悉,建议看下之前我写的一篇文章100 * 100 Canvas 占用内存多大,花上几分钟看完,基本上够看懂这篇文章了。","text":"在实际的工作中,有时候会有一些需求,让你做一些图片的滤镜效果,比如将图片变成黑白,调整图片亮度等。本文手把手教你如何实现五种滤镜效果,核心代码总共不到 70 行。 笔者所在的公司就有一个需求需要用到图片处理的知识,大概场景我来描述一下: 用户可以手动上传印章,并且支持给印章设置不同的显示效果,这里的效果具体指的是“线条的清晰程度”,如下图所示: 这里我们使用 Canvas 来实现。如果你对 Canvas 不熟悉,建议看下之前我写的一篇文章100 * 100 Canvas 占用内存多大,花上几分钟看完,基本上够看懂这篇文章了。 准备工作首先我们先将图片绘制到 Canvas 画布上,为了简单起见,图片大小固定为 300 x 300。 1<canvas id=\"canvas\" width=\"300px\" height=\"300px\"></canvas> (html) 12345678910//获取canvas元素ctx = document.getElementById(\"canvas\").getContext(\"2d\");//创建image对象var img = new Image();img.src = require(\"./seal.png\");//待图片加载完后,将其显示在canvas上img.onload = () => { ctx.drawImage(img, 0, 0); this.imgData = ctx.getImageData(0, 0, 300, 300);}; (js) 效果是这样的: 操作像素熟悉 Canvas 的应该知道上面的 this.imgData 实际上就是ImageData类的实例,其中 imgData.data 是一个 Uint8ClampedArray, 其描述了一个一维数组,包含以 RGBA 顺序的数据,数据使用 0 至 255(包含)的整数表示。 简单来说,就是图片像素信息,每四位表示一个像素单元。其中每四位的信息分别是 RGBA。即第一个 Bit 标记 R,第二个 Bit 表示 G,第三个 Bit 表示 B,第四个 Bit 表示 A,第五个 Bit 又是 R…,依次类推。 接下来,我们就要操作 imgData,来实现滤镜的效果。简单起见,我这里对超过 200 的值进行了一次提高亮度的操作。实际上这个值是 200,还是别的数字,需要我们化身”调参工程师”,不断实验才行。 并且粗暴地对 RGB 执行同样的逻辑是不合理的。更为合理的做法是对 RGB 的阀值分别进行度量,由于比较麻烦,我这里没有实现。但是如果你对效果要求比较高,那么最好可以分开度量。 123456789101112131415const data = this.imgData.data;for (let i = 0; i < data.length; i += 4) { if (data[i] < 200) { data[i] = data[i] + brightness > 255 ? 255 : data[i] + brightness; } if (data[i + 1] < 200) { data[i + 1] = data[i + 1] + brightness > 255 ? 255 : data[i + 1] + brightness; } if (data[i + 2] < 200) { data[i + 2] = data[i + 2] + brightness > 255 ? 255 : data[i + 2] + brightness; }} 如上,我们对图片的像素进行了处理,以达到我们的目的,这样从用户感官上来看,显示效果发生了变化,大概效果如图: (清晰版) (模糊版) 如果你愿意的话,你也可以将处理好的图片进行导出,也很简单,直接调用 Canvas 实例的 toDataURL 方法即可,图片保存的格式也可以在这个方法中进行指定。 日常开发中,我们还可能碰到很多其他的滤镜效果。下面介绍几个比较现常见的效果。 如果你正好用到了不妨作为参考。如果遇到了新的滤镜效果, 不妨在文末向我留言,看到后会及时回答,提前感谢你的参与。 下面介绍其他四种滤镜效果。这里只贴出核心代码,完整代码可以访问我的 Github Repo 进行查看。如果你嫌下载到本地麻烦,也可以在这里在线安装并访问,打开这个链接,分别执行yarn和yarn start即可。 以下效果均以下图为原图制作: 如何实现黑白效果 12345for (let i = 0; i < data.length; i += 4) { // 将红黄蓝按照一定比例混合,具体比例为0.299 : 0.587 : 0.114, 这个比例需要慢慢调制。 const avg = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; data[i] = data[i + 1] = data[i + 2] = avg;} 如何实现反色效果 12345for (let i = 0; i < data.length; i += 4) { data[i] = 255 - data[i]; //r data[i + 1] = 255 - data[i + 1]; //g data[i + 2] = 255 - data[i + 2]; //b} 如何给图片增加噪音 123456const random = ((Math.random() * 70) >>> 0) - 35;for (let i = 0; i < data.length; i += 4) { data[i] = data[i] + random; data[i + 1] = data[i + 1] + random; data[i + 2] = data[i + 2] + random;} 如何提高图片亮度 123456const brightness = +e.target.value;for (let i = 0; i < data.length; i += 4) { data[i] = data[i] + brightness > 255 ? 255 : data[i] + brightness; data[i + 1] = data[i + 1] + brightness > 255 ? 255 : data[i + 1] + brightness; data[i + 2] = data[i + 2] + brightness > 255 ? 255 : data[i + 2] + brightness;} 总结本文通过不到 70 行代码实现了五种滤镜效果,对于其他滤镜效果也可以参考这种方式来实现。还不赶紧拿上小姐姐的照片来秀一手么?","categories":[],"tags":[{"name":"Canvas","slug":"Canvas","permalink":"https://lucifer.ren/blog/tags/Canvas/"},{"name":"图片处理","slug":"图片处理","permalink":"https://lucifer.ren/blog/tags/图片处理/"},{"name":"滤镜","slug":"滤镜","permalink":"https://lucifer.ren/blog/tags/滤镜/"}]},{"title":"《82 年生的金智英》","slug":"82-jinzhiying","date":"2020-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.401Z","comments":true,"path":"2020/04/06/82-jinzhiying/","link":"","permalink":"https://lucifer.ren/blog/2020/04/06/82-jinzhiying/","excerpt":"《82 年生的金智英》(韩文原始名: 82년생 김지영)是一部由同名小说改编,于 2019 年 10 月 23 号在韩国上映的韩国电影。由金度英执导,郑裕美、孔刘主演。该片讲述出生于 1982 年的三十多岁平凡女性金智英,在产子后因为周围人事变化,以及家庭中婆婆等家人的言行一度造成其心理疾病,以及在其丈夫和家人的帮助下寻找自我恢复。 ​","text":"《82 年生的金智英》(韩文原始名: 82년생 김지영)是一部由同名小说改编,于 2019 年 10 月 23 号在韩国上映的韩国电影。由金度英执导,郑裕美、孔刘主演。该片讲述出生于 1982 年的三十多岁平凡女性金智英,在产子后因为周围人事变化,以及家庭中婆婆等家人的言行一度造成其心理疾病,以及在其丈夫和家人的帮助下寻找自我恢复。 ​ 评分 观后感金智英的生活状况和很多人一样。片子着重讲述的是女人的社会生活状况,包括但不限于被工作歧视,家庭重男轻女,社会中处于弱势位置。 工作中,由于是女性,会被歧视,调侃,连升职也会变得不顺利。借用片中的台词就是“和我一起的男同事,早就升到了 xx”。 重男轻女在韩国就好像之前的中国一样。是整个社会的意识,很难从根本上得到改观。”女生就应该相夫教子等“观念已经深入人心,尤其是老一辈。 社会中处弱势位置。片中讲述了小女孩被一个男孩子盯上,吓得给公交车路人发暗号,并向爸爸发信息求救。爸爸知道了还责怪女孩子不小心,这一幕既真实又令人深思。上厕所被偷拍,以至于上厕所不得不小心翼翼。 带孩子的种种艰辛,或许带过孩子的人会感同身受吧。 我看的时候弹幕一直在刷”我一定要对我的老婆好“,”只恋爱不结婚“。但是话容易说,两个人只有相互理解,尊重,才是对另一方好。否则也只是自己的一厢情愿,强加于人罢了。这样的话,与其说是对别人好,倒不如说是”给自己赎罪,减轻自己的心灵负担“的自私行为罢了。 总的来说,由于社会阅历等原因,这部片子没有给我感同身受的感觉。但是确是一部反映现实,控诉社会的好电影。","categories":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/categories/电影/"},{"name":"观后感","slug":"电影/观后感","permalink":"https://lucifer.ren/blog/categories/电影/观后感/"}],"tags":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/tags/电影/"}]},{"title":"《饥饿站台》","slug":"movie-eager-game","date":"2020-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.768Z","comments":true,"path":"2020/04/06/movie-eager-game/","link":"","permalink":"https://lucifer.ren/blog/2020/04/06/movie-eager-game/","excerpt":"《饥饿站台》 (西班牙语:El hoyo)是一部 2019 年的西班牙科幻惊悚电影。导演为加尔德图.加兹特鲁—乌鲁蒂亚里,编剧为佩德罗.里书罗、大卫・狄索拉;由伊万·马萨格、安东尼亚·圣·胡安、佐里昂・埃奎勒、埃米利奥·布阿勒、亚莉珊卓·马桑凯主演。电影情节设于一个塔状的监狱中,囚犯从监狱中间逐渐下降的大平台拿取食物。2019 年 9 月 6 日于 2019 年多伦多国际影展举行首映。","text":"《饥饿站台》 (西班牙语:El hoyo)是一部 2019 年的西班牙科幻惊悚电影。导演为加尔德图.加兹特鲁—乌鲁蒂亚里,编剧为佩德罗.里书罗、大卫・狄索拉;由伊万·马萨格、安东尼亚·圣·胡安、佐里昂・埃奎勒、埃米利奥·布阿勒、亚莉珊卓·马桑凯主演。电影情节设于一个塔状的监狱中,囚犯从监狱中间逐渐下降的大平台拿取食物。2019 年 9 月 6 日于 2019 年多伦多国际影展举行首映。 评分影评网站烂番茄的 54 条评论中,其中 45 篇给出了“新鲜”的正面评价,“新鲜度”为 83%,平均分数 7.43 分(满分 10 分)。 观后感这部片子是少有的可以在国内放映的”限制级“电影。其中有很多暴力血腥以及色情内容。本部片子的主线很简单,简单到“很多人看几分钟的简介就可以了解到整部片子的内容”。 这是一个具有讽刺意味的电影 - “世间只有三类人,一类高层人,一类底层人,还有一类正在坠落。” 片中用楼层的来反应阶级,片中多次有人从上面掉下来,其中掉落的时间都是月末。或许是过惯了好日子,无法再忍受底层的艰苦,而选择了死亡。 片子有一个设定: 每个月都会重新洗牌,交换一次楼层。 片中有合作,背叛,猜忌等人性面,这在平常的生活中很难显现。这让我想起了之前看过的《欺诈游戏》,《下一层》,以及玩过的游戏《999 逃脱系列》。人与人之间,最难建立的是信任,并且信任一旦失去便很难重新建立。如果每个人都能足够信任,就不会存在下层人被饿死的局面。 实际上,这种阶层的观念是很难消除的,这是群体意识决定的。 《乌合之众》中也反复强调过群体意识和个人意识的不同,提到“群体往往呈现出“盲目”、“冲动”、“狂热”、“轻信”的特点,而统治者又是如何利用群体的这些特点建立和巩固自身统治的”。群体意识是会被”利用“的,这种利用可能是好的方向,也可能是不好的方向。群体的力量过于巨大,如同没有被驯化的野兽一般。 我不怕鬼,但是我怕扮成“鬼”的人。","categories":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/categories/电影/"},{"name":"观后感","slug":"电影/观后感","permalink":"https://lucifer.ren/blog/categories/电影/观后感/"}],"tags":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/tags/电影/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(2)","slug":"91-algo2","date":"2020-03-24T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/03/25/91-algo2/","link":"","permalink":"https://lucifer.ren/blog/2020/03/25/91-algo2/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。前天想加入却没能加入的小伙伴可以进来啦,直接扫描文末二维码即可。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。前天想加入却没能加入的小伙伴可以进来啦,直接扫描文末二维码即可。 ​ 自从前天开始,就接到了非常多的入群申请, 我一个人忙不过来, 就暂停了入群,希望大家理解。今天除了宣布微信群今天开启入群 之外,还给大家带来了几个大家问的多的问题的答案。分别是我需要提前准备什么?,具体形式是怎么样的?,我还可以入群么?。 qq 群开放时间待定,开放后会第一时间在朋友圈和微信交流群进行告知 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。1 活动时间2020-06-01 至 2020-08-30 需要提前准备些什么? 数据结构与算法的基础知识。 推荐看一下大学里面的教材讲义,或者看一些入门的图书,视频等,比如《图解算法》,邓俊辉的《数据结构与算法》免费视频课程。总之, 至少你要知道有哪些常见的数据结构与算法以及他们各自的特点。 有 Github 账号,且会使用 Github 常用操作。 比如提 issue,留言等。 有 LeetCode 账号,且会用其提交代码。 语言不限,大家可以用自己喜欢的任何语言。同时我也希望你不要纠结于语言本身。 具体形式是什么样的? 总共三个大的阶段 每个大阶段划分为几个小阶段 每个小阶段前会将这个小阶段的资料发到群里 每个小阶段的时间内,每天都会出关于这个阶段的题目,第二天进行解答 比如: 第一个大阶段是基础 基础中第一个小阶段是数组,栈和队列。 数组,栈和队列正式开始前,会将资料发到群里,大家可以提前预习。 之后的每天都会围绕数组,栈和队列出一道题,第二天进行解答。大家可以在出题当天上 Github 上打卡。 大家遇到问题可以在群里回答,对于比较好的问题,会记录到 github issue 中,让更多的人看到。Github 仓库地址届时会在群里公布。 有微信群么?有很多小伙伴反应没有 qq,或者平时不用 qq,能否提供微信群供学习。其实我的内心是拒绝的,这会增加系统复杂度。但是随着反应的人数越来越多,我决定开发微信群。还是和 qq 群一样的收费标准,,具体看下方。 如何加入微信群?添加我的微信,备注“91 算法”。那么怎么添加我呢?大家可以关注公众号脑洞前端,然后点击更多,在弹出的菜单中选择联系我即可。 冲鸭课程大纲可以点这里查看 收费标准: 前 50 人免费 51 - 100 收费 5 元 101 - 500 收费 10 元 目前已经满 100 人了。 本次活动并不是为了赚钱,而是为了给想学习的人营造一个良好的学习氛围,并且我们会对活跃的群员进行抽奖,活动基金就来源于大家的入群费。 入群截止时间: 2020-05-31 24:00:00","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"位运算","slug":"bit","date":"2020-03-23T16:00:00.000Z","updated":"2023-01-05T12:24:49.491Z","comments":true,"path":"2020/03/24/bit/","link":"","permalink":"https://lucifer.ren/blog/2020/03/24/bit/","excerpt":"我这里总结了几道位运算的题目分享给大家,分别是 136和137, 260 和 645, 总共加起来四道题。 四道题全部都是位运算的套路,如果你想练习位运算的话,不要错过哦~~","text":"我这里总结了几道位运算的题目分享给大家,分别是 136和137, 260 和 645, 总共加起来四道题。 四道题全部都是位运算的套路,如果你想练习位运算的话,不要错过哦~~ 前菜开始之前我们先了解下异或,后面会用到。 异或的性质 两个数字异或的结果a^b是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是果同一位的数字相同则为 0,不同则为 1 异或的规律 任何数和本身异或则为0 任何数和 0 异或是本身 异或运算满足交换律,即: a ^ b ^ c = a ^ c ^ b OK,我们来看下这三道题吧。 136. 只出现一次的数字1题目大意是除了一个数字出现一次,其他都出现了两次,让我们找到出现一次的数。我们执行一次全员异或即可。 123456class Solution: def singleNumber(self, nums: List[int]) -> int: single_number = 0 for num in nums: single_number ^= num return single_number 复杂度分析 时间复杂度:$O(N)$,其中N为数组长度。 空间复杂度:$O(1)$ 137. 只出现一次的数字2题目大意是除了一个数字出现一次,其他都出现了三次,让我们找到出现一次的数。 灵活运用位运算是本题的关键。 Python3: 1234567891011121314class Solution: def singleNumber(self, nums: List[int]) -> int: res = 0 for i in range(32): cnt = 0 # 记录当前 bit 有多少个1 bit = 1 << i # 记录当前要操作的 bit for num in nums: if num & bit != 0: cnt += 1 if cnt % 3 != 0: # 不等于0说明唯一出现的数字在这个 bit 上是1 res |= bit return res - 2 ** 32 if res > 2 ** 31 - 1 else res 为什么Python最后需要对返回值进行判断? 如果不这么做的话测试用例是[-2,-2,1,1,-3,1,-3,-3,-4,-2] 的时候,就会输出 4294967292。 其原因在于Python是动态类型语言,在这种情况下其会将符号位置的1看成了值,而不是当作符号“负数”。 这是不对的。 正确答案应该是 - 4,-4的二进制码是 1111…100,就变成 2^32-4=4294967292,解决办法就是 减去 2 ** 32 。 之所以这样不会有问题的原因还在于题目限定的数组范围不会超过 2 ** 32 JavaScript: 123456789101112var singleNumber = function(nums) { let res = 0; for (let i = 0; i < 32; i++) { let cnt = 0; let bit = 1 << i; for (let j = 0; j < nums.length; j++) { if (nums[j] & bit) cnt++; } if (cnt % 3 != 0) res = res | bit; } return res;}; 复杂度分析 时间复杂度:$O(N)$,其中N为数组长度。 空间复杂度:$O(1)$ 645. 错误的集合和上面的137. 只出现一次的数字2思路一样。这题没有限制空间复杂度,因此直接hashmap 存储一下没问题。 不多说了,我们来看一种空间复杂度$O(1)$的解法。 由于和137. 只出现一次的数字2思路基本一样,我直接复用了代码。具体思路是,将nums的所有索引提取出一个数组idx,那么由idx和nums组成的数组构成singleNumbers的输入,其输出是唯二不同的两个数。 但是我们不知道哪个是缺失的,哪个是重复的,因此我们需要重新进行一次遍历,判断出哪个是缺失的,哪个是重复的。 123456789101112131415161718192021222324252627282930class Solution: def singleNumbers(self, nums: List[int]) -> List[int]: ret = 0 # 所有数字异或的结果 a = 0 b = 0 for n in nums: ret ^= n # 找到第一位不是0的 h = 1 while(ret & h == 0): h <<= 1 for n in nums: # 根据该位是否为0将其分为两组 if (h & n == 0): a ^= n else: b ^= n return [a, b] def findErrorNums(self, nums: List[int]) -> List[int]: nums = [0] + nums idx = [] for i in range(len(nums)): idx.append(i) a, b = self.singleNumbers(nums + idx) for num in nums: if a == num: return [a, b] return [b, a] 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 260. 只出现一次的数字3题目大意是除了两个数字出现一次,其他都出现了两次,让我们找到这个两个数。 我们进行一次全员异或操作,得到的结果就是那两个只出现一次的不同的数字的异或结果。 我们刚才讲了异或的规律中有一个任何数和本身异或则为0, 因此我们的思路是能不能将这两个不同的数字分成两组 A 和 B。分组需要满足两个条件. 两个独特的的数字分成不同组 相同的数字分成相同组 这样每一组的数据进行异或即可得到那两个数字。 问题的关键点是我们怎么进行分组呢? 由于异或的性质是,同一位相同则为 0,不同则为 1. 我们将所有数字异或的结果一定不是 0,也就是说至少有一位是 1. 我们随便取一个, 分组的依据就来了, 就是你取的那一位是 0 分成 1 组,那一位是 1 的分成一组。这样肯定能保证2. 相同的数字分成相同组, 不同的数字会被分成不同组么。 很明显当然可以, 因此我们选择是 1,也就是说两个独特的的数字在那一位一定是不同的,因此两个独特元素一定会被分成不同组。 12345678910111213141516171819class Solution: def singleNumbers(self, nums: List[int]) -> List[int]: ret = 0 # 所有数字异或的结果 a = 0 b = 0 for n in nums: ret ^= n # 找到第一位不是0的 h = 1 while(ret & h == 0): h <<= 1 for n in nums: # 根据该位是否为0将其分为两组 if (h & n == 0): a ^= n else: b ^= n return [a, b] 复杂度分析 时间复杂度:$O(N)$,其中N为数组长度。 空间复杂度:$O(1)$ 更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经接近30K star啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解","categories":[],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"位运算","slug":"位运算","permalink":"https://lucifer.ren/blog/tags/位运算/"}]},{"title":"回炉重铸, 91 天见证不一样的自己","slug":"91-algo","date":"2020-03-22T16:00:00.000Z","updated":"2023-01-07T13:56:10.277Z","comments":true,"path":"2020/03/23/91-algo/","link":"","permalink":"https://lucifer.ren/blog/2020/03/23/91-algo/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。 活动时间2020-06-01 至 2020-08-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少参与一次打卡 违反上述条件的人员会被强制清退 课程大纲基础篇(30 天) 数组,队列,栈 链表 树与递归 哈希表 双指针 进阶篇(30 天) 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 … 专题篇(31 天) 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 … 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定的 Github 仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 第二天会对前一天的题目进行讲解。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书等 连续打卡七天可以获得补签卡一张哦 冲鸭为了大家的学习体验,防止不相关人员进群,同时为了更好地展开工作,我们决定采用 QQ 群的方式进行,希望大家能够理解哦。 对了,还有一句话,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 收费标准: 前 50 人免费 51 - 100 收费 5 元 101 - 500 收费 10 元 目前已经满 100 人了。 想要参与的小伙伴加我的 QQ:695694307,发红包拉你进群。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"一文带你看懂二叉树的序列化","slug":"serialize","date":"2020-03-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.976Z","comments":true,"path":"2020/03/20/serialize/","link":"","permalink":"https://lucifer.ren/blog/2020/03/20/serialize/","excerpt":"我们先来看下什么是序列化,以下定义来自维基百科: 序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 可见,序列化和反序列化在计算机科学中的应用还是非常广泛的。就拿 LeetCode 平台来说,其允许用户输入形如: 1[1,2,3,null,null,4,5] 这样的数据结构来描述一颗树: ([1,2,3,null,null,4,5] 对应的二叉树) 其实序列化和反序列化只是一个概念,不是一种具体的算法,而是很多的算法。并且针对不同的数据结构,算法也会不一样。本文主要讲述的是二叉树的序列化和反序列化。看完本文之后,你就可以放心大胆地去 AC 以下两道题: 449. 序列化和反序列化二叉搜索树(中等) 297. 二叉树的序列化与反序列化(困难)","text":"我们先来看下什么是序列化,以下定义来自维基百科: 序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 可见,序列化和反序列化在计算机科学中的应用还是非常广泛的。就拿 LeetCode 平台来说,其允许用户输入形如: 1[1,2,3,null,null,4,5] 这样的数据结构来描述一颗树: ([1,2,3,null,null,4,5] 对应的二叉树) 其实序列化和反序列化只是一个概念,不是一种具体的算法,而是很多的算法。并且针对不同的数据结构,算法也会不一样。本文主要讲述的是二叉树的序列化和反序列化。看完本文之后,你就可以放心大胆地去 AC 以下两道题: 449. 序列化和反序列化二叉搜索树(中等) 297. 二叉树的序列化与反序列化(困难) 前置知识阅读本文之前,需要你对树的遍历以及 BFS 和 DFS 比较熟悉。如果你还不熟悉,推荐阅读一下相关文章之后再来看。或者我这边也写了一个总结性的文章二叉树的遍历,你也可以看看。 前言我们知道:二叉树的深度优先遍历,根据访问根节点的顺序不同,可以将其分为前序遍历,中序遍历, 后序遍历。即如果先访问根节点就是前序遍历,最后访问根节点就是后序遍历,其它则是中序遍历。而左右节点的相对顺序是不会变的,一定是先左后右。 当然也可以设定为先右后左。 并且知道了三种遍历结果中的任意两种即可还原出原有的树结构。这不就是序列化和反序列化么?如果对这个比较陌生的同学建议看看我之前写的《构造二叉树系列》 有了这样一个前提之后算法就自然而然了。即先对二叉树进行两次不同的遍历,不妨假设按照前序和中序进行两次遍历。然后将两次遍历结果序列化,比如将两次遍历结果以逗号“,” join 成一个字符串。 之后将字符串反序列即可,比如将其以逗号“,” split 成一个数组。 序列化: 1234567891011121314class Solution: def preorder(self, root: TreeNode): if not root: return [] return [str(root.val)] +self. preorder(root.left) + self.preorder(root.right) def inorder(self, root: TreeNode): if not root: return [] return self.inorder(root.left) + [str(root.val)] + self.inorder(root.right) def serialize(self, root): ans = '' ans += ','.join(self.preorder(root)) ans += '$' ans += ','.join(self.inorder(root)) return ans 反序列化: 这里我直接用了力扣 105. 从前序与中序遍历序列构造二叉树 的解法,一行代码都不改。 1234567891011121314151617class Solution: def deserialize(self, data: str): preorder, inorder = data.split('$') if not preorder: return None return self.buildTree(preorder.split(','), inorder.split(',')) def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 实际上inorder 和 preorder 一定是同时为空的,因此你无论判断哪个都行 if not preorder: return None root = TreeNode(preorder[0]) i = inorder.index(root.val) root.left = self.buildTree(preorder[1:i + 1], inorder[:i]) root.right = self.buildTree(preorder[i + 1:], inorder[i+1:]) return root 实际上这个算法是不一定成立的,原因在于树的节点可能存在重复元素。也就是说我前面说的知道了三种遍历结果中的任意两种即可还原出原有的树结构是不对的,严格来说应该是如果树中不存在重复的元素,那么知道了三种遍历结果中的任意两种即可还原出原有的树结构。 聪明的你应该发现了,上面我的代码用了 i = inorder.index(root.val),如果存在重复元素,那么得到的索引 i 就可能不是准确的。但是,如果题目限定了没有重复元素则可以用这种算法。但是现实中不出现重复元素不太现实,因此需要考虑其他方法。那究竟是什么样的方法呢? 接下来进入正题。 DFS序列化我们来模仿一下力扣的记法。 比如:[1,2,3,null,null,4,5](本质上是 BFS 层次遍历),对应的树如下: 选择这种记法,而不是 DFS 的记法的原因是看起来比较直观。并不代表我们这里是要讲 BFS 的序列化和反序列化。 序列化的代码非常简单, 我们只需要在普通的遍历基础上,增加对空节点的输出即可(普通的遍历是不处理空节点的)。 比如我们都树进行一次前序遍历的同时增加空节点的处理。选择前序遍历的原因是容易知道根节点的位置,并且代码好写,不信你可以试试。 因此序列化就仅仅是普通的 DFS 而已,直接给大家看看代码。 Python 代码: 123456789101112class Codec: def serialize_dfs(self, root, ans): # 空节点也需要序列化,否则无法唯一确定一棵树,后不赘述。 if not root: return ans + '#,' # 节点之间通过逗号(,)分割 ans += str(root.val) + ',' ans = self.serialize_dfs(root.left, ans) ans = self.serialize_dfs(root.right, ans) return ans def serialize(self, root): # 由于最后会添加一个额外的逗号,因此需要去除最后一个字符,后不赘述。 return self.serialize_dfs(root, '')[:-1] Java 代码: 12345678910111213141516public class Codec { public String serialize_dfs(TreeNode root, String str) { if (root == null) { str += \"None,\"; } else { str += str.valueOf(root.val) + \",\"; str = serialize_dfs(root.left, str); str = serialize_dfs(root.right, str); } return str; } public String serialize(TreeNode root) { return serialize_dfs(root, \"\"); }} [1,2,3,null,null,4,5] 会被处理为1,2,#,#,3,4,#,#,5,#,# 我们先看一个短视频: (动画来自力扣) 反序列化反序列化的第一步就是将其展开。以上面的例子来说,则会变成数组:[1,2,#,#,3,4,#,#,5,#,#],然后我们同样执行一次前序遍历,每次处理一个元素,重建即可。由于我们采用的前序遍历,因此第一个是根元素,下一个是其左子节点,下下一个是其右子节点。 Python 代码: 1234567891011121314def deserialize_dfs(self, nodes): if nodes: if nodes[0] == '#': nodes.pop(0) return None root = TreeNode(nodes.pop(0)) root.left = self.deserialize_dfs(nodes) root.right = self.deserialize_dfs(nodes) return root return Nonedef deserialize(self, data: str): nodes = data.split(',') return self.deserialize_dfs(nodes) Java 代码: 12345678910111213141516171819public TreeNode deserialize_dfs(List<String> l) { if (l.get(0).equals(\"None\")) { l.remove(0); return null; } TreeNode root = new TreeNode(Integer.valueOf(l.get(0))); l.remove(0); root.left = deserialize_dfs(l); root.right = deserialize_dfs(l); return root;}public TreeNode deserialize(String data) { String[] data_array = data.split(\",\"); List<String> data_list = new LinkedList<String>(Arrays.asList(data_array)); return deserialize_dfs(data_list);} 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:空间复杂度取决于栈深度,因此空间复杂度为 $O(h)$,其中 $h$ 为树的深度。 BFS序列化实际上我们也可以使用 BFS 的方式来表示一棵树。在这一点上其实就和力扣的记法是一致的了。 我们知道层次遍历的时候实际上是有层次的。只不过有的题目需要你记录每一个节点的层次信息,有些则不需要。 这其实就是一个朴实无华的 BFS,唯一不同则是增加了空节点。 Python 代码: 1234567891011121314class Codec: def serialize(self, root): ans = '' queue = [root] while queue: node = queue.pop(0) if node: ans += str(node.val) + ',' queue.append(node.left) queue.append(node.right) else: ans += '#,' return ans[:-1] 反序列化如图有这样一棵树: 那么其层次遍历为 [1,2,3,#,#, 4, 5]。我们根据此层次遍历的结果来看下如何还原二叉树,如下是我画的一个示意图: 容易看出: level x 的节点一定指向 level x + 1 的节点,如何找到 level + 1 呢? 这很容易通过层次遍历来做到。 对于给的的 level x,从左到右依次对应 level x + 1 的节点,即第 1 个节点的左右子节点对应下一层的第 1 个和第 2 个节点,第 2 个节点的左右子节点对应下一层的第 3 个和第 4 个节点。。。 接上,其实如果你仔细观察的话,实际上 level x 和 level x + 1 的判断是无需特别判断的。我们可以把思路逆转过来:即第 1 个节点的左右子节点对应第 1 个和第 2 个节点,第 2 个节点的左右子节点对应第 3 个和第 4 个节点。。。(注意,没了下一层三个字) 因此我们的思路也是同样的 BFS,并依次连接左右节点。 Python 代码: 123456789101112131415161718192021222324252627282930def deserialize(self, data: str): if data == '#': return None # 数据准备 nodes = data.split(',') if not nodes: return None # BFS root = TreeNode(nodes[0]) queue = [root] # 已经有 root 了,因此从 1 开始 i = 1 while i < len(nodes) - 1: node = queue.pop(0) # lv = nodes[i] rv = nodes[i + 1] i += 2 # 对于给的的 level x,从左到右依次对应 level x + 1 的节点 # node 是 level x 的节点,l 和 r 则是 level x + 1 的节点 if lv != '#': l = TreeNode(lv) node.left = l queue.append(l) if rv != '#': r = TreeNode(rv) node.right = r queue.append(r) return root 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:$O(N)$,其中 $N$ 为节点的总数。 总结除了这种方法还有很多方案, 比如括号表示法。 关于这个可以参考力扣606. 根据二叉树创建字符串,这里就不再赘述了。 本文从 BFS 和 DFS 角度来思考如何序列化和反序列化一棵树。 如果用 BFS 来序列化,那么相应地也需要 BFS 来反序列化。如果用 DFS 来序列化,那么就需要用 DFS 来反序列化。 我们从马后炮的角度来说,实际上对于序列化来说,BFS 和 DFS 都比较常规。对于反序列化,大家可以像我这样举个例子,画一个图。可以先在纸上,电脑上,如果你熟悉了之后,也可以画在脑子里。 (Like This) 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"算法,序列化","slug":"算法,序列化","permalink":"https://lucifer.ren/blog/categories/算法,序列化/"},{"name":"数据结构,二叉树","slug":"数据结构,二叉树","permalink":"https://lucifer.ren/blog/categories/数据结构,二叉树/"}],"tags":[{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/tags/二叉树/"},{"name":"序列化","slug":"序列化","permalink":"https://lucifer.ren/blog/tags/序列化/"}]},{"title":"纪念LeetCode项目Star突破2W","slug":"thanksGaving-2","date":"2020-03-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.415Z","comments":true,"path":"2020/03/20/thanksGaving-2/","link":"","permalink":"https://lucifer.ren/blog/2020/03/20/thanksGaving-2/","excerpt":"假期这几天我买了《逆转裁判 123》合集,面对着这香喷喷的冷饭吃了半天。从 GBA 玩到 NDS,从 NDS 玩到 3DS, 现在 NS 虽然没有出新作有点遗憾。不过有了高清重制,也当是个回忆和收藏了 🎉🎉","text":"假期这几天我买了《逆转裁判 123》合集,面对着这香喷喷的冷饭吃了半天。从 GBA 玩到 NDS,从 NDS 玩到 3DS, 现在 NS 虽然没有出新作有点遗憾。不过有了高清重制,也当是个回忆和收藏了 🎉🎉 目前打通了第一第二关,剩下的过一段时间再玩好啦 😁 回到正题,就在今天,我的《leetcode 题解》项目成功突破 2w star, 并且现在 Github 搜索关键字”LeetCode”我的项目已经排名第一啦,这是继 1W star 之后的第二个巨大突破,非常感谢大家一路以来的支持和陪伴。 最近在写一本关于 LeetCode 题解的书,有很多人表示想买,这无形之中给了我很大的压力,名字还没定,暂时给它取一个代号《攻克 LeetCode》。 新书《攻克 LeetCode》 这里是《攻克 LeetCode》的草稿目录,目前有 20 章的内容,本书要讲的内容就是 LeetCode 上反复出现的算法,经过我进一步提炼,抽取数百道题目在这里进行讲解,帮助大家理清整体思绪,从而高效率地刷题,做到事半功倍。我这里总结了 7 个常见的数据结构和 7 个常见的算法以及 5 个常见的算法思想。 7 个数据结构分别是: 数组,栈,队列,链表,二叉树,散列表,图 7 个算法分别是:二分法,递归,回溯法,排序,双指针,滑动窗口,并查集 5 个算法思想分别是:分治,贪心,深度优先遍历,广度优先遍历,动态规划 只有掌握了这些基础的数据结构和算法,更为复杂的算法才能得心应手,事半功倍。而 LeetCode 的题目虽然不断出新,但是最终用到的算法永远是那几个,很多题目都是穿着新的衣服的老面孔了。大家学好这些基础套路之后会更加明白这个道理。 后期可能会有大幅度修改,希望大家提出宝贵意见,以特别的方式参与到这本书的编写中来。 2W star 截图 Star 曲线 (star 增长曲线图) 知乎引流上次主要讲了项目从开始建立到拥有 1W star 的经历,本次书接前文,继续讲一下后面的故事。 上回提到知乎上的“量子位”在帮我做宣传,引入了不小的流量。 我就想为什么不自己去拉流量呢?我自己以作者的角度去回答一些问题岂不是更好,更受欢迎么?于是我就开始在知乎上回答问题,很开心其中一个还获得了专业认可。 事实上并没有我想的那么好,我回答了两个 LeetCode 话题的内容,虽然也有几百的点赞和感谢,但是这离我的目标还差很远。 但是转念一想,我知乎刚起步,也没什么粉丝,并且写答案的时间也就一个月左右,这样想就好多了。 我相信将来会有更多的人看到我的答案,然后加入进来。 建立自己的博客现在我发表的文章都是在各大平台。这有一个很大的问题就是各个平台很难满足你的需求,比如按照标签,按照日期进行归档。 甚至很多平台的阅读体验很差,比如没有导航功能,广告太多等。因此我觉得自己搭建一个博客还是很有必要的,这个渠道也为我吸引了少部分的流量,目前添加的主要内容大概有: 总体上来说效果还是不错的,之后的文章会在博客首发,各个平台也会陆续更新,感兴趣的可以来个 RSS 订阅,订阅方式已经在《每日一荐 - 九月刊》里面介绍了。 GithubDaily 的 推荐GithubDaily 转载了量子位的文章也为我的仓库涨了至少几百的 star,非常感谢。GithubDaily 是一个拥有 3W 多读者的公众号,大家有兴趣的可以关注一波。 其他自媒体的推荐一些其他自媒体也会帮忙推广我的项目 口耳相传我后来才知道竟然有海外华侨和一些华人社区都能看到我了。 (一亩三分地是一个集中讨论美国加拿大留学的论坛) 另外通过朋友之间口耳相传的介绍也变得越来越多。 非常感谢大家一直以来的陪伴和支持,我们一起努力,加油 💪。 如果你还没有加入我们,看了这篇文章想加入,那么可以访问我的项目主页 leetcode 题解我在这里等着你。","categories":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/categories/日记/"},{"name":"技术","slug":"日记/技术","permalink":"https://lucifer.ren/blog/categories/日记/技术/"}],"tags":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/tags/日记/"}]},{"title":"一文带你 AC 十道题【滑动窗口】","slug":"slide-window","date":"2020-03-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.788Z","comments":true,"path":"2020/03/16/slide-window/","link":"","permalink":"https://lucifer.ren/blog/2020/03/16/slide-window/","excerpt":"笔者最早接触滑动窗口是滑动窗口协议,滑动窗口协议(Sliding Window Protocol),属于 TCP 协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。 发送方和接收方分别有一个窗口大小 w1 和 w2。窗口大小可能会根据网络流量的变化而有所不同,但是在更简单的实现中它们是固定的。窗口大小必须大于零才能进行任何操作。 我们算法中的滑动窗口也是类似,只不过包括的情况更加广泛。实际上上面的滑动窗口在某一个时刻就是固定窗口大小的滑动窗口,随着网络流量等因素改变窗口大小也会随着改变。接下来我们讲下算法中的滑动窗口。","text":"笔者最早接触滑动窗口是滑动窗口协议,滑动窗口协议(Sliding Window Protocol),属于 TCP 协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。 发送方和接收方分别有一个窗口大小 w1 和 w2。窗口大小可能会根据网络流量的变化而有所不同,但是在更简单的实现中它们是固定的。窗口大小必须大于零才能进行任何操作。 我们算法中的滑动窗口也是类似,只不过包括的情况更加广泛。实际上上面的滑动窗口在某一个时刻就是固定窗口大小的滑动窗口,随着网络流量等因素改变窗口大小也会随着改变。接下来我们讲下算法中的滑动窗口。 介绍滑动窗口是一种解决问题的思路和方法,通常用来解决一些连续问题。 比如 LeetCode 的 209. 长度最小的子数组。更多滑动窗口题目见下方题目列表。 常见套路滑动窗口主要用来处理连续问题。比如题目求解“连续子串 xxxx”,“连续子数组 xxxx”,就应该可以想到滑动窗口。能不能解决另说,但是这种敏感性还是要有的。 从类型上说主要有: 固定窗口大小 窗口大小不固定,求解最大的满足条件的窗口 窗口大小不固定,求解最小的满足条件的窗口(上面的 209 题就属于这种) 后面两种我们统称为可变窗口。当然不管是哪种类型基本的思路都是一样的,不一样的仅仅是代码细节。 固定窗口大小对于固定窗口,我们只需要固定初始化左右指针 l 和 r,分别表示的窗口的左右顶点,并且保证: l 初始化为 0 初始化 r,使得 r - l + 1 等于窗口大小 同时移动 l 和 r 判断窗口内的连续元素是否满足题目限定的条件 4.1 如果满足,再判断是否需要更新最优解,如果需要则更新最优解 4.2 如果不满足,则继续。 可变窗口大小对于可变窗口,我们同样固定初始化左右指针 l 和 r,分别表示的窗口的左右顶点。后面有所不同,我们需要保证: l 和 r 都初始化为 0 r 指针移动一步 判断窗口内的连续元素是否满足题目限定的条件 4.1 如果满足,再判断是否需要更新最优解,如果需要则更新最优解。并尝试通过移动 l 指针缩小窗口大小。循环执行 4.1 4.2 如果不满足,则继续。 形象地来看的话,就是 r 指针不停向右移动,l 指针仅仅在窗口满足条件之后才会移动,起到窗口收缩的效果。 模板代码以下是 209 题目的代码,使用 Python 编写,大家意会即可。 1234567891011class Solution: def minSubArrayLen(self, s: int, nums: List[int]) -> int: l = total = 0 ans = len(nums) + 1 for r in range(len(nums)): total += nums[r] while total >= s: ans = min(ans, r - l + 1) total -= nums[l] l += 1 return 0 if ans == len(nums) + 1 else ans 题目列表以下题目有的信息比较直接,有的题目信息比较隐蔽,需要自己发掘 【Python,JavaScript】滑动窗口(3. 无重复字符的最长子串) 76. 最小覆盖子串 209. 长度最小的子数组 【Python】滑动窗口(438. 找到字符串中所有字母异位词) 【904. 水果成篮】(Python3) 【930. 和相同的二元子数组】(Java,Python) 【992. K 个不同整数的子数组】滑动窗口(Python) 【1004. 最大连续 1 的个数 III】滑动窗口(Python3) 【1234. 替换子串得到平衡字符串】[Java/C++/Python] Sliding Window 【1248. 统计「优美子数组」】滑动窗口(Python) 扩展阅读 LeetCode Sliding Window Series Discussion","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"算法,滑动窗口","slug":"算法,滑动窗口","permalink":"https://lucifer.ren/blog/categories/算法,滑动窗口/"},{"name":"数据结构,数组","slug":"数据结构,数组","permalink":"https://lucifer.ren/blog/categories/数据结构,数组/"},{"name":"数据结构,字符串","slug":"数据结构,字符串","permalink":"https://lucifer.ren/blog/categories/数据结构,字符串/"}],"tags":[{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"}]},{"title":"【LeetCode 日记】 84. 柱状图中最大的矩形","slug":"84.largest-rectangle-in-histogram","date":"2020-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.542Z","comments":true,"path":"2020/03/04/84.largest-rectangle-in-histogram/","link":"","permalink":"https://lucifer.ren/blog/2020/03/04/84.largest-rectangle-in-histogram/","excerpt":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​","text":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​ 原题地址: https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ 题目描述`给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。 示例: 输入:[2,1,5,6,2,3]输出:10 暴力枚举 - 左右端点法(TLE)思路我们暴力尝试所有可能的矩形。由于矩阵是二维图形, 我我们可以使用左右两个端点来唯一确认一个矩阵。因此我们使用双层循环枚举所有的可能性即可。 而矩形的面积等于(右端点坐标 - 左端点坐标 + 1) * 最小的高度,最小的高度我们可以在遍历的时候顺便求出。 代码1234567891011class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, ans = len(heights), 0 if n != 0: ans = heights[0] for i in range(n): height = heights[i] for j in range(i, n): height = min(height, heights[j]) ans = max(ans, (j - i + 1) * height) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(1)$ 暴力枚举 - 中心扩展法(TLE)思路我们仍然暴力尝试所有可能的矩形。只不过我们这一次从中心向两边进行扩展。对于每一个 i,我们计算出其左边第一个高度小于它的索引 p,同样地,计算出右边第一个高度小于它的索引 q。那么以 i 为最低点能够构成的面积就是(q - p - 1) * heights[i]。 这种算法毫无疑问也是正确的。 我们证明一下,假设 f(i) 表示求以 i 为最低点的情况下,所能形成的最大矩阵面积。那么原问题转化为max(f(0), f(1), f(2), ..., f(n - 1))。 具体算法如下: 我们使用 l 和 r 数组。l[i] 表示 左边第一个高度小于它的索引,r[i] 表示 右边第一个高度小于它的索引。 我们从前往后求出 l,再从后往前计算出 r。 再次遍历求出所有的可能面积,并取出最大的。 代码1234567891011121314151617class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j -= 1 l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j += 1 r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(N)$ 优化中心扩展法(Accepted)思路实际上我们内层循环没必要一步一步移动,我们可以直接将j -= 1 改成 j = l[j], j += 1 改成 j = r[j]。 代码123456789101112131415161718class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j = l[j] l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j = r[j] r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 单调栈(Accepted)思路实际上,读完第二种方法的时候,你应该注意到了。我们的核心是求左边第一个比 i 小的和右边第一个比 i 小的。 如果你熟悉单调栈的话,那么应该会想到这是非常适合使用单调栈来处理的场景。 为了简单起见,我在 heights 首尾添加了两个哨兵元素,这样可以减少边界处理的额外代码。 代码12345678class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构,单调栈","slug":"数据结构,单调栈","permalink":"https://lucifer.ren/blog/categories/数据结构,单调栈/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/categories/Hard/"}],"tags":[{"name":"数据结构,算法,LeetCode 日记,Hard","slug":"数据结构,算法,LeetCode-日记,Hard","permalink":"https://lucifer.ren/blog/tags/数据结构,算法,LeetCode-日记,Hard/"}]},{"title":"【LeetCode 日记】85. 最大矩形","slug":"85.maximal-rectangle","date":"2020-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.911Z","comments":true,"path":"2020/03/04/85.maximal-rectangle/","link":"","permalink":"https://lucifer.ren/blog/2020/03/04/85.maximal-rectangle/","excerpt":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​","text":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​ 原题地址: https://leetcode-cn.com/problems/maximal-rectangle/ 题目描述给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 示例: 输入: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 输出:6 思路我在 【84. 柱状图中最大的矩形】多种方法(Python3) 使用了多种方法来解决。 然而在这道题,我们仍然可以使用完全一样的思路去完成。 不熟悉的可以看下我的题解。本题解是基于那道题的题解来进行的。 拿题目给的例子来说: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 我们逐行扫描得到 84. 柱状图中最大的矩形 中的 heights 数组: 这样我们就可以使用84. 柱状图中最大的矩形 中的解法来进行了,这里我们使用单调栈来解。 代码1234567891011121314151617181920212223class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans def maximalRectangle(self, matrix: List[List[str]]) -> int: m = len(matrix) if m == 0: return 0 n = len(matrix[0]) heights = [0] * n ans = 0 for i in range(m): for j in range(n): if matrix[i][j] == \"0\": heights[j] = 0 else: heights[j] += 1 ans = max(ans, self.largestRectangleArea(heights)) return ans 复杂度分析 时间复杂度:$O(M * N)$ 空间复杂度:$O(N)$ 欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构,单调栈","slug":"数据结构,单调栈","permalink":"https://lucifer.ren/blog/categories/数据结构,单调栈/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/categories/Hard/"}],"tags":[{"name":"数据结构,算法,LeetCode 日记,Hard","slug":"数据结构,算法,LeetCode-日记,Hard","permalink":"https://lucifer.ren/blog/tags/数据结构,算法,LeetCode-日记,Hard/"}]},{"title":"每日一荐 2020-03 汇总","slug":"daily-featured-2020-03","date":"2020-03-01T16:00:00.000Z","updated":"2023-01-07T14:01:24.669Z","comments":true,"path":"2020/03/02/daily-featured-2020-03/","link":"","permalink":"https://lucifer.ren/blog/2020/03/02/daily-featured-2020-03/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2020-032020-03-31[好文]gRPC 使用 protobuf 进行数据封装(序列化和反序列化),同时使用 http2 进行数据传输,为什么不直接基于 TCP 传输呢?grpc 究竟和其他 rpc 框架,比如阿里的 dubbo,facebook 的 Thrift 有什么区别?这篇文章带你了解一下。 地址:https://mp.weixin.qq.com/s/GuMp4Z8oJv9K_MJxMptsSA 2020-03-30[好文]《吊打面试官》系列 Node.js 全栈秒杀系统。这篇文章非常详细地讲述了如何使用 nodejs 构建一个秒杀系统,文中提到的知识点,我也经常在面试中向候选人提问。 https://mp.weixin.qq.com/s/LoRr76smB-M8sNp-85wdqg 2020-03-27[库]今天给大家推荐的是一个图片上传组件 - uppload。支持: 20 多种选择文件的方式 10 种编辑文件的方式 支持自定义将文件发送到服务端 主题 插件 。。。 更重要的是其源码写的很赞,模块划分,代码解耦,以及单元测试都非常值得学习, 感兴趣的可以研究一下。 https://github.com/elninotech/uppload 2020-03-26[好文]一个外国游客来中国广州游玩,定了一家酒店,但是通过 Google 地图去找,离目的地相差几英里,原因在于 Google 使用的地图坐标系统是 WGS-84 ,而国内的比如 Baidu 地图可以很好的显示,因为其用的是 GCJ-02。一句话总结来说: 原文地址:https://abstractkitchen.com/blog/a-short-guide-to-chinese-coordinate-system/ 2020-03-25[工具]微软开源的 Puppeteer 的衍生项目 Playwirght,或许能够替代 Puppeteer。 和 Puppeteer 相比,其有以下特点: 弥补了 Puppeteer 的平台局限性,为所有热门渲染引擎提供类似的功能 和 Puppeteer 基本兼容,用户可以无痛(低痛)迁移 使用了隔离的 BrowserContext,而不是像 Puppeteer 一样共用一个 defaultBrowserContext。 项目地址: https://github.com/microsoft/playwright 2020-03-24[工具]如果你是学生党或者学术党,需要经常查找文献,那么一个文献管理工具就显得很有必要。这里推荐一个工具:Zotero。 地址: https://www.zotero.org/ 2020-03-23[好文]Elasticsearch 已经火了很多年了,现在依然可以见到他们活跃的身影。笔者公司就在用,我也参与了相关开发,其使用起来很简单,但是精通起来却不容易。而很多人正好对其不熟悉,这里正好有一个非常简单易懂的中文教程:《Elasticsearch 学习:入门篇》 地址: https://www.cyhone.com/articles/introduction-of-elasticsearch/index.html 2020-03-20[工具]这是一个在线服务,用来生成几何占位符,类似于 Github 的默认头像。 (Github 的默认头像) 使用方式也很简单,并支持多种参数: 123456789<img src=\"https://generative-placeholders.glitch.me/image?width=600&height=300&img=01\"/><img src=\"https://generative-placeholders.glitch.me/image?width=600&height=300&img=02\"/><img src=\"https://generative-placeholders.glitch.me/image?width=600&height=300&img=03\"/> 地址: https://generative-placeholders.glitch.me/ 2020-03-19[好文]政采云的前端 leader(花名堂主) 的一个关于前端基建的分享《堂主 - 如何推动前端团队的基础设施建设 | 7500 字》。如果你的团队也在做基础建设,那么或许可以帮到你,至少可以提供一些思路。 地址: https://mp.weixin.qq.com/s/2VSa3xBpy5St8G1v0RjW9g 2020-03-14[仓库]这里有一个很有意思的仓库,专门用来做远程面试,支持白板代码,视频通话,回放等功能。效果类似之前我在使用的 showmebug。 地址: https://github.com/AgoraIO-Community/2019-Hackathon-Works-CoderLane/blob/master/README.ZH.md 2020-03-13[仓库]一个可以在浏览器端压缩图片的库,从而减少网络传输,进而减小服务端的压力。 地址: https://github.com/Donaldcwl/browser-image-compression 2020-03-12[仓库]一个获取本机网卡信息的库,可以获取到 IPv4,IPv6 以及 MAC 地址。 地址: https://github.com/scravy/node-macaddress 2020-03-11[仓库]著名的知识管理平台《羽雀》就是从最开是的 CodeMirror 迁移到了 slate,slate 其实就是一个 Markdown 编辑器。 但是羽雀最终还是转向自研道路,基于浏览器的 contenteditable 实现富文本编辑器,通过 canvas 实现表格编辑器,通过 SVG 实现思维导图编辑器。 地址:https://github.com/slatedocs/slate 2020-03-10[仓库]一个可以制作类似“Github Project”效果的库。 地址: https://github.com/lourenci/react-kanban 2020-03-09[网站]之前给大家推荐了几个在线练习的网站,有算法的,有正则的,还有 git 的。今天介绍一个练习 SQL 语句的: SQLZOO 是一款很好用的 SQL 练习网站,这里都是比较常用的 SQL 命令。不仅对每个命令的用法有详细解释,每个专题后面还有题目。循序渐进,LeetCode 也有 SQL 相关的题目,不过难度一般比较大,建议大家 把 SQLZOO 刷完基础 SQL 命令再去 LeetCode 刷 SQL 题目。 网站地址:https://sqlzoo.net/ 2020-03-05[好文]Base64 编/解码器有不同实现,有的不相互兼容,如果使用了不兼容的实现,就会有 bug,比如典型的报错“Illegal base64 character a”。本文详细介绍了产生这个问题的原因,文章通俗易懂,适合新手阅读。 记一个 Base64 有关的 Bug 2020-03-03[好文]前端新建一个项目的时候,需要用到很多配置文件,通常是以。开头,因此我们也叫 dotfiles。这篇文章介绍了前端开发常见的 dotfiles,以及其简单用法,或许可以给你一点参考。而且我在我的 mac 装机教程 中也提到了 dotfiles,只不过那边的 dotfiles 更为广泛。 文章地址: https://lyreal666.com/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E9%85%8D%E7%BD%AE-react-typescript%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9Adotfiles/ 2020-03-02[好文]原文标题《使用 TypeScript 开发 Web 应用的最佳实践》。文中基本将 TS 在日常开发中的姿势都提到了,并且总结了很多坑点,并且给出了自己的探索和思考。 文章地址:https://yrq110.me/post/front-end/best-practice-of-typescript-in-webapp-developing/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2020-03","slug":"每日一荐/2020-03","permalink":"https://lucifer.ren/blog/categories/每日一荐/2020-03/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"使用web-component搭建企业级组件库","slug":"web-components-enterprize","date":"2020-02-21T16:00:00.000Z","updated":"2023-01-05T12:24:49.573Z","comments":true,"path":"2020/02/22/web-components-enterprize/","link":"","permalink":"https://lucifer.ren/blog/2020/02/22/web-components-enterprize/","excerpt":"前端目前比较主流的框架有 react,vuejs,angular 等。 我们通常去搭建组件库的时候都是基于某一种框架去搭建,比如 ant-design 是基于 react 搭建的 UI 组件库,而 elementUI 则是基于 vuejs 搭建的组件库。 虽然目前社区有相关工具,提供框架之间的转化服务,比如讲 vuejs 组件转化为 react 组件。但是毕竟是不同的框架,有不同的标准。因此框架 api 发生变动,那么你就需要重写转化逻辑,显然是不灵活的,因此我们暂不讨论这种情况。作为公司而言,就需要为不同的框架写不同的组件库,尽管逻辑都是一样的。 另外如果框架升级,比如从 1.x 升级到 2.x,那么对应组件库就需要升级,如果公司的组件库有很多(vuejs,react,angular 等),那么这种升级的概率就会更大。","text":"前端目前比较主流的框架有 react,vuejs,angular 等。 我们通常去搭建组件库的时候都是基于某一种框架去搭建,比如 ant-design 是基于 react 搭建的 UI 组件库,而 elementUI 则是基于 vuejs 搭建的组件库。 虽然目前社区有相关工具,提供框架之间的转化服务,比如讲 vuejs 组件转化为 react 组件。但是毕竟是不同的框架,有不同的标准。因此框架 api 发生变动,那么你就需要重写转化逻辑,显然是不灵活的,因此我们暂不讨论这种情况。作为公司而言,就需要为不同的框架写不同的组件库,尽管逻辑都是一样的。 另外如果框架升级,比如从 1.x 升级到 2.x,那么对应组件库就需要升级,如果公司的组件库有很多(vuejs,react,angular 等),那么这种升级的概率就会更大。 什么是 web-component?那么有没有更好的方案,一次编写,到处使用呢? 答案就是借助 web component。 Web Components 是一系列加入w3c的 HTML 和 DOM 的特性,使得开发者可以创建可复用的组件。 由于 web components 是由 w3c 组织去推动的,因此它很有可能在不久的将来成为浏览器的一个标配。 Web Components 主要由以下四个部分组成: Custom Elements – 定义新 html 元素的 api Shadow DOM – Encapsulated DOM and styling, with composition HTML Imports – Declarative methods of importing HTML documents into other documents HTML Templates – The <template> element, which allows documents to contain inert chunks of DOM web-component 有什么优点使用 web components 搭建组件库能够带来什么好处呢?前面说了,web components 是 w3c 推动的一系列的规范,它是一个标准。 如果我们使用 web components 的 api 开发一个组件,这个组件是脱离框架存在的,也就是说你可以在任何框架中使用它,当然也可以直接在原生 js 中使用。 我们无须为不同的框架编写不同的组件库。 使用 web components 编写的组件库的基本使用方法大概是这样的: 1234<script src=\"/build/duiba.js\"></script><!-- 运营位组件 --><operation-list></operation-list> 毫不夸张地说, web components 就是未来。 但是 web components 的 api 还是相对复杂的,因此用原生的 api 开发 web components 还是相对比较复杂的,就好像你直接用原生 canvas api 去开发游戏一样。 下面我们介绍下用于简化 web components 开发的库。 polymerpolymer 是我接触的第一个 web componment 开发库,那已经是很多年前的往事了。 Build modern apps using web components 更多介绍polymer stencilstencil 是在 polymer 之后出现的一个库。第一次接触时在 Polymer Summit 2017 的分享上,这里贴下地址Using Web Components in Ionic - Polymer Summit 2017。 Stencil is a tool developers use to create Web Components with some powerful features baked in, but it gets out of the way at runtime. 那么 powerful features 具体指的是什么? 12345Virtual DOMAsync rendering (inspired by React Fiber)Reactive data-bindingTypeScriptJSX 它也是一个用于生成 web compoennt 的 tool。 不同的是她提供了更多的特性(Reactive data-binding,TypeScript,JSX, virtual dom)以及更强的性能(virtual dom, Async rendering). 细心的人可能已经发现了,我将 Virtual DOM 既归为特性,又归为性能,没错! Virtual DOM 提供了一种到真实 dom 的映射,使得开发者不必关心真实 dom,从这个层面讲它是特性。 从虚拟 dom 之间的 diff,并将 diff info patch 到 real dom(调和)的过程来看,它是性能。 用 stencil 开发 web components 体验大概是这样的: 12345678910111213141516171819202122import { Component, Prop, State } from \"@stencil/core\";@Component({ tag: \"my-component\", styleUrl: \"my-component.scss\",})export class MyComponent { // Indicate that name should be a property on our new component @Prop() first: string; @Prop() last: string; @State() isVisible: boolean = true; render() { return ( <p> Hello, my name is {this.first} {this.last} </p> ); }} Demo这是我基于stenciljs + storybook写的一个小例子。大家可以 clone,并运行查看效果。 duiba-components 通过这样搭建的企业级组件库,就可以轻松地为不同业务线提供基础组件库,而不必担心使用者(各个业务方)的技术栈。 将来业务方的框架升级(比如 vue1 升级到 vue2),我们的组件库照样可以使用。 可以想象,如果 es 标准发展地够好,web components 等规范也足够普及,无框架时代将会到来。 无框架,不代表不使用库。 只需要借助工具库就可以开发足够通用的组件,也不需要 babel 这样的转换器,更不需要各种 polyfill。那么开发者大概会非常幸福吧,可惜这样的日子不可能存在,但是离这个目标足够近也是极好的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"组件化","slug":"前端/组件化","permalink":"https://lucifer.ren/blog/categories/前端/组件化/"},{"name":"web-component","slug":"前端/web-component","permalink":"https://lucifer.ren/blog/categories/前端/web-component/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"组件化","slug":"组件化","permalink":"https://lucifer.ren/blog/tags/组件化/"},{"name":"web-component","slug":"web-component","permalink":"https://lucifer.ren/blog/tags/web-component/"}]},{"title":"文科生都能看懂的循环移位算法","slug":"rotate-list","date":"2020-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:50.093Z","comments":true,"path":"2020/02/20/rotate-list/","link":"","permalink":"https://lucifer.ren/blog/2020/02/20/rotate-list/","excerpt":"循环移位问题真的是一个特别经典的问题了,今天我们就来攻克它。 循环移位的表现形式有很多种,就数据结构来说包括数组,字符串,链表等。就算法来说,有包含问题,直接移动问题,还有查找问题等。 虽然表现形式有很多,但是本质都是一样的,因为从逻辑上来讲其实他们都是线性数据结构,那么让我们来看一下吧。","text":"循环移位问题真的是一个特别经典的问题了,今天我们就来攻克它。 循环移位的表现形式有很多种,就数据结构来说包括数组,字符串,链表等。就算法来说,有包含问题,直接移动问题,还有查找问题等。 虽然表现形式有很多,但是本质都是一样的,因为从逻辑上来讲其实他们都是线性数据结构,那么让我们来看一下吧。 数组循环移位LeetCode 和 编程之美等都有这道题目,题目难度为 Easy。LeeCode 链接 题目描述123456789101112131415161718192021给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。示例 1:输入: [1,2,3,4,5,6,7] 和 k = 3输出: [5,6,7,1,2,3,4]解释:向右旋转 1 步: [7,1,2,3,4,5,6]向右旋转 2 步: [6,7,1,2,3,4,5]向右旋转 3 步: [5,6,7,1,2,3,4]示例 2:输入: [-1,-100,3,99] 和 k = 2输出: [3,99,-1,-100]解释:向右旋转 1 步: [99,-1,-100,3]向右旋转 2 步: [3,99,-1,-100]说明:尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。要求使用空间复杂度为 O(1) 的 原地 算法。 不符合题意的解法如果你拿到这道题没有思路,不要紧张,因为你不是一个人。 让我们先不要管题目的时间和空间复杂度的限制, 来用最最普通的方式实现它,看能不能得出一点思路。 最简单的做法就是新开辟一个完全一样的数组,然后每次移动的时候从 copy 的数组中取即可,由于新开辟的数组不会被改变,因此这种做法可行,我们直接看下代码: 1234567891011function RShift(list, k) { // 空间复杂度O(n) // 时间复杂度O(n) const copy = [...list]; const n = list.length; for (let i = 0; i < n; i++) { list[(k + i) % n] = copy[i]; } return list;} 实际上我们还可以优化一下,如果 k 是 N 的倍数,实际上是不需要做任何移动的,因此直接返回即可。 12345function RShift(list, k) { const n = list.length; if (k % n === 0) return; // 剩下代码} 由于我们需要覆盖原来的数组,那么原来的数组中的数据就会缺失,因此我们最简单的就是开辟一个完全一样的数组,这样就避免了问题,但是这样的空间复杂度是 N。我们有没有办法优化这个过程呢? 而且如果 k 是负数呢? 这其实在考察我们思考问题的严谨性。 除此之外,我们还应该思考: k 的范围是多少?如果很大,我的算法还有效么? n 的范围是多少?如果很大,我的算法还有效么? 上面两个问题的答案都是有效。 因为 k 就算再大,我们只需要求模,求模的值当成新的 k 即可。因此 k 最大不过就是 n。 如果 n 很大,由于我们的算法是 O(N)的复杂度,也就是线性,这个复杂度还是比较理想的。 时间换空间我们来试一下常数空间复杂度的解法,这种做法思路很简单,我们只需要每次移动一位,移动 k 次即可,移动一次的时间复杂度是 1,k 次共用一个变量即可,因此总的空间复杂度可以降低到 1。 我们来看下代码,这次我们把上面提到的 k 为负数的情况考虑进去。 123456789101112131415161718function RShift(list, k) { const n = list.length; if (k % n === 0) return; let cnt = Math.abs(k > 0 ? k % n : n + (k % n)); let t = null; while (cnt--) { t = list[n - 1]; // 右移一位 for (let i = n - 1; i > 0; i--) { list[i] = list[i - 1]; } list[0] = t; } return list;} 虽然上面的解法是常数空间复杂度,但是时间复杂度是 O(N * K),K 取值不限制的话,就是 O(N^2),还是不满足题意。不过没关系,我们继续思考。 我们再来看一种空间换时间的做法,这种做法的思路是拼接一个完全一样的数组到当前数组的尾部,然后问题就转化为截取数组使之满足右移的效果,这样的时间复杂度 O(N),空间复杂度是 O(N). 我们看下代码: 12345function RShift(list, k) { const n = list.length; let i = Math.abs(k > 0 ? k % n : n + (k % n)); return list.concat([...list]).slice(n - i, n * 2 - i);} 哈哈,虽然离题目越来越远了,但是扩展了思路,也不错,这就是刷题的乐趣。 三次翻转法我们来看下另外一种方法 - 经典的三次翻转法,我们可以这么做: 先把[0, n - k - 1]翻转 然后把[n - k, n - 1]翻转 最后把[0, n - 1]翻转 12345678910111213141516171819function reverse(list, start, end) { let t = null; while (start < end) { t = list[start]; list[start] = list[end]; list[end] = t; start++; end--; }}function RShift(list, k) { const n = list.length; if (k % n === 0) return; reverse(list, 0, n - k - 1); reverse(list, n - k, n - 1); reverse(list, 0, n - 1); return list;} 这里给一个简单的数学证明: 对于[0, n - k - 1] 部分,我们翻转一次后新的坐标 y 和之前的坐标 x 的关系可以表示为y = n - 1 - k - x 对于[n - k, n -1] 部分,我们翻转一次后新的坐标 y 和之前的坐标 x 的关系可以表示为y = 2 * n - 1 - k - x 最后我们整体进行翻转的时候,新的坐标 y 和之前的坐标 x 的关系可以表示为 y = n - 1 - (n - 1 - k - x) 即 y = k + x (0 <= x <= n - k - 1) y = n - 1 - (2 * n - 1 - k - x) 即 y = k + x - n (n - k <= x <= n - 1) 正好满足我们的位移条件。 这种做法时间复杂度是 O(N)空间复杂度 O(1),终于满足了我们的要求。 其他类似题目推荐: Sentence Reversal 字符串循环移位字符串和数组真的是一模一样,因为字符串也可以看成是字符序列,因此字符串就是数组。本质上来说,它和数组循环移位题目没有任何区别, 现在让我们来通过一道题来感受下。 题目描述给定两个字符串 s1 和 s2,要求判定 s2 是否能被 s1 循环移位得到的字符串包含。比如,给定 s1 = AABCD 和 s2 = CDAA,返回 true 。 给定 s1 = ABCD,s2 = ACBD, 则返回 false。 题目来自《编程之美》 暴力法s1 我们每次移动一位,然后判断逐一判断以每一个位置开头的字符串是否包含 s2,如果包含则返回 true,否则继续匹配。 这种做法很暴力,时间复杂度 O(n^2),在 n 特别大的时候不是很有效。 代码如下: 1234567891011121314151617181920212223242526function RIncludes(s1, s2) { const n = s1.length; const m = s2.length; let t = null; let p1; // s1的指针 let p2; // s2的指针 for (let i = 0; i < n; i++) { t = s1[0]; for (let j = 0; j < n - 1; j++) { s1[j] = s1[j + 1]; } s1[n - 1] = t; p1 = 0; p2 = 0; while (p1 < n && p2 < m) { if (s1[p1] === s2[p2]) { p1++; p2++; } else break; } if (p2 === m) return true; } return false;} 巧用模运算另外一种方法就是上面那种空间换时间的方式,我们将两个 s1 连接到一起,然后直接双指针即可,这里不再赘述。 这种方法虽然巧妙,但是我们花费了额外的 N 的空间,能否不借助额外的空间呢?答案是可以的,我们可以假想已经存在了另外一个相同的 s1,并且我们将它连接到 s1 的末尾。注意这里是假想,实际不存在,因此空间复杂度是 O(1)。那么如何实现呢? 答案还是利用求模。 1234567891011121314151617181920212223function RIncludes(s1, s2) { const n = s1.length; const m = s2.length; let t = null; let p1; // s1的指针 let p2; // s2的指针 for (let i = 0; i < n; i++) { p1 = i; // 这一行代码变了 p2 = 0; while (p1 < 2 * n && p2 < m) { // 不需要循环移动一位了,也就是说省了一个N的循环 if (s1[p1 % n] === s2[p2]) { // 这一行代码变了 p1++; p2++; } else break; } if (p2 === m) return true; } return false;} 至此,这道题就告一段落了,大家如果有别的方法,也欢迎在评论区留言。 链表循环移位链表不同于前面的数组和字符串,我们来个题目感受一下。 这里出一个 LeetCode 题目,官方难度为中等难度的一个题 - 61. 旋转链表 题目描述123456789101112131415161718给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。示例 1:输入: 1->2->3->4->5->NULL, k = 2输出: 4->5->1->2->3->NULL解释:向右旋转 1 步: 5->1->2->3->4->NULL向右旋转 2 步: 4->5->1->2->3->NULL示例 2:输入: 0->1->2->NULL, k = 4输出: 2->0->1->NULL解释:向右旋转 1 步: 2->0->1->NULL向右旋转 2 步: 1->2->0->NULL向右旋转 3 步: 0->1->2->NULL向右旋转 4 步: 2->0->1->NULL 思路其实这个思路简单,就是先找到断点,然后重新拼接链表即可。这个断点其实就是第n - k % n个节点, 其中 k 为右移的位数,n 为链表长度。这里取模的原因和上面一样,为了防止 k 过大做的无谓运算。但是这道题目限定了 k 是非负数,那么我们就不需要做这个判断了。 如图所示: 代码也很简单,我们来看下。 代码1234567891011121314151617181920212223var rotateRight = function (head, k) { if (head === null || head.next === null) return head; let p1 = head; let n = 1; let res = null; while (p1 && p1.next) { p1 = p1.next; n++; } let cur = 1; let p2 = head; while (cur < n - (k % n)) { p2 = p2.next; cur++; } p1.next = head; res = p2.next; p2.next = null; return res;}; 扩展 循环移位的有序数组,查找某一个值,要求时间复杂度为 O(logn) 这道题我在《每日一题》出过","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"字符串","slug":"数据结构/字符串","permalink":"https://lucifer.ren/blog/categories/数据结构/字符串/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"循环移位","slug":"算法/循环移位","permalink":"https://lucifer.ren/blog/categories/算法/循环移位/"},{"name":"链表","slug":"数据结构/链表","permalink":"https://lucifer.ren/blog/categories/数据结构/链表/"},{"name":"编程之美","slug":"编程之美","permalink":"https://lucifer.ren/blog/categories/编程之美/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"数组","slug":"数组","permalink":"https://lucifer.ren/blog/tags/数组/"},{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/tags/链表/"},{"name":"循环移位","slug":"循环移位","permalink":"https://lucifer.ren/blog/tags/循环移位/"},{"name":"字符串","slug":"字符串","permalink":"https://lucifer.ren/blog/tags/字符串/"},{"name":"编程之美","slug":"编程之美","permalink":"https://lucifer.ren/blog/tags/编程之美/"}]},{"title":"致作者","slug":"to-author","date":"2020-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.907Z","comments":true,"path":"2020/02/20/to-author/","link":"","permalink":"https://lucifer.ren/blog/2020/02/20/to-author/","excerpt":"写给我敬爱的作者们,关于“云写书”的初衷,愿景,规划以及具体细节。","text":"写给我敬爱的作者们,关于“云写书”的初衷,愿景,规划以及具体细节。 初衷建立作者群,让大家参与进来的目的有三个。 缩短写作的周期 我希望这本书尽快与大家见面,毕竟时间就是金钱,一个人的力量还是很有限的,目前计划作者控制在 2-10 个人,参与人数不限制。 结识志同道合的朋友,将来可以继续合作 本来计划写别的书的,只是突然觉得 LeetCode 题解这块受众更大,大家普遍希望有这么一本书,因此才决定先写这本。我也希望之后写别的书的时候大家也可以在一起合作。 更好地推广 作者们也可以起到很好地宣传作用,毕竟是自己深度参与的书,大家宣传的意愿很会有的。 愿景背靠着 Github LeetCode 排名第一的项目,再加上多个媒体平台的宣传推广,我个人觉得市场还是有的,另外市面上的多是以数据结构和算法为基础进行讲解,而不是 LeetCode 题解方面,这方面我认为是一个缺口。 另外我看了很多相关的资料,包括电子书,实体书以及博客,官方 articles 等,决定要不就是不够系统,要不就是不够通俗易懂。 我的受众群体是想找工作的LeetCode新手,帮助他们攻克一些高频题目,掌握解题技巧,更加有效率地刷题 规划 2019-10 建立初步的成员名单,制定写作规范,联系 LeetCode 官方授权 2019-11 分配章节给大家,大家分别书写 2019-12 汇总大家的文章,进行审阅 & 校验 Code StyleTODO 文章格式TODO 大纲这个是我之前写的大纲,需要微调, 大家可以先大概看一下 https://lucifer.ren/blog/2019/10/03/draft/ 样张 一文搞懂《链表反转》 文科生都能看懂的循环移位算法 一文看懂《最大子序列和问题》 如何参与要参加写作的, 给出你写过的文章,最好 LeetCode 或者算法相关,然后等待我审核,写文章的时候语言要求 python,不会的可以花几个小时学习一下。 另外需要提供三种语言(分别是 JS,Java 和 Python)的代码到我新建的一个仓库中,专门给这本书放源码,按照语言和章节划分一下。 作者们别忘记让我拉你进组织,我们的组织是https://github.com/leetcode-book 如果语言有什么困难,直接群里沟通,我相信语言不是问题。 另外大家写题解的时候,一定少用语言特有的东西。 有能力的欢迎提供其他语言的代码实现","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"LeetCode题解书","slug":"LeetCode/LeetCode题解书","permalink":"https://lucifer.ren/blog/categories/LeetCode/LeetCode题解书/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"【算法提高班】并查集","slug":"union-find","date":"2020-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.545Z","comments":true,"path":"2020/02/20/union-find/","link":"","permalink":"https://lucifer.ren/blog/2020/02/20/union-find/","excerpt":"关于并查集的题目不少,官方给的数据是 30 道(截止 2020-02-20),但是有一些题目虽然官方没有贴并查集标签,但是使用并查集来说确非常简单。这类题目如果掌握模板,那么刷这种题会非常快,并且犯错的概率会大大降低,这就是模板的好处。","text":"关于并查集的题目不少,官方给的数据是 30 道(截止 2020-02-20),但是有一些题目虽然官方没有贴并查集标签,但是使用并查集来说确非常简单。这类题目如果掌握模板,那么刷这种题会非常快,并且犯错的概率会大大降低,这就是模板的好处。 我这里总结了几道并查集的题目: 547.朋友圈 721. 账户合并 990. 等式方程的可满足性 大家可以学了模板之后去套用一下上面的三道题,做不出来的可以看看我的题解。 并查集概述并查集算法,主要是解决图论中「动态连通性」问题的 Union-Find 算法解决的是图的动态连通性问题,这个算法本身不难,能不能应用出来主要是看你抽象问题的能力,是否能够把原始问题抽象成一个有关图论的问题。 如果你对这个算法不是很明白,推荐看一下这篇文章Union-Find 算法详解,讲的非常详细。 你可以把并查集的元素看成部门的人,几个人可以组成一个部门个数。 并查集核心的三个方法分别是union, find, connected。 union: 将两个人所在的两个部门合并成一个部门(如果两个人是相同部门,实际山不需要合并) (图来自 labuladong) find: 查找某个人的部门 leader connnected: 判断两个人是否是一个部门的 (图来自 labuladong) 模板这是一个我经常使用的模板,我会根据具体题目做细小的变化,但是大体是不变的。 12345678910111213141516class UF: parent = {} cnt = 0 def __init__(self, M): # 初始化 parent 和 cnt def find(self, x): while x != self.parent[x]: x = self.parent[x] return x def union(self, p, q): if self.connected(p, q): return self.parent[self.find(p)] = self.find(q) self.cnt -= 1 def connected(self, p, q): return self.find(p) == self.find(q) 如果你想要更好的性能,这个模板更适合你,相应地代码稍微有一点复杂。 12345678910111213141516171819202122232425class UF: parent = {} size = {} cnt = 0 def __init__(self, M): # 初始化 parent,size 和 cnt def find(self, x): while x != self.parent[x]: x = self.parent[x] # 路径压缩 self.parent[x] = self.parent[self.parent[x]]; return x def union(self, p, q): if self.connected(p, q): return # 小的树挂到大的树上, 使树尽量平衡 leader_p = self.find(p) leader_q = self.find(q) if self.size[leader_p] < self.size[leader_q]: self.parent[leader_p] = leader_q else: self.parent[leader_q] = leader_p self.cnt -= 1 def connected(self, p, q): return self.find(p) == self.find(q) 大家可以根据情况使用不同的模板。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"}]},{"title":"【LeetCode日记】 1449. 数位成本和为目标值的最大数字","slug":"1449.form-largest-integer-with-digits-that-add-up-to-target","date":"2020-02-08T16:00:00.000Z","updated":"2023-01-07T12:20:39.935Z","comments":true,"path":"2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/","link":"","permalink":"https://lucifer.ren/blog/2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/","excerpt":"这是一道难度 Hard 的经典背包问题,属于完全背包问题,来看看背包问题的套路吧~ ​","text":"这是一道难度 Hard 的经典背包问题,属于完全背包问题,来看看背包问题的套路吧~ ​ 题目地址(1449. 数位成本和为目标值的最大数字)https://leetcode-cn.com/problems/form-largest-integer-with-digits-that-add-up-to-target/ 题目描述1234567891011121314151617181920212223242526272829303132333435363738394041424344454647给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数:给当前结果添加一个数位(i + 1)的成本为 cost[i] (cost 数组下标从 0 开始)。总成本必须恰好等于 target 。添加的数位中没有数字 0 。由于答案可能会很大,请你以字符串形式返回。如果按照上述要求无法得到任何整数,请你返回 "0" 。 示例 1:输入:cost = [4,3,2,5,6,7,2,5,5], target = 9输出:"7772"解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "997" 也是满足要求的数字,但 "7772" 是较大的数字。 数字 成本 1 -> 4 2 -> 3 3 -> 2 4 -> 5 5 -> 6 6 -> 7 7 -> 2 8 -> 5 9 -> 5示例 2:输入:cost = [7,6,5,5,5,6,8,7,8], target = 12输出:"85"解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12 。示例 3:输入:cost = [2,4,6,2,4,6,4,4,4], target = 5输出:"0"解释:总成本是 target 的条件下,无法生成任何整数。示例 4:输入:cost = [6,10,15,40,40,40,40,40,40], target = 47输出:"32211" 提示:cost.length == 91 <= cost[i] <= 50001 <= target <= 5000 思路由于数组可以重复选择,因此这是一个完全背包问题。 01 背包对于 01 背包问题,我们的套路是: 123for i in 0 to N: for j in 1 to V + 1: dp[j] = max(dp[j], dp[j - cost[i]) 而一般我们为了处理边界问题,我们一般会这么写代码: 1234for i in 1 to N + 1: # 这里是倒序的,原因在于这里是01背包。 for j in V to 0: dp[j] = max(dp[j], dp[j - cost[i - 1]) 其中 dp[j] 表示只能选择前 i 个物品,背包容量为 j 的情况下,能够获得的最大价值。 dp[j] 不是没 i 么? 其实我这里 i 指的是 dp[j]当前所处的循环中的 i 值 完全背包问题回到问题,我们这是完全背包问题: 1234for i in 1 to N + 1: # 这里不是倒序,原因是我们这里是完全背包问题 for j in 1 to V + 1: dp[j] = max(dp[j], dp[j - cost[i - 1]) 为什么 01 背包需要倒序,而完全背包则不可以实际上,这是一个骚操作,我来详细给你讲一下。 其实要回答这个问题,我要先将 01 背包和完全背包退化二维的情况。 对于 01 背包: 123for i in 1 to N + 1: for j in V to 0: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cost[i - 1]) 注意等号左边是 i,右边是 i - 1,这很好理解,因为 i 只能取一次嘛。 那么如果我们不降序遍历会怎么样呢? 如图橙色部分表示已经遍历的部分,而让我们去用[j - cost[i - 1]] 往前面回溯的时候,实际上回溯的是 dp[i]j - cost[i - 1]],而不是 dp[i - 1]j - cost[i - 1]]。 如果是降序就可以了,如图: 这个明白的话,我们继续思考为什么完全背包就要不降序了呢? 我们还是像上面一样写出二维的代码: 123for i in 1 to N + 1: for j in 1 to V + 1: dp[i][j] = max(dp[i - 1][j], dp[i][j - cost[i - 1]) 由于 i 可以取无数次,那么正序遍历正好可以满足,如上图。 恰好装满 VS 可以不装满题目有两种可能,一种是要求背包恰好装满,一种是可以不装满(只要不超过容量就行)。而本题是要求恰好装满的。而这两种情况仅仅影响我们dp数组初始化。 恰好装满。只需要初始化 dp[0] 为 0, 其他初始化为负数即可。 可以不装满。 只需要全部初始化为 0,即可, 原因很简单,我多次强调过 dp 数组本质上是记录了一个个自问题。 dp[0]是一个子问题,dp[1]是一个子问题。。。 有了上面的知识就不难理解了。 初始化的时候,我们还没有进行任何选择,那么也就是说 dp[0] = 0,因为我们可以通过什么都不选达到最大值 0。而 dp[1],dp[2]…则在当前什么都不选的情况下无法达成,也就是无解,因为为了区分,我们可以用负数来表示,当然你可以用任何可以区分的东西表示,比如 None。 回到本题而这道题和普通的完全背包不一样,这个是选择一个组成的最大数。由小学数学知识一个数字的全排列中,按照数字降序排列是最大的,我这里用了一个骚操作,那就是 cost 从后往前遍历,因为后面表示的数字大。 代码12345678class Solution: def largestNumber(self, cost: List[int], target: int) -> str: dp = [0] + [float('-inf')] * target for i in range(9, 0, -1): for j in range(1, target+1): if j >= cost[i - 1]: dp[j] = max(dp[j], (dp[j-cost[i - 1]] * 10) + i) return str(dp[target]) if dp[target] > 0 else '0' _复杂度分析_ 时间复杂度:$O(target))$ 空间复杂度:$O(target)$ 扩展最后贴几个我写过的背包问题,让大家看看历史是多么的相似。 (322. 硬币找零(完全背包问题)) 这里内外循环和本题正好是反的,我只是为了”秀技”(好玩),实际上在这里对答案并不影响。 (518. 零钱兑换 II) 这里内外循环和本题正好是反的,但是这里必须这么做,否则结果是不对的,具体可以点进去链接看我那个题解 所以这两层循环的位置起的实际作用是什么? 代表的含义有什么不同? 本质上: 123for i in 1 to N + 1: for j in V to 0: ... 这种情况选择物品 1 和物品 3(随便举的例子),是一种方式。选择物品 3 个物品 1(注意是有顺序的)是同一种方式。 原因在于你是固定物品,去扫描容量。 而: 123for j in V to 0: for i in 1 to N + 1: ... 这种情况选择物品 1 和物品 3(随便举的例子),是一种方式。选择物品 3 个物品 1(注意是有顺序的)也是一种方式。原因在于你是固定容量,去扫描物品。 因此总的来说,如果你认为[1,3]和[3,1]是一种,那么就用方法 1 的遍历,否则用方法 2。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"背包问题","slug":"算法/背包问题","permalink":"https://lucifer.ren/blog/categories/算法/背包问题/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"构造二叉树系列","slug":"构造二叉树专题","date":"2020-02-07T16:00:00.000Z","updated":"2023-01-05T12:24:50.080Z","comments":true,"path":"2020/02/08/构造二叉树专题/","link":"","permalink":"https://lucifer.ren/blog/2020/02/08/构造二叉树专题/","excerpt":"构造二叉树是一个常见的二叉树考点,相比于直接考察二叉树的遍历,这种题目的难度会更大。截止到目前(2020-02-08) LeetCode 关于构造二叉树一共有三道题目,分别是: 105. 从前序与中序遍历序列构造二叉树 106. 从中序与后序遍历序列构造二叉树 889. 根据前序和后序遍历构造二叉树 今天就让我们用一个套路一举攻破他们。 ​","text":"构造二叉树是一个常见的二叉树考点,相比于直接考察二叉树的遍历,这种题目的难度会更大。截止到目前(2020-02-08) LeetCode 关于构造二叉树一共有三道题目,分别是: 105. 从前序与中序遍历序列构造二叉树 106. 从中序与后序遍历序列构造二叉树 889. 根据前序和后序遍历构造二叉树 今天就让我们用一个套路一举攻破他们。 ​ 105. 从前序与中序遍历序列构造二叉树题目描述12345678910111213141516根据一棵树的前序遍历与中序遍历构造二叉树。注意:你可以假设树中没有重复的元素。例如,给出前序遍历 preorder = [3,9,20,15,7]中序遍历 inorder = [9,3,15,20,7]返回如下的二叉树: 3 / \\ 9 20 / \\ 15 7 思路我们以题目给出的测试用例来讲解: 前序遍历是根左右,因此 preorder 第一个元素一定整个树的根。由于题目说明了没有重复元素,因此我们可以通过 val 去 inorder 找到根在 inorder 中的索引 i。而由于中序遍历是左根右,我们容易找到 i 左边的都是左子树,i 右边都是右子树。 我使用红色表示根,蓝色表示左子树,绿色表示右子树。 根据此时的信息,我们能构造的树是这样的: 我们 preorder 继续向后移动一位,这个时候我们得到了第二个根节点”9“,实际上就是左子树的根节点。 我们 preorder 继续向后移动一位,这个时候我们得到了第二个根节点”20“,实际上就是右子树的根节点。其中右子树由于个数大于 1,我们无法确定,我们继续执行上述逻辑。 根据此时的信息,我们能构造的树是这样的: 我们不断执行上述逻辑即可。简单起见,递归的时候每次我都开辟了新的数组,这个其实是没有必要的,我们可以通过四个变量来记录 inorder 和 preorder 的起始位置即可。 代码代码支持:Python3 Python3 Code: 123456789101112class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 实际上inorder 和 postorder一定是同时为空的,因此你无论判断哪个都行 if not preorder: return None root = TreeNode(preorder[0]) i = inorder.index(root.val) root.left = self.buildTree(preorder[1:i + 1], inorder[:i]) root.right = self.buildTree(preorder[i + 1:], inorder[i+1:]) return root 复杂度分析 时间复杂度:由于每次递归我们的 inorder 和 preorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度忽略了开辟数组的内存消耗。 106. 从中序与后序遍历序列构造二叉树如果你会了上面的题目,那么这个题目对你来说也不是难事,我们来看下。 题目描述12345678910111213141516根据一棵树的中序遍历与后序遍历构造二叉树。注意:你可以假设树中没有重复的元素。例如,给出中序遍历 inorder = [9,3,15,20,7]后序遍历 postorder = [9,15,7,20,3]返回如下的二叉树: 3 / \\ 9 20 / \\ 15 7 思路我们以题目给出的测试用例来讲解: 后序遍历是左右根,因此 postorder 最后一个元素一定整个树的根。由于题目说明了没有重复元素,因此我们可以通过 val 去 inorder 找到根在 inorder 中的索引 i。而由于中序遍历是左根右,我们容易找到 i 左边的都是左子树,i 右边都是右子树。 我使用红色表示根,蓝色表示左子树,绿色表示右子树。 根据此时的信息,我们能构造的树是这样的: 其中右子树由于个数大于 1,我们无法确定,我们继续执行上述逻辑。我们 postorder 继续向前移动一位,这个时候我们得到了第二个根节点”20“,实际上就是右子树的根节点。 根据此时的信息,我们能构造的树是这样的: 我们不断执行上述逻辑即可。简单起见,递归的时候每次我都开辟了新的数组,这个其实是没有必要的,我们可以通过四个变量来记录 inorder 和 postorder 的起始位置即可。 代码代码支持:Python3 Python3 Code: 1234567891011class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: # 实际上inorder 和 postorder一定是同时为空的,因此你无论判断哪个都行 if not inorder: return None root = TreeNode(postorder[-1]) i = inorder.index(root.val) root.left = self.buildTree(inorder[:i], postorder[:i]) root.right = self.buildTree(inorder[i+1:], postorder[i:-1]) return root 复杂度分析 时间复杂度:由于每次递归我们的 inorder 和 postorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度忽略了开辟数组的内存消耗。 889. 根据前序和后序遍历构造二叉树题目描述1234567891011121314151617返回与给定的前序和后序遍历匹配的任何二叉树。 pre 和 post 遍历中的值是不同的正整数。 示例:输入:pre = [1,2,4,5,3,6,7], post = [4,5,2,6,7,3,1]输出:[1,2,3,4,5,6,7] 提示:1 <= pre.length == post.length <= 30pre[] 和 post[] 都是 1, 2, ..., pre.length 的排列每个输入保证至少有一个答案。如果有多个答案,可以返回其中一个。 思路我们以题目给出的测试用例来讲解: 前序遍历是根左右,因此 preorder 第一个元素一定整个树的根,preorder 第二个元素(如果存在的话)一定是左子树。由于题目说明了没有重复元素,因此我们可以通过 val 去 postorder 找到 pre[1]在 postorder 中的索引 i。而由于后序遍历是左右根,因此我们容易得出。 postorder 中的 0 到 i(包含)是左子树,preorder 的 1 到 i+1(包含)也是左子树。 其他部分可以参考上面两题。 代码代码支持:Python3 Python3 Code: 1234567891011121314class Solution: def constructFromPrePost(self, pre: List[int], post: List[int]) -> TreeNode: # 实际上pre 和 post一定是同时为空的,因此你无论判断哪个都行 if not pre: return None node = TreeNode(pre[0]) if len(pre) == 1: return node i = post.index(pre[1]) node.left = self.constructFromPrePost(pre[1:i + 2], post[:i + 1]) node.right = self.constructFromPrePost(pre[i + 2:], post[i + 1:-1]) return node 复杂度分析 时间复杂度:由于每次递归我们的 postorder 和 preorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度忽略了开辟数组的内存消耗。 总结如果你仔细对比一下的话,会发现我们的思路和代码几乎一模一样。注意到每次递归我们的两个数组个数都会减去 1,因此我们递归终止条件不难写出,并且递归问题规模如何缩小也很容易,那就是数组总长度减去 1。 我们拿最后一个题目来说: 12node.left = self.constructFromPrePost(pre[1:i + 2], post[:i + 1])node.right = self.constructFromPrePost(pre[i + 2:], post[i + 1:-1]) 我们发现 pre 被拆分为两份,pre[1:i + 2]和 pre[i + 2:]。很明显总数少了 1,那就是 pre 的第一个元素。 也就是说如果你写出一个,其他一个不用思考也能写出来。 而对于 post 也一样,post[:i + 1] 和 post[i + 1:-1],很明显总数少了 1,那就是 post 最后一个元素。 这个解题模板足够简洁,并且逻辑清晰,大家可以用我的模板试试~ 简单起见,递归的时候每次我都开辟了新的数组,这个其实是没有必要的,我们可以通过四个变量来记录 inorder 和 postorder 的起始位置即可, 具体见下方代码区。 代码: 1234567891011class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: def dfs(inorder, i_start, i_end, postorder, p_start, p_end): if i_start > i_end: return None if i_start == i_end: return TreeNode(inorder[i_start]) node = TreeNode(postorder[p_end]) i = inorder.index(postorder[p_end]) node.left = dfs(inorder, i_start, i - 1, postorder, p_start, p_start + (i - 1 - i_start)) node.right = dfs(inorder, i + 1, i_end, postorder, p_start + (i - 1 - i_start) + 1, p_end - 1) return node return dfs(inorder, 0, len(inorder) - 1, postorder, 0, len(postorder) - 1) updated:(有同学不懂为啥 post_end 是 post_start + i - 1 - in_start,我解释一下) 我上面提到了 实际上 inorder 的 长度和 postorder 长度是一样的。而: inorder 的长度是 i - 1 - in_start 因此 postorder 的长度也是 i - 1 - in_start postorder 的长度 = post_end - post_start 因此 post_end 就是 post_start + i - 1 - in_start 复杂度分析 时间复杂度:由于每次递归我们的 inorder 和 postorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 关注我更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/categories/二叉树/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/tags/二叉树/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"}]},{"title":"Floyd-Warshall 解题模板,助你快速AC","slug":"1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance","date":"2020-02-03T16:00:00.000Z","updated":"2023-01-07T12:20:39.932Z","comments":true,"path":"2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/","link":"","permalink":"https://lucifer.ren/blog/2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/","excerpt":"Floyd-Warshall 是解决任意两点间的最短路径的一种算法,LeetCode 有很多题目都用了,掌握这套解题模板帮你快速 AC。","text":"Floyd-Warshall 是解决任意两点间的最短路径的一种算法,LeetCode 有很多题目都用了,掌握这套解题模板帮你快速 AC。 题目地址(1334. 阈值距离内邻居最少的城市)https://leetcode-cn.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/ 题目描述123456789有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] = [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold。返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为 distanceThreshold 的城市。如果有多个这样的城市,则返回编号最大的城市。注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。 示例 1: 12345678910111213输入:n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4输出:3解释:城市分布图如上。每个城市阈值距离 distanceThreshold = 4 内的邻居城市分别是:城市 0 -> [城市 1, 城市 2] 城市 1 -> [城市 0, 城市 2, 城市 3] 城市 2 -> [城市 0, 城市 1, 城市 3] 城市 3 -> [城市 1, 城市 2] 城市 0 和 3 在阈值距离 4 以内都有 2 个邻居城市,但是我们必须返回城市 3,因为它的编号最大。示例 2: 123456789101112131415161718192021输入:n = 5, edges = [[0,1,2],[0,4,8],[1,2,3],[1,4,2],[2,3,1],[3,4,1]], distanceThreshold = 2输出:0解释:城市分布图如上。 每个城市阈值距离 distanceThreshold = 2 内的邻居城市分别是:城市 0 -> [城市 1] 城市 1 -> [城市 0, 城市 4] 城市 2 -> [城市 3, 城市 4] 城市 3 -> [城市 2, 城市 4]城市 4 -> [城市 1, 城市 2, 城市 3] 城市 0 在阈值距离 4 以内只有 1 个邻居城市。 提示:2 <= n <= 1001 <= edges.length <= n * (n - 1) / 2edges[i].length == 30 <= fromi < toi < n1 <= weighti, distanceThreshold <= 10^4所有 (fromi, toi) 都是不同的。 思路这道题的本质就是: 在一个无向图中寻找每两个城镇的最小距离,我们使用 Floyd-Warshall 算法(英语:Floyd-Warshall algorithm),中文亦称弗洛伊德算法,是解决任意两点间的最短路径的一种算法。 筛选最小距离不大于 distanceThreshold 的城镇。 统计每个城镇,其满足条件的城镇有多少个 我们找出最少的即可 Floyd-Warshall 算法的时间复杂度和空间复杂度都是$O(N^3)$, 而空间复杂度可以优化到$O(N^2)$。Floyd-Warshall 的基本思想是对于每两个点之间的最小距离,要么经过中间节点 k,要么不经过,我们取两者的最小值,这是一种动态规划思想,详细的解法可以参考Floyd-Warshall 算法(wikipedia) 代码代码支持:Python3 Python3 Code: 1234567891011121314151617181920212223242526class Solution: def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int: # 构建dist矩阵 dist = [[float('inf')] * n for _ in range(n)] for i, j, w in edges: dist[i][j] = w dist[j][i] = w for i in range(n): dist[i][i] = 0 for k in range(n): for i in range(n): for j in range(n): dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) # 过滤 res = 0 minCnt = float('inf') for i in range(n): cnt = 0 for d in dist[i]: if d <= distanceThreshold: cnt += 1 if cnt <= minCnt: minCnt = cnt res = i return res 关键点解析 Floyd-Warshall 算法 你可以将本文给的 Floyd-Warshall 算法当成一种解题模板使用","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"图","slug":"数据结构/图","permalink":"https://lucifer.ren/blog/categories/数据结构/图/"},{"name":"解题模板","slug":"解题模板","permalink":"https://lucifer.ren/blog/categories/解题模板/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"解题模板","slug":"解题模板","permalink":"https://lucifer.ren/blog/tags/解题模板/"},{"name":"图","slug":"图","permalink":"https://lucifer.ren/blog/tags/图/"},{"name":"Floyd-Warshall","slug":"Floyd-Warshall","permalink":"https://lucifer.ren/blog/tags/Floyd-Warshall/"}]},{"title":"我的日程安排表系列","slug":"leetcode-我的日程安排表系列","date":"2020-02-02T16:00:00.000Z","updated":"2023-01-07T12:34:34.361Z","comments":true,"path":"2020/02/03/leetcode-我的日程安排表系列/","link":"","permalink":"https://lucifer.ren/blog/2020/02/03/leetcode-我的日程安排表系列/","excerpt":"《我的日程安排表》截止目前(2020-02-03)在 LeetCode 上一共有三道题,其中两个中等难度,一个困难难度,分别是: 729. 我的日程安排表 I 731. 我的日程安排表 II 732. 我的日程安排表 III 另外 LeetCode 上有一个类似的系列《会议室》,截止目前(2020-02-03)有两道题目。其中一个简单一个中等,分别是: 252. 会议室 253. 会议室 II 今天我们就来攻克它们。","text":"《我的日程安排表》截止目前(2020-02-03)在 LeetCode 上一共有三道题,其中两个中等难度,一个困难难度,分别是: 729. 我的日程安排表 I 731. 我的日程安排表 II 732. 我的日程安排表 III 另外 LeetCode 上有一个类似的系列《会议室》,截止目前(2020-02-03)有两道题目。其中一个简单一个中等,分别是: 252. 会议室 253. 会议室 II 今天我们就来攻克它们。 729. 我的日程安排表 I题目地址https://leetcode-cn.com/problems/my-calendar-i 题目描述实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内没有其他安排,则可以存储这个新的日程安排。 MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。 当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订。 每次调用 MyCalendar.book 方法时,如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。 请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end) 示例 1: MyCalendar();MyCalendar.book(10, 20); // returns trueMyCalendar.book(15, 25); // returns falseMyCalendar.book(20, 30); // returns true解释:第一个日程安排可以添加到日历中. 第二个日程安排不能添加到日历中,因为时间 15 已经被第一个日程安排预定了。第三个日程安排可以添加到日历中,因为第一个日程安排并不包含时间 20 。说明: 每个测试用例,调用 MyCalendar.book 函数最多不超过 100 次。调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。 暴力法思路首先我们考虑暴力法。每插入一个元素我们都判断其是否和已有的所有课程重叠。 我们定一个函数intersected(calendar, calendars),其中 calendar 是即将要插入的课程,calendars 是已经插入的课程。 只要 calendar 和 calendars 中的任何一个课程有交叉,我们就返回 True,否则返回 False。 对于两个 calendar,我们的判断逻辑都是一样的。假设连个 calendar 分别是[s1, e1]和[s2, e2]。那么如果s1 >= e2 or s2 <= e1, 则两个课程没有交叉,可以预定,否则不可以。如图,1,2,3 可以预定,剩下的不可以。 代码是这样的: 12345678def intersected(calendar, calendars): for [start, end] in calendars: if calendar[0] >= end or calendar[1] <= start: continue else: return True return False 复杂度分析: 时间复杂度:$O(N^2)$。N 指的是日常安排的数量,对于每个新的日常安排,我们检查新的日常安排是否发生冲突来决定是否可以预订新的日常安排。 空间复杂度: $O(N)$。 这个代码写出来之后整体代码就呼之欲出了,全部代码见下方代码部分。 代码代码支持 Python3: Python3 Code: 1234567891011121314151617181920212223242526272829303132## @lc app=leetcode.cn id=729 lang=python3## [729] 我的日程安排表 I## @lc code=startclass MyCalendar: def __init__(self): self.calendars = [] def book(self, start: int, end: int) -> bool: def intersected(calendar, calendars): for [start, end] in calendars: if calendar[0] >= end or calendar[1] <= start: continue else: return True return False if intersected([start, end], self.calendars): return False self.calendars.append([start, end]) return True # Your MyCalendar object will be instantiated and called as such: # obj = MyCalendar() # param_1 = obj.book(start,end) # @lc code=end 实际上我们还可以换个角度,上面的思路判断交叉部分我们考虑的是“如何不交叉”,剩下的就是交叉。我们也可以直接考虑交叉。还是上面的例子,如果两个课程交叉,那么一定满足s1 < e2 and e1 > s2。基于此,我们写出下面的代码。 代码支持 Python3: Python3 Code: 12345678910111213141516171819202122232425## @lc app=leetcode.cn id=729 lang=python3## [729] 我的日程安排表 I## @lc code=startclass MyCalendar: def __init__(self): self.calendars = [] def book(self, start: int, end: int) -> bool: for s, e in self.calendars: if start < e and end > s: return False self.calendars.append([start, end]) return True # Your MyCalendar object will be instantiated and called as such: # obj = MyCalendar() # param_1 = obj.book(start,end) # @lc code=end 二叉查找树法思路和上面思路类似,只不过我们每次都对 calendars 进行排序,那么我们可以通过二分查找日程安排的情况来检查新日常安排是否可以预订。如果每次插入之前都进行一次排序,那么时间复杂度会很高。如图,我们的[s1,e1], [s2,e2], [s3,e3] 是按照时间顺序排好的日程安排。我们现在要插入[s,e],我们使用二分查找,找到要插入的位置,然后和插入位置的课程进行一次比对即可,这部分的时间复杂度是 O(logN)$。 我们考虑使用平衡二叉树来维护这种动态的变化,在最差的情况时间复杂度会退化到上述的$O(N^2)$,平均情况是$O(NlogN)$,其中 N 是已预订的日常安排数。 代码代码支持 Python3: Python3 Code: 1234567891011121314151617181920212223242526272829class Node: def __init__(self, start, end): self.start = start self.end = end self.left = self.right = None def insert(self, node): if node.start >= self.end: if not self.right: self.right = node return True return self.right.insert(node) elif node.end <= self.start: if not self.left: self.left = node return True return self.left.insert(node) else: return Falseclass MyCalendar(object): def __init__(self): self.root = None def book(self, start, end): if self.root is None: self.root = Node(start, end) return True return self.root.insert(Node(start, end)) 731. 我的日程安排表 II题目地址https://leetcode-cn.com/problems/my-calendar-ii 题目描述实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。 MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。 当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订。 每次调用 MyCalendar.book 方法时,如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。 请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end) 示例: MyCalendar();MyCalendar.book(10, 20); // returns trueMyCalendar.book(50, 60); // returns trueMyCalendar.book(10, 40); // returns trueMyCalendar.book(5, 15); // returns falseMyCalendar.book(5, 10); // returns trueMyCalendar.book(25, 55); // returns true解释:前两个日程安排可以添加至日历中。 第三个日程安排会导致双重预订,但可以添加至日历中。第四个日程安排活动(5,15)不能添加至日历中,因为它会导致三重预订。第五个日程安排(5,10)可以添加至日历中,因为它未使用已经双重预订的时间 10。第六个日程安排(25,55)可以添加至日历中,因为时间 [25,40] 将和第三个日程安排双重预订;时间 [40,50] 将单独预订,时间 [50,55)将和第二个日程安排双重预订。 提示: 每个测试用例,调用 MyCalendar.book 函数最多不超过 1000 次。调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。 暴力法思路暴力法和上述思路类似。但是我们多维护一个数组 intersectedCalendars 用来存储二次预定的日程安排。如果课程第一次冲突,我们将其加入 intersectedCalendars,如果和 intersectedCalendars 也冲突了,说明出现了三次预定,我们直接返回 False。 代码代码支持 Python3: Python3 Code: 123456789101112131415class MyCalendarTwo: def __init__(self): self.calendars = [] self.intersectedCalendars = [] def book(self, start: int, end: int) -> bool: for [s, e] in self.intersectedCalendars: if start < e and end > s: return False for [s, e] in self.calendars: if start < e and end > s: self.intersectedCalendars.append([max(start, s), min(end, e)]) self.calendars.append([start, end]) return True 二叉查找树法和上面的题目类似,我们仍然可以使用平衡二叉树来简化查找逻辑。具体可以参考这个 discussion>) 每次插入之前我们都需要进行一次判断,判断是否可以插入。如果不可以插入,直接返回 False,否则我们进行一次插入。 插入的时候,如果和已有的相交了,我们判断是否之前已经相交了一次,如果是返回 False,否则返回 True。关于如何判断是否和已有的相交,我们可以在 node 节点增加一个字段的方式来标记,在这里我们使用 single_overlap,True 表示产生了二次预定,False 则表示没有产生过两次及以上的预定。 代码代码支持 Python3: Python3 Code: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172class Node: def __init__(self, start, end): self.start = start self.end = end self.left = None self.right = None self.single_overlap = Falseclass MyCalendarTwo: def __init__(self): self.root = None def book(self, start, end): if not self.canInsert(start, end, self.root): return False self.root = self.insert(start, end, self.root) return True def canInsert(self, start, end, root): if not root: return True if start >= end: return True if end <= root.start: return self.canInsert(start, end, root.left) elif start >= root.end: return self.canInsert(start, end, root.right) else: if root.single_overlap: return False elif start >= root.start and end <= root.end: return True else: return self.canInsert(start, root.start, root.left) and self.canInsert(root.end, end, root.right) def insert(self, start, end, root): if not root: root = Node(start, end) return root if start >= end: return root if start >= root.end: root.right = self.insert(start, end, root.right) elif end <= root.start: root.left = self.insert(start, end, root.left) else: root.single_overlap = True a = min(root.start, start) b = max(root.start, start) c = min(root.end, end) d = max(root.end, end) root.start, root.end = b, c root.left, root.right = self.insert(a, b, root.left), self.insert(c, d, root.right) return root# Your MyCalendarTwo object will be instantiated and called as such:# obj = MyCalendarTwo()# param_1 = obj.book(start,end) 732. 我的日程安排表 III题目地址https://leetcode-cn.com/problems/my-calendar-iii/ 题目描述实现一个 MyCalendar 类来存放你的日程安排,你可以一直添加新的日程安排。 MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。 当 K 个日程安排有一些时间上的交叉时(例如 K 个日程安排都在同一时间内),就会产生 K 次预订。 每次调用 MyCalendar.book 方法时,返回一个整数 K ,表示最大的 K 次预订。 请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end) 示例 1: MyCalendarThree();MyCalendarThree.book(10, 20); // returns 1MyCalendarThree.book(50, 60); // returns 1MyCalendarThree.book(10, 40); // returns 2MyCalendarThree.book(5, 15); // returns 3MyCalendarThree.book(5, 10); // returns 3MyCalendarThree.book(25, 55); // returns 3解释:前两个日程安排可以预订并且不相交,所以最大的 K 次预订是 1。第三个日程安排[10,40]与第一个日程安排相交,最高的 K 次预订为 2。其余的日程安排的最高 K 次预订仅为 3。请注意,最后一次日程安排可能会导致局部最高 K 次预订为 2,但答案仍然是 3,原因是从开始到最后,时间[10,20],[10,40]和[5,15]仍然会导致 3 次预订。说明: 每个测试用例,调用 MyCalendar.book 函数最多不超过 400 次。调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。 二叉查找树法思路我们仍然可以使用上述的平衡二叉树的做法。只不过我们需要额外维护一个全局的最大值“k”,表示需要多少个预定。最终我们返回 k。 同时每一个 node 我们都增加一个属性 k,用来表示局部的最大值,对于每次插入,我们将 node 的 k 和全部的 k 进行比较,取出最大值即可。 代码代码支持 Python3: Python3 Code: 123456789101112131415161718192021222324252627282930313233343536373839404142434445class Node(object): def __init__(self, start, end, ktime=1): self.k = ktime self.s = start self.e = end self.right = None self.left = Noneclass MyCalendarThree(object): def __init__(self): self.root = None self.k = 0 def book(self, start, end): self.root = self.insert(self.root, start, end, 1) return self.k def insert(self, root, start, end, k): if start >= end: return root if not root: self.k = max(self.k, k) return Node(start, end, k) else: if start >= root.e: root.right = self.insert(root.right, start, end, k) return root elif end <= root.s: root.left = self.insert(root.left, start, end, k) return root else: a = min(root.s, start) b = max(root.s, start) c = min(root.e, end) d = max(root.e, end) root.left = self.insert(root.left, a, b, a == root.s and root.k or k) root.right = self.insert(root.right, c,d, d == root.e and root.k or k) root.k += k root.s = b root.e = c self.k = max(root.k, self.k) return root Count Map 法思路这个是我在看了 Discussion [C++] Map Solution, beats 95%+ 之后写的解法,解法非常巧妙。 我们使用一个 count map 来存储所有的预定,对于每次插入,我们执行count[start] += 1和count[end] -= 1。 count[t] 表示从 t 开始到下一个 t 我们有几个预定。因此我们需要对 count 进行排序才行。 我们维护一个最大值来 cnt 来表示需要的预定数。 比如预定[1,3]和[5,7],我们产生一个预定即可: 再比如预定[1,5]和[3,7],我们需要两个预定: 我们可以使用红黑树来简化时间复杂度,如果你使用的是 Java,可以直接使用现成的数据结构 TreeMap。我这里偷懒,每次都排序,时间复杂度会很高,但是可以 AC。 读到这里,你可能会发现: 这个解法似乎更具有通用型。对于第一题我们可以判断 cnt 是否小于等于 1,对于第二题我们可以判断 cnt 是否小于等于 2。 如果你不借助红黑树等数据结构直接使用 count-map 法,即每次都进行一次排序,第一题和第二题可能会直接超时。 代码代码支持 Python3: Python3 Code: 12345678910111213141516171819class MyCalendarThree: def __init__(self): self.count = dict() def book(self, start: int, end: int) -> int: self.count[start] = self.count.get(start, 0) + 1 self.count[end] = self.count.get(end, 0) - 1 cnt = 0 cur = 0 for k in sorted(self.count): cur += self.count[k] cnt = max(cnt, cur) return cnt # Your MyCalendarThree object will be instantiated and called as such: # obj = MyCalendarThree() # param_1 = obj.book(start,end) 相关题目LeetCode 上有一个类似的系列《会议室》,截止目前(2020-02-03)有两道题目。其中一个简单一个中等,解题思路非常类似,大家用这个解题思路尝试一下,检测一下自己是否已经掌握。两道题分别是: 252. 会议室 253. 会议室 II 总结我们对 LeetCode 上的专题《我的日程安排》的三道题进行了汇总。对于区间判断是否重叠,我们可以反向判断,也可以正向判断。 暴力的方法是每次对所有的课程进行判断是否重叠,这种解法可以 AC。我们也可以进一步优化,使用二叉查找树来简化时间复杂度。最后我们介绍了一种 Count-Map 方法来通用解决所有的问题,不仅可以完美解决这三道题,还可以扩展到《会议室》系列的两道题。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"}]},{"title":"BigPipe和微前端","slug":"bigpipe-and-micro-fe","date":"2020-02-01T16:00:00.000Z","updated":"2023-01-07T12:20:39.942Z","comments":true,"path":"2020/02/02/bigpipe-and-micro-fe/","link":"","permalink":"https://lucifer.ren/blog/2020/02/02/bigpipe-and-micro-fe/","excerpt":"你可能听说过 BigPipe,这是一个十多年前的技术,而 BigPipe 通常都会跟“性能优化”同时被提起。微前端也是一个很早被提出的技术,但是最近几年才开始比较流行。而目前微前端能够解决的最大的问题恐怕就是遗留系统改造。我们可以将新技术构造的系统和旧技术构造的系统完美融合到一起,彼此构建,发布,运行等不受干扰。 那么 BigPipe 究竟和微前端有什么关系呢,我为什么要把这两个放到一起来看?","text":"你可能听说过 BigPipe,这是一个十多年前的技术,而 BigPipe 通常都会跟“性能优化”同时被提起。微前端也是一个很早被提出的技术,但是最近几年才开始比较流行。而目前微前端能够解决的最大的问题恐怕就是遗留系统改造。我们可以将新技术构造的系统和旧技术构造的系统完美融合到一起,彼此构建,发布,运行等不受干扰。 那么 BigPipe 究竟和微前端有什么关系呢,我为什么要把这两个放到一起来看? 回答这个问题之前,我们先来看下什么是 BigPipe,以及什么是微前端。 BigPipeBigPipe 最早上 FaceBook 用来提升自家网站性能的一个秘密武器。其核心思想在于将页面分成若干小的构件,我们称之为 pagelet。每一个构件之间并行执行。 那么 BigPipe 做了什么?和传统方式有什么不同呢?我们知道浏览器处理我们的 HTML 文档以及其中包含的 CSS,JS 等资源的时候是从上到下串行执行的。如果我们把浏览器处理的过程划分为若干阶段(stage),那么这些阶段之间有着明显的时间先后关系。那么我们能不能将其并行化,从而减少时间呢?这就是 BigPipe 的基本思想。 话不多说,我们通过一段代码来帮助大家理解,比如你的项目首页是 home.html,大概这样子: 1234567891011121314151617<!DOCTYPE html><html> <head> <script> window.BigPipe = { render(selector, content) { document.querySelector(selector).innerHTML = content; } }; </script> </head> <body> <div id=\"pagelet1\"></div> <div id=\"pagelet2\"></div> <div id=\"pagelet3\"></div> </body></html> 浏览器首先加载过来就是一个占位元素,这部分没有 JS 和 CSS,只有 HTML 部分,因此会很快。 之后我们慢慢填充pagelet1,pagelet2, pagelet3,在用户看来,就是一种“渐进式渲染”的效果。 服务端代码大概是: 12345678910111213141516171819202122232425const app = require('express')();const fs = require('fs');// 模拟真实场景function wirteChunk(content, delay, res) { return new Promise(r => { setTimeout(function() { res.write(content); delay); })}app.get('/', function (req, res) { // 为了简化代码,直接同步读。 强烈不建议生产环境这么做! res.write(fs.readFileSync(__dirname + \"/home.html\").toString()); const p1 = wirteChunk('<script>BigPipe.render(\"#pagelet1\",\"hello\");</script>', 1000) const p2 = wirteChunk('<script>BigPipe.render(\"#pagelet2\",\"word\");</script>', 2000) const p3 = wirteChunk('<script>BigPipe.render(\"#pagelet3\",\"!\");</script>', 3000) Promise.all([p1, p2, p3]).then(res.end)});app.listen(3000); 从这里我们可以看出,BigPipe 不是框架,不是新技术。我们只需要按照这样做就行了。 这对于页面可以细分为多个块,块之间关联不大的场景非常有用。如果还是不太明白,可以看下这篇文章 -bigpipe-pipelining-web-pages-for-high-performance 说完了 BigPipe,我们再来看一下微前端。 微前端和后端微服务类似,“微前端是一种架构风格,其中众多独立交付的前端应用组合成一个大型整体。” 如果你想做微前端,一定要能够回答出这 10 个问题。 微应用的注册、异步加载和生命周期管理; 微应用之间、主从之间的消息机制; 微应用之间的安全隔离措施; 微应用的框架无关、版本无关; 微应用之间、主从之间的公共依赖的库、业务逻辑(utils)以及版本怎么管理; 微应用独立调试、和主应用联调的方式,快速定位报错(发射问题); 微应用的发布流程; 微应用打包优化问题; 微应用专有云场景的出包方案; 渐进式升级:用微应用方案平滑重构老项目。 这里有一篇文档,区别与别的微前端文章的点在于其更加靠近规范层面,而不是结合自己的业务场景做的探索。这篇文章来自于阿里团队。 文章地址: https://mp.weixin.qq.com/s/rYNsKPhw2zR84-4K62gliw 还有一篇文章也不错,一并推荐给大家 - 大前端时代下的微前端架构:实现增量升级、代码解耦、独立部署 微前端中有一个重要的需要解决的问题是子系统之间的路由。而我们的 BigPipe 如果被当作一个个子应用的,那不就是微前端中的一个点么?BigPipe 也好,微前端也好,都是一种概念,一种指导思想。微前端是不限于技术栈的, 你可以使用传统的 ssr,也可以使用 csr,也可以使用现代 csr + ssr 等,框架也可以五花八门。 如何将这些系统组合起来,并且能够有条不紊地进行合作完成一个完整的应用?这是微前端所研究和要解决的问题。 对于微前端,我们隔离各个应用的方式有几种: iframe 类似 bigpipe 这种客户端异步加载技术 web-components 不管采用哪种方式,我们的大体逻辑都是: 先加载主框架 异步加载各个子应用 只不过加载子应用,我们可以通过 iframe 去加载,也可以使用 web-component 去加载,也可以使用类似 bigpipe 的方式分段并行加载。我们甚至可以将这几者进行结合使用。而 iframe 和 web-compoents 顺带解决了诸如 js 和 css 等隔离的作用,而 bigPipe 只是对资源加载的一个有效控制,其本身并没有什么特殊含义,更不要说诸如 js 和 css 等隔离作用了。 事物关联当前端有了 Nodejs 之后,我们发现可以做的事情变多了,除了 BigPipe,我们又去做 ssr,又要做 graphql,还要做微前端,海报服务,AI 等等。当你从大的视角看的时候,会发现这些技术或多或少都有交集,比如我刚才提到的 ssr。 我们知道 ssr 中有一点就是我们先返回给用户一个有内容的 html,这个 html 在服务端生成,由于在服务端生成,因此只有样式,没有绑定事件,所以后续我们需要在客户端合成事件。 如果将上面 BigPipe 的代码拿过来看的话,会发现我们的 html markup 可以看作服务端渲染内容(可以是直接写死的,也可以是服务端动态生成的)。之后我们输出后续 pagelet 的 JS 代码到前端,前端继续去执行。基于 BigPipe 我们甚至可以控制页面优先级显示。我们再继续去看的话, BFF 常见的一个功能“合并请求”在这里扮演了什么样的角色?大家可以自己想一下。当你不断从不同角度思考问题,会发现很多东西都有关联。每一个技术背后往往都会落到几个基本的原理上。了解技术初始产生背后解决的问题对于掌握一个技术来说非常重要。","categories":[],"tags":[{"name":"BigPipe","slug":"BigPipe","permalink":"https://lucifer.ren/blog/tags/BigPipe/"},{"name":"微前端","slug":"微前端","permalink":"https://lucifer.ren/blog/tags/微前端/"}]},{"title":"我是如何刷 LeetCode 的?","slug":"how-am-I-conque-leetcode","date":"2020-02-01T16:00:00.000Z","updated":"2023-01-07T12:34:34.207Z","comments":true,"path":"2020/02/02/how-am-I-conque-leetcode/","link":"","permalink":"https://lucifer.ren/blog/2020/02/02/how-am-I-conque-leetcode/","excerpt":"我就是那个 @量子位 答案里面提到的“lucifer 小哥哥”。 我本人从开始准备算法以来,大概经过了几个月的时间,这期间自己成长了很多,从刷题菜鸡,到现在对刷题套路,题型有了自己的理解,感受还是蛮多的。我本人不是算法高手,算是勤能补拙类型。不过经过几个月的学习和练习,不仅面试变得更加得心应手,而且在工作中写更容易写出干净优雅,性能好的代码。","text":"我就是那个 @量子位 答案里面提到的“lucifer 小哥哥”。 我本人从开始准备算法以来,大概经过了几个月的时间,这期间自己成长了很多,从刷题菜鸡,到现在对刷题套路,题型有了自己的理解,感受还是蛮多的。我本人不是算法高手,算是勤能补拙类型。不过经过几个月的学习和练习,不仅面试变得更加得心应手,而且在工作中写更容易写出干净优雅,性能好的代码。 我将自己这几个月的刷题经历都整理了下来,除了给出思路和关键点,还横向地对知识点进行整理,尽量做到一题多解,多题同解。 现在 GitHub 仓库有 18k+的 ✨ , 欢迎大家关注。仓库地址: azl397985856/leetcode 那么今天我就来回答一下这个问题,谈一下我是怎么刷leetcode的。 对于我来说,刷题的过程其实就是学习数据结构和算法的过程, 不仅仅是为了刷题而刷题,这样你才能感受到刷题的乐趣。 第一遍按 tag 刷,第二遍一题多解,多题同解个人建议,第一遍刷的时候可以先快速按照 tag 过一遍,快速感受一下常见数据结构和算法的套路,这样自己有一个感性的认识。 第二遍我们就不能像第一遍那样了,这个阶段我们需要多个角度思考问题,尽量做到一题多解,多题同解。我们需要对问题的本质做一些深度的理解,将来碰到类似的问题我们才能够触类旁通。 但是很多人做了几遍,碰到新题还是没有任何头绪,这是一个常见的问题,这怎么办呢? 我们继续往下看。 艾宾浩斯记忆曲线总结并记忆是学习以及刷题过程中非常重要的一环,不管哪个阶段,我们都需要做对应的总结,这样将来我们再回过头看的时候,才能够快读拾起来。 信息输入大脑后,遗忘也就随之开始了。遗忘率随时间的流逝而先快后慢,特别是在刚刚识记的短时间里,遗忘最快,这就是著名的艾宾浩斯遗忘曲线。 anki 就是根据艾宾浩斯记忆曲线开发的一个软件,它是一个使记忆变得容易的学习软件。因为它是一个自定义多功能的记忆方式,可以大大减少你的学习时间,也可以大大提高 你的学习容量。 对于我本人而言,我在 anki 里面写了很多 leetcode 题目和套路的 Card,然后 anki 会自动帮我安排复习时间,大大减少我的认知负担,提高了我的复习效率。 这个是我的anki card 大家可以直接导入使用,但是还是建议大家自己制作卡片,毕竟每个人情况不一样,并且制作卡片的过程也是记忆的过程。 使用方法: anki - 文件 - 导入 - 下拉格式选择“打包的 anki 集合”,然后选中你下载好的文件,确定即可。 更多关于 anki 使用方法的请查看anki 官网 目前已更新卡片一览(仅列举正面) 二分法解决问题的关键点是什么,相关问题有哪些? 如何用栈的特点来简化操作, 涉及到的题目有哪些? 双指针问题的思路以及相关题目有哪些? 滑动窗口问题的思路以及相关题目有哪些? 回溯法解题的思路以及相关题目有哪些? 数论解决问题的关键点是什么,相关问题有哪些? 位运算解决问题的关键点是什么,相关问题有哪些? 殊途同归大家刷了很多题之后,就会发现来来回回,题目就那么几种类型,因此掌握已有题目类型是多么重要。那样 leetcode 出题的老师,很多也是在原有的题目基础上做了适当扩展(比如 two-sum,two-sum2,three-sum, four-sum 等等)或者改造(使得不那么一下子看出问题的本质,比如猴子吃香蕉问题)。 其中算法,主要是以下几种: 基础技巧:分治、二分、贪心 排序算法:快速排序、归并排序、计数排序 搜索算法:回溯、递归、深度优先遍历,广度优先遍历,二叉搜索树等 图论:最短路径、最小生成树 动态规划:背包问题、最长子序列 数据结构,主要有如下几种: 数组与链表:单 / 双向链表 栈与队列 哈希表 堆:最大堆 / 最小堆 树与图:最近公共祖先、并查集 字符串:前缀树(字典树) / 后缀树 (图片来自 leetcode) 坚持做到了以上几点,我们还需要坚持。这个其实是最难的,不管做什么事情,坚持都是最重要也是最难的。 为了督促自己,同时帮助大家成长,我在群里举办《每日一题》活动,每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块。 大家如果发现自己很难坚持下去,也可以加入我的群聊,我们互相监督。 另外我还专门组建了 slack 群,有兴趣的可以加群后在群里喊即可。 关注我大家可以关注我的公众号《脑洞前端》,公众号后台回复“大前端”,拉你进《大前端面试宝典 - 图解前端群》。回复“leetcode”,拉你进《leetcode 题解交流群》 最后祝大家刷题愉快,拿到自己心仪的 offer。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/categories/学习方法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/tags/学习方法/"}]},{"title":"【LeetCode日记】 335. 路径交叉","slug":"335.self-crossing","date":"2020-01-30T16:00:00.000Z","updated":"2023-01-05T12:24:49.554Z","comments":true,"path":"2020/01/31/335.self-crossing/","link":"","permalink":"https://lucifer.ren/blog/2020/01/31/335.self-crossing/","excerpt":"这是一道 Hard 难度的题目,题目的难点在于我们不可以使用额外的空间完成。让我们来看下怎么做吧。 ​","text":"这是一道 Hard 难度的题目,题目的难点在于我们不可以使用额外的空间完成。让我们来看下怎么做吧。 ​ 原题地址:https://leetcode-cn.com/problems/self-crossing/ 题目描述123456789101112131415161718192021222324252627282930313233给定一个含有 n 个正数的数组 x。从点 (0,0) 开始,先向北移动 x[0] 米,然后向西移动 x[1] 米,向南移动 x[2] 米,向东移动 x[3] 米,持续移动。也就是说,每次移动后你的方位会发生逆时针变化。编写一个 O(1) 空间复杂度的一趟扫描算法,判断你所经过的路径是否相交。 示例 1:┌───┐│ │└───┼──> │输入: [2,1,1,2]输出: true示例 2:┌──────┐│ │││└────────────>输入: [1,2,3,4]输出: false示例 3:┌───┐│ │└───┼>输入: [1,1,1,1]输出: true 思路符合直觉的做法是$O(N)$时间和空间复杂度的算法。这种算法非常简单,但是题目要求我们使用空间复杂度为$O(1)$的做法。 关于空间复杂度为$O(N)$的算法可以参考我之前的874.walking-robot-simulation。 思路基本是类似,只不过 obstacles(障碍物)不是固定的,而是我们不断遍历的时候动态生成的,我们每遇到一个点,就将其标记为 obstacle。随着算法的进行,我们的 obstacles 逐渐增大,最终和 N 一个量级。 我们考虑进行优化。我们仔细观察发现,如果想让其不相交,从大的范围来看只有两种情况: 我们画的圈不断增大。 我们画的圈不断减少。 (有没有感觉像迷宫?) 这样我们会发现,其实我们画最新一笔的时候,并不是之前画的所有的都需要考虑,我们只需要最近的几个就可以了,实际上是最近的五个,不过不知道也没关系,我们稍后会讲解。 红色部分指的是我们需要考虑的,而剩余没有被红色标注的部分则无需考虑。不是因为我们无法与之相交,而是我们一旦与之相交,则必然我们也一定会与红色标记部分相交。 然而我们画的方向也是不用考虑的。比如我当前画的方向是从左到右,那和我画的方向是从上到下有区别么?在这里是没区别的,不信我帮你将上图顺时针旋转 90 度看一下: 方向对于我们考虑是否相交没有差别。 当我们仔细思考的时候,会发现其实相交的情况只有以下几种: 这个时候代码就呼之欲出了。 我们只需要遍历数组 x,假设当前是第 i 个元素。 如果 x[i] >= x[i - 2] and x[i - 1] <= x[i - 3],则相交(第一种情况) 如果 x[i - 1] <= x[i - 3] and x[i - 2] <= x[i],则相交(第二种情况) 如果 i > 3 and x[i - 1] == x[i - 3] and x[i] + x[i - 4] == x[i - 2],则相交(第三种情况) 如果 i > 4 and x[i] + x[i - 4] >= x[i - 2] and x[i - 1] >= x[i - 3] - x[i - 5] \\ and x[i - 1] <= x[i - 3] and x[i - 2] >= x[i - 4] and x[i - 3] >= x[i - 5] ,则相交(第四种情况) 否则不相交 关键点解析 一定要画图辅助 对于这种$O(1)$空间复杂度有固定的套路。常见的有: 直接修改原数组 滑动窗口(当前状态并不是和之前所有状态有关,而是仅和某几个有关)。 我们采用的是滑动窗口。但是难点就在于我们怎么知道当前状态和哪几个有关。对于这道题来说,画图或许可以帮助你打开思路。另外面试的时候说出$O(N)$的思路也不失为一个帮助你冷静分析问题的手段。 代码代码支持:Python3 Python3 Code: 12345678910111213141516class Solution: def isSelfCrossing(self, x: List[int]) -> bool: n = len(x) if n < 4: return False for i in range(3, n): if x[i] >= x[i - 2] and x[i - 1] <= x[i - 3]: return True if x[i - 1] <= x[i - 3] and x[i - 2] <= x[i]: return True if i > 3 and x[i - 1] == x[i - 3] and x[i] + x[i - 4] == x[i - 2]: return True if i > 4 and x[i] + x[i - 4] >= x[i - 2] and x[i - 1] >= x[i - 3] - x[i - 5] \\ and x[i - 1] <= x[i - 3] and x[i - 2] >= x[i - 4] and x[i - 3] >= x[i - 5]: return True return False","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"就地算法","slug":"算法/就地算法","permalink":"https://lucifer.ren/blog/categories/算法/就地算法/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/categories/Hard/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/tags/Hard/"}]},{"title":"【LeetCode日记】 50. Pow(x, n)","slug":"50.powx-n","date":"2020-01-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.767Z","comments":true,"path":"2020/01/29/50.powx-n/","link":"","permalink":"https://lucifer.ren/blog/2020/01/29/50.powx-n/","excerpt":"这是一道让我们实现系统函数的造轮子题目,我们来看下。 ​","text":"这是一道让我们实现系统函数的造轮子题目,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/powx-n/description/ 题目描述12345678910111213141516171819实现 pow(x, n) ,即计算 x 的 n 次幂函数。示例 1:输入: 2.00000, 10输出: 1024.00000示例 2:输入: 2.10000, 3输出: 9.26100示例 3:输入: 2.00000, -2输出: 0.25000解释: 2-2 = 1/22 = 1/4 = 0.25说明:-100.0 < x < 100.0n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。 解法零 - 遍历法思路这道题是让我们实现数学函数幂,因此直接调用系统内置函数是不被允许的。 符合直觉的做法是将x乘以n次,这种做法的时间复杂度是$O(N)$。 经实际测试,这种做法果然超时了。测试用例通过 291/304,在 0.00001\\n2147483647这个测试用例挂掉了。如果是面试,这个解法可以作为一种兜底解法。 代码语言支持: Python3 Python3 Code: 12345678910class Solution: def myPow(self, x: float, n: int) -> float: if n == 0: return 1 if n < 0: return 1 / self.myPow(x, -n) res = 1 for _ in range(n): res *= x return res 解法一 - 普通递归(超时法)思路首先我们要知道: 如果想要求 x ^ 4,那么我们可以求 (x^2)^2 如果是奇数,会有一点不同。 比如 x ^ 5 就等价于 x * (x^2)^2。 当然 x ^ 5 可以等价于 (x ^ 2) ^ 2.5, 但是这不相当于直接调用了幂函数了么。对于整数,我们可以很方便的模拟,但是小数就不方便了。 我们的思路就是: 将 n 地板除 2,我们不妨设结果为 a 那么 myPow(x, n) 就等价于 myPow(x, a) * myPow(x, n - a) 很可惜这种算法也会超时,原因在于重复计算会比较多,你可以试一下缓存一下计算看能不能通过。 如果你搞不清楚有哪些重复计算,建议画图理解一下。 代码语言支持: Python3 Python3 Code: 123456789class Solution: def myPow(self, x: float, n: int) -> float: if n == 0: return 1 if n == 1: return x if n < 0: return 1 / self.myPow(x, -n) return self.myPow(x, n // 2) * self.myPow(x, n - n // 2) 解法二 - 优化递归思路上面的解法每次直接 myPow 都会调用两次自己。我们不从缓存计算角度,而是从减少这种调用的角度来优化。 我们考虑 myPow 只调用一次自身可以么? 没错,是可以的。 我们的思路就是: 如果 n 是偶数,我们将 n 折半,底数变为 x^2 如果 n 是奇数, 我们将 n 减去 1 ,底数不变,得到的结果再乘上底数 x 这样终于可以 AC。 代码语言支持: Python3 Python3 Code: 123456789class Solution: def myPow(self, x: float, n: int) -> float: if n == 0: return 1 if n == 1: return x if n < 0: return 1 / self.myPow(x, -n) return self.myPow(x _ x, n // 2) if n % 2 == 0 else x _ self.myPow(x, n - 1) 解法三 - 位运算思路我们来从位(bit)的角度来看一下这道题。如果你经常看我的题解和文章的话,可能知道我之前写过几次相关的“从位的角度思考分治法”,比如 LeetCode 458.可怜的小猪。 以 x 的 10 次方举例。10 的 2 进制是 1010,然后用 2 进制转 10 进制的方法把它展成 2 的幂次的和。 因此我们的算法就是: 不断的求解 x 的 2^0 次方,x 的 2^1 次方,x 的 2^2 次方等等。 将 n 转化为二进制表示 将 n 的二进制表示中1的位置pick 出来。比如 n 的第 i 位为 1,那么就将 x^i pick 出来。 将 pick 出来的结果相乘 这里有两个问题: 第一个问题是似乎我们需要存储 x^i 以便后续相乘的时候用到。实际上,我们并不需要这么做。我们可以采取一次遍历的方式来完成,具体看代码。 第二个问题是,如果我们从低位到高位计算的时候,我们如何判断最高位置是否为 1?我们需要一个 bitmask 来完成,这种算法我们甚至需要借助一个额外的变量。 然而我们可以 hack 一下,直接从高位到低位进行计算,这个时候我们只需要判断最后一位是否为 1 就可以了,这个就简单了,我们直接和 1 进行一次与运算即可。 代码语言支持: Python3 Python3 Code: 1234567891011class Solution: def myPow(self, x: float, n: int) -> float: if n < 0: return 1 / self.myPow(x, -n) res = 1 while n: if n & 1 == 1: res *= x x *= x n >>= 1 return res 关键点解析 超时分析 hashtable 数学分析 位运算 二进制转十进制 相关题目 458.可怜的小猪","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数学","slug":"算法/数学","permalink":"https://lucifer.ren/blog/categories/算法/数学/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"位运算","slug":"算法/位运算","permalink":"https://lucifer.ren/blog/categories/算法/位运算/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"累死累活干不过一个写PPT的","slug":"ppt-data","date":"2020-01-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.404Z","comments":true,"path":"2020/01/20/ppt-data/","link":"","permalink":"https://lucifer.ren/blog/2020/01/20/ppt-data/","excerpt":"无论是身处什么行业什么领域,数据分析越来越成为一向必不可少的技能,而运用数据思维进行决策更能产生形成高质量的决策结果。 随着互联网的不断发展和物联网设备的不断普及,我们日常生活中的各种数据被存储下来,让我们可以通过定量分析数据,利用数据实现更好的决策制定。","text":"无论是身处什么行业什么领域,数据分析越来越成为一向必不可少的技能,而运用数据思维进行决策更能产生形成高质量的决策结果。 随着互联网的不断发展和物联网设备的不断普及,我们日常生活中的各种数据被存储下来,让我们可以通过定量分析数据,利用数据实现更好的决策制定。 现在越来越多的公司开始注重这一块,一方面自建数据体系,一方面去买一些数据。而对于我们个人似乎还没有意识到或者开始挖掘数据对我们的价值。 笔者最近的工作大都是做一些基础设施搭建和流程优化相关的工作。这部分工作对很多人来说都是“隐形”的,对上层使用者来说很难有很大的感知。对于领导来说,如果你只是闷头去做事情,他们也是很难知道你干的怎么样,如果这之间再加上你没有什么反馈,就会给同事和领导一种“不靠谱”的感觉。 因此给予反馈和直观展示自己劳动成果的能力就显得非常重要。然而如果你能很好展示自己的劳动成果,那么只需要将这个给老板看就是一种很好很直观的反馈。 这篇文章,我们来谈一下,如何量化我们的工作,如何将我们的工作成果展示出来。如何让同事,让领导体会到我们工作的成果。我会通过几个例子来帮助大家快速理解,以及掌握这门“技术”。 《让数据开口说话》是我给这篇文章的标题,让数据开口说话,你就可以少说一点,并且摆数据就是摆事实,数据带来的说服力要比你说的话强很多。 关于我我是一个对技术充满兴趣的程序员, 擅长前端工程化,前端性能优化,前端标准化等。 做过.net, 搞过 Java,现在是一名前端工程师。 除了我的本职工作外,我会在开源社区进行一些输出和分享,GitHub 共计获得 1.5W star。比较受欢迎的项目有leetcode 题解 , 宇宙最强的前端面试指南 和我的第一本小书 收集数据如果让数据开头说话,那么首先第一步你要有数据。 因此我们的第一步就是收集数据,那么在这之前你需要知道你需要什么数据。这部分的内容随着每个人任务不同肯定是不一样的。因此有着很大的灵活性, 有一个指导思想就是对关键指标分解。比如我现在要做打包时间进行优化,那么打包时间由哪些时间决定。 1打包时间 = 阶段1 时间 + 阶段2 时间 + 阶段3 时间 我们减少打包时间肯定要减少其中一个或多个。 有时候我们无法找到这种简单的分解,那就教大家另外一个技巧:运用对比。 一方面可以基于时间进行对比,比如环比增长,同比增长等数据都是这么来的。 另一方面我们可以基于用户属性进行对比,比如用户年龄,性别,偏好,操作系统类型,地域属性等。 下面我举几个例子。 打包优化假如你被分配了一个任务。让你对项目的打包过程进行优化。 你需要对打包时间进行优化,减少打包的时间 你需要对打包的最终产物进行优化,减少打出的包的包体大小。 将打包变得尽可能的简单,也就说尽量减少人为的操作过程。 你接到了这样一个任务,你会如何去做? 这里我们不考虑具体的具体思路和细节。 假设你的架构思路,方案规划,各种 fallback 已经想好了。我们如何通过上面提到的让数据说话的角度来收集数据呢? 换句话说,我们需要收集哪些数据? 打包时间对于打包时间的数据,最简单的我们计算一下总体的打包时间。 最后我们只需要对比优化前后的总体打包时间差异即可。 这对于老板来说可能已经够了,但是这缺乏一些精确性,我们无法知道通过优化了哪个环节进行减少了打包时间。 因此一种简单的改进是将打包划分为多个阶段,每个阶段分别进行统计计时 ⌛️ 。 包的大小包的大小的数据其实和上面讲的打包时间思路类似。 我们当然可以只统计总体包大小。 但是为了获得更加灵活的定制和更加精确的范围我们可以对包进行一定的划分。这个划分可以是业务纬度,也可以是纯技术纬度。 打包命令这部分比较简单,我们只需要简单地统计手动操作的次数即可。 通过收集以上的数据,我们就可以用数据来表示我们的成果,让数据说话,关于如何使用这些数据,我们稍后讨论。 页面加载性能优化假如你被分配了一个任务。让你对项目的页面加载速度进行优化。你会怎么做? 这个任务有点太宽泛了,更多的时候会有一些更精确的指标,比如将网络状态为fast 3G的中端机型的白屏时间降低到3s以内。 timing性能优化的第一步就是测量,没有测量就没有优化。我们不能为了优化而优化, 而是看到了某些点需要优化才去优化的。 而发现这个点一个重要的方式就是要靠测量。 说到测量,普遍接受的方式是,在浏览器中进行打点,将浏览器打开网页的过程看作是一个旅行。那么我们每到一个地方就拍张带有时间的照片(事件),最后我们对这些照片按照时间进行排列, 然后分析哪部分是我们的瓶颈点等。 有了这些 timing 我们可以很方便的计算各项性能指标。我们还可以自定义一些我们关心的指标,比如请求时间(成功和失败分开统计),较长 js 操作时间,或者比较重要的功能等。 总之收集到这些数据之后,我们只需要根据我们的需求去定制一些指标即可。 这样我们就很容易展示出这样的画面: 人效提升假如你是一个项目的管理者,上级分配给你一个任务,要在未来几个季度去做“研发效率提升”,也就是提高“交付速度”。 你会怎么做这件事? 任务这个事情是比较主观的了,因此我们切实需要一些可以量化的东西来辅助我们。 我们考虑将需求进行拆分,变成一个个任务。一个需求可能有多个任务。 我们考虑对每一个任务进行计时,而不是需求,因为需求有太大的差异。我们可以针对任务进行分类,然后我们的目标就可以变成“减少同类任务的交付时长”。 但是这种粒度似乎还是有点大。我们可以采取标签的形式,对任务进行交叉分类。 任务纬度可能还是有点太大,我们可以采取更小的粒度划分,比如模块和组件。 这样我们的统计纬度就丰富起来了,我们不仅可以总体进行统计分析,我们还可以根据 tag 和 tag 的组合进行汇总。 比如一个典型的统计结果大概是: 12345678- task1 (tagA) - module1 (tagA) - component1 (tagB) - component2 (tagA) - module2 (tagB) - module3 (tagB)- task2 (tagA)- task3 (tagC) 比如这里有一种 tag 叫“是否复用了以前的代码”,那么我们就很容易统计出组件复用率,也就很容易很直观地知道前后的差距了。 用户拉新和留存再比如我们需要做“用户拉新和留存”,我们应该怎么做? 这个留做思考题,大家可以思考一下。 我这里抛砖引玉一下,比如我们的统计纬度可以是: 12345- 用户访问时长 (tagA)- 跳出率 (tagB)- 新用户 (tagA)- 流失的老用户 (tagB)- 地址位置 (tagA) 假如我的 tag 有两个分别是 用户 id 和时间, 我们就可以方便地统计每个用户的活动数据趋势。 让数据说话有了数据,我们如何通过数据来增强表现力呢? 一种非常有效的措施是可视化。现在的可视化引擎和工具有很多,功能也非常复杂。 但是我发现我个人需要的就那么几个,可能大家每个人需要的种类不大一样,但是我相信作为个人,你需要的种类不会很多。因此自己根据自身的实际情况,挑选适合自己的几种类型,做到迎刃有余就足够了。 对于我而言,我常用的是饼图,用来表示分布关系。 曲线图用来表示趋势。用柱状图表示对比+趋势。用热度图表示离散的数据分布等等。 我们可以使用一些现有的成熟的产品来帮助我们将刚才我们收集到的数据转化为各种图表,比如 画布 这个网站能做的图表种类比较少。 当然作为一名前端我们也可以自己写代码去更灵活地展示我们的数据,比如D3或者百度的echarts 任何类型的图表都可以做,只有你想不到,没有它做不到。 相对折中一点,我们可以选择支持代码定制的一些产品,在特殊情况我们可以自定义。 累死累活干不过做 PPT 的有了这些数据图表,是时候写一份 PPT 来秀一下了。 一种方式是使用你电脑的办公软件或者一些在线的幻灯片制作工具做,比如slides 。 另一种方式通过写代码的方式实现,作为程序员我推荐使用第二种。这里推荐一款nodejs cli 工具 nodeppt,还有另外一个JS 框架 reveal.js 。上面提到的 slides 背后的原理就是它。 总结这篇文章我主要讲述了如何量化我们的工作,并将我们的工作成果展示出来。从而摆脱“干了很多事情,却说不出来,甚至功劳被人无情拿走的尴尬局面”。 首先我们将了如何收集数据,收集数据的一些技巧,这里通过几个实际工作的例子,分别是“打包优化”,“性能优化”,“人效提升”,“用户留存” ,来帮助大家理解这个过程,掌握这个技巧。 有了数据之后,我们需要通过一些手段将其数据展示出来,给人直观的感受,最好有视觉冲击感。这里我推荐了几个工具和平台,大家可以根据自己的情况选择。 最后结合我们实际情况,PPT 是一个很好的展示自己的东西,不管是晋升还是宣传都是很好的方式,这里我也推荐了几个产品,帮助大家更快更好地将图表展示出来。 让数据开口说话,你就可以少说一点,并且摆数据就是摆事实,数据带来的说服力要比你说的话强很多。 关注我最近我重新整理了下自己的公众号,并且我还给他换了一个名字《脑洞前端》,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解前端是我的目标。 之后我的文章同步到微信公众号 脑洞前端 ,您可以关注获取最新的文章,或者和我进行交流。 交流群现在还是初级阶段,需要大家的意见和反馈,为了减少沟通成本,我组建了交流群。大家可以扫码进入 QQ 群 微信群 (由于微信的限制,100 个人以上只能邀请加入, 你可以添加我的机器人回复“大前端”拉你进群)","categories":[],"tags":[{"name":"技能","slug":"技能","permalink":"https://lucifer.ren/blog/tags/技能/"},{"name":"PPT","slug":"PPT","permalink":"https://lucifer.ren/blog/tags/PPT/"}]},{"title":"一文搞懂《链表反转》","slug":"reverseList","date":"2020-01-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.728Z","comments":true,"path":"2020/01/20/reverseList/","link":"","permalink":"https://lucifer.ren/blog/2020/01/20/reverseList/","excerpt":"翻转链表一直都是热门题目,笔者就在某大型互联网公司的面试题中碰到过这种题目,这种题目很常常见,相对应的变形和扩展也很多,今天我们就来攻克它吧。","text":"翻转链表一直都是热门题目,笔者就在某大型互联网公司的面试题中碰到过这种题目,这种题目很常常见,相对应的变形和扩展也很多,今天我们就来攻克它吧。 反转链表反转链表是这个系列中最简单的了,没有别的要求,就是将一个链表从头到尾进行反转,最后返回反转后的链表即可。 我们来看一个 LeetCode 题目, 206. 反转链表, 官方难度为 Easy。 题目描述12345678反转一个单链表。示例:输入: 1->2->3->4->5->NULL输出: 5->4->3->2->1->NULL进阶:你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 思路链表的翻转过程,初始化一个为 null 的 previous node(prev),然后遍历链表的同时,当前 node (curr)的下一个(next)指向前一个 node(prev), 在改变当前 node 的指向之前,用一个临时变量记录当前 node 的下一个 node(curr.next). 即 1234ListNode temp = curr.next;curr.next = prev;prev = curr;curr = temp; 举例如图:翻转整个链表 1->2->3->4->null -> 4->3->2->1->null 代码我们直接来看下代码: 12345678910111213141516171819202122232425/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } *//** * @param {ListNode} head * @return {ListNode} */function reverseList(head) { if (!head || !head.next) return head; let cur = head; let pre = null; while (cur) { const next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre;} 这里不再赘述,如果不理解,想看更多更详细内容,请参考我的LeetCode 题解 - 206.reverse-linked-list 分组反转这个题目和上面的有点类似,只不过我们并不是从头到尾反转,而是每 k 个为一组进行反转。LeetCode 同样有原题25. K 个一组翻转链表官方难度为 Hard。 题目描述123456789101112131415161718给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。示例 :给定这个链表:1->2->3->4->5当 k = 2 时,应当返回: 2->1->4->3->5当 k = 3 时,应当返回: 3->2->1->4->5说明 :你的算法只能使用常数的额外空间。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 思路我们的思路还是一样的,我们把每 k 位的位置当成是尾节点即可。 我们的任务就是每次反转头尾之间的所有节点,然后将链表重新拼起来即可。 我们先来写一下反转头尾之间的所有节点这个方法。 1234567891011121314151617181920// 翻转head到tail之间的部分,不包括head和tail// 返回原链表的第一个元素,也就是翻转后的最后一个元素function reverseList(head, tail) { if (head === null || head.next === null) return head; let cur = head.next; first = cur; let pre = head; // 这里就是翻转不包括head的原因,否则就是head.pre了(当然我们没有pre指针) // 这里就是翻转不包括tail的原因,否则就是tail.next了。 while (cur !== tail) { const next = cur.next; cur.next = pre; pre = cur; cur = next; } // 拼接 head.next = pre; first.next = cur; return first;} 这里的反转不包括 head 和 tail,并不是我一开始的思路,但是在慢慢想的过程,发现这样写代码会更优雅。 上面的代码如果是 head 是我们的头节点,tail 是 null,那么就等效于上面的那道题。也就是说我们的这个 k 分组是上面题目的一般形式,当 k 为链表长度的时候,就会变成上面那道题了。 还有一点不同的是,我们每次反转之后都要对链表进行拼接,这是上面那个反转所没有的,这里要注意一下。 12head.next = pre;first.next = cur; 这里是对每一组(k个nodes)进行翻转, 先分组,用一个count变量记录当前节点的个数 用一个start 变量记录当前分组的起始节点位置的前一个节点 用一个end变量记录要翻转的最后一个节点位置 翻转一组(k个nodes)即(start, end) - start and end exclusively。 翻转后,start指向翻转后链表, 区间(start,end)中的最后一个节点, 返回start 节点。 如果不需要翻转,end 就往后移动一个(end=end.next),每一次移动,都要count+1. 如图所示 步骤 4 和 5: 翻转区间链表区间(start, end) 举例如图,head=[1,2,3,4,5,6,7,8], k = 3 NOTE: 一般情况下对链表的操作,都有可能会引入一个新的dummy node,因为head有可能会改变。这里head 从1->3,dummy (List(0))保持不变。 这种做法的时间复杂度为 O(n),空间复杂度为 O(1)。 代码Java 代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455class ReverseKGroupsLinkedList { public ListNode reverseKGroup(ListNode head, int k) { if (head == null || k == 1) { return head; } ListNode dummy = new ListNode(0); dummy.next = head; ListNode start = dummy; ListNode end = head; int count = 0; while (end != null) { count++; // group if (count % k == 0) { // reverse linked list (start, end] start = reverse(start, end.next); end = start.next; } else { end = end.next; } } return dummy.next; } /** * reverse linked list from range (start, end), return last node. * for example: * 0->1->2->3->4->5->6->7->8 * | | * start end * * After call start = reverse(start, end) * * 0->3->2->1->4->5->6->7->8 * | | * start end * first * */ private ListNode reverse(ListNode start, ListNode end) { ListNode curr = start.next; ListNode prev = start; ListNode first = curr; while (curr != end){ ListNode temp = curr.next; curr.next = prev; prev = curr; curr = temp; } start.next = prev; first.next = curr; return first; }} Python3 代码: 1234567891011121314151617181920212223242526272829class Solution: def reverseKGroup(self, head: ListNode, k: int) -> ListNode: if head is None or k < 2: return head dummy = ListNode(0) dummy.next = head start = dummy end = head count = 0 while end: count += 1 if count % k == 0: start = self.reverse(start, end.next) end = start.next else: end = end.next return dummy.next def reverse(self, start, end): prev, curr = start, start.next first = curr while curr != end: temp = curr.next curr.next = prev prev = curr curr = temp start.next = prev first.next = curr return first JavaScript 代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */// 翻转head到tail之间的部分,不包括head和tail// 返回原链表的第一个元素,也就是翻转后的最后一个元素function reverseList(head, tail) { if (head === null || head.next === null) return head; let cur = head.next; first = cur; let pre = head; // 这里就是翻转不包括head的原因 while (cur !== tail) { // 这里就是翻转不包括tail的原因 const next = cur.next; cur.next = pre; pre = cur; cur = next; } // 拼接 head.next = pre; first.next = cur; return first;}/** * @param {ListNode} head * @param {number} k * @return {ListNode} */var reverseKGroup = function (head, k) { if (head === null || k === 1) { return head; } let cnt = 0; const dummy = { next: head, }; let start = dummy; let end = head; while (end !== null) { cnt++; if (cnt % k !== 0) { end = end.next; } else { start = reverseList(start, end.next); end = start.next; } } return dummy.next;}; 这里不再赘述,如果不理解,想看更多更详细内容,请参考我的LeetCode 题解 - 25.reverse-nodes-in-k-groups-cn 分组反转 - 增强版这道题目来自字节跳动面试题。 题目描述要求从后往前以 k 个为一组进行翻转。 例子,1->2->3->4->5->6->7->8, k = 3, 从后往前以 k=3 为一组, 6->7->8 为一组翻转为 8->7->6,3->4->5 为一组翻转为 5->4->3.1->2 只有 2 个 nodes 少于 k=3 个,不翻转。最后返回: 1->2->5->4->3->8->7->6 思路这里的思路跟从前往后以k个为一组进行翻转类似,可以进行预处理: 翻转链表 对翻转后的链表进行从前往后以 k 为一组翻转。 翻转步骤 2 中得到的链表。 例子:1->2->3->4->5->6->7->8, k = 3 翻转链表得到:8->7->6->5->4->3->2->1 以 k 为一组翻转: 6->7->8->3->4->5->2->1 翻转步骤#2 链表: 1->2->5->4->3->8->7->6 类似题目 Swap Nodes in Pairs","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"链表反转","slug":"算法/链表反转","permalink":"https://lucifer.ren/blog/categories/算法/链表反转/"},{"name":"链表","slug":"数据结构/链表","permalink":"https://lucifer.ren/blog/categories/数据结构/链表/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"字节跳动","slug":"字节跳动","permalink":"https://lucifer.ren/blog/tags/字节跳动/"},{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/tags/链表/"}],"author":{"name":"snowan","avatar":"https://avatars1.githubusercontent.com/u/6018815?s=40&v=4","url":"https://github.com/snowan"}},{"title":"【LeetCode 日记】面试题46. 把数字翻译成字符串","slug":"面试题46. 把数字翻译成字符串","date":"2020-01-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.695Z","comments":true,"path":"2020/01/20/面试题46. 把数字翻译成字符串/","link":"","permalink":"https://lucifer.ren/blog/2020/01/20/面试题46. 把数字翻译成字符串/","excerpt":"​","text":"​ 原题地址: https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof 题目描述给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。 示例 1: 输入: 12258输出: 5解释: 12258 有 5 种不同的翻译,分别是”bccfi”, “bwfi”, “bczi”, “mcfi”和”mzi” 提示: 0 <= num < 231 思路我们另 f(n)表示给定数字 num 的情况下,从 num 的第 1 位(包含)到第 n 位(包含)有多少种不同的翻译方法。 我们从几个简单的例子入手,尝试打开思路。 对于数字 12258 来说: | (挡板)表示从这里分开翻译, ,(逗号)表示分割多个翻译方式。 f(1) = 1,分别为 1。 f(2) = 2,分别为 1|2, 12。 f(3) = 3,分别为 1|2|2,1|22,12|2 … 其实对于 f(3) 来说, 我手动的情况下,是这么想的: 先把 f(2) 结果搬过来,即 1|2,12 在 f(2)的基础上分割,我要添加第三位,也就是一个 2 到末尾。 1|2|2 这样是行的, 12|2 同样是可以的。 继续在 f(1) 的基础上分割,我要添加第三位,也就是一个 2 到末尾。 1|22 那么总的情况就是三种。OK,总结下我的逻辑: 如果我不可以和前面的数字组成 10 - 25 之间的数,那么在 f(n - 1) 的末尾添加挡板 如果可以,同时在 f(n - 1)和 f(n -2) 的末尾添加挡板 用图来表示: 因此,实际上这道题就是爬楼梯的换皮题。 代码12345678910class Solution: def translateNum(self, num: int) -> int: @lru_cache def helper(s: str) -> int: if not s: return 1 pre = helper(s[:-1]) if 10 <= int(s[-2:]) <= 25: return pre + helper(s[:-2]) return pre return helper(str(num)) 复杂度分析 时间复杂度:最坏的情况,每一个数组都可以和前面的组成新的数组, 有大约 $2^N$ 种组合,因此时间复杂度为 $O(2^N)$,而我这里使用了 @lru_cache 因此不会有重复计算,时间复杂度为 $(N)$,其中 N 为 数字长度。 空间复杂度:由于空间复杂的受递归调用栈的影响,因此空间复杂度为 $O(2^N)$,而我这里使用了 @lru_cache 因此不会有重复计算,空间复杂度为 $(N)$,其中 N 为 数字长度。 如果你愿意的话,其实优化起来也比较简单,我们只需要 bottom-up 即可。 12345678910class Solution: def translateNum(self, num: int) -> int: s = str(num) n = len(s) dp = [1] * n for i in range(1, n): dp[i] = dp[i - 1] if 10 <= int(s[i - 1:i + 1]) <= 25: dp[i] += dp[i - 2] return dp[-1] 进而可以优化到空间 $O(1)$ 12345678910111213class Solution: def translateNum(self, num: int) -> int: s = str(num) n = len(s) a = b = 1 for i in range(1, n): if 10 <= int(s[i - 1:i + 1]) <= 25: temp = a a = b b = temp + b else: a = b return b 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法,动态规划","slug":"算法,动态规划","permalink":"https://lucifer.ren/blog/categories/算法,动态规划/"},{"name":"中等","slug":"中等","permalink":"https://lucifer.ren/blog/categories/中等/"}],"tags":[{"name":"数据结构,算法,LeetCode 日记,中等","slug":"数据结构,算法,LeetCode-日记,中等","permalink":"https://lucifer.ren/blog/tags/数据结构,算法,LeetCode-日记,中等/"}]},{"title":"【LeetCode日记】 1332. 删除回文子序列","slug":"1332.remove-palindromic-subsequences","date":"2020-01-13T16:00:00.000Z","updated":"2023-01-05T12:24:49.528Z","comments":true,"path":"2020/01/14/1332.remove-palindromic-subsequences/","link":"","permalink":"https://lucifer.ren/blog/2020/01/14/1332.remove-palindromic-subsequences/","excerpt":"LeetCode 上有很多抖机灵的题目,需要你仔细审题,否则很容易被套路。这里就有一道,我们来看下。 ​","text":"LeetCode 上有很多抖机灵的题目,需要你仔细审题,否则很容易被套路。这里就有一道,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/remove-palindromic-subsequences/ 题目描述s,它仅由字母 'a' 和 'b' 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。12345678910111213141516171819202122232425262728293031323334353637返回删除给定字符串中所有字符(字符串为空)的最小删除次数。「子序列」定义:如果一个字符串可以通过删除原字符串某些字符而不改变原字符顺序得到,那么这个字符串就是原字符串的一个子序列。「回文」定义:如果一个字符串向后和向前读是一致的,那么这个字符串就是一个回文。 示例 1:输入:s = "ababa"输出:1解释:字符串本身就是回文序列,只需要删除一次。示例 2:输入:s = "abb"输出:2解释:"abb" -> "bb" -> "".先删除回文子序列 "a",然后再删除 "bb"。示例 3:输入:s = "baabb"输出:2解释:"baabb" -> "b" -> "".先删除回文子序列 "baab",然后再删除 "b"。示例 4:输入:s = ""输出:0 提示:0 <= s.length <= 1000s 仅包含字母 'a' 和 'b'在真实的面试中遇到过这道题? 思路这又是一道“抖机灵”的题目,类似的题目有1297.maximum-number-of-occurrences-of-a-substring 由于只有 a 和 b 两个字符。其实最多的消除次数就是 2。因为我们无论如何都可以先消除全部的 1 再消除全部的 2(先消除 2 也一样),这样只需要两次即可完成。 我们再看一下题目给的一次消除的情况,题目给的例子是“ababa”,我们发现其实它本身就是一个回文串,所以才可以一次全部消除。那么思路就有了: 如果 s 是回文,则我们需要一次消除 否则需要两次 一定要注意特殊情况, 对于空字符串,我们需要 0 次 代码代码支持:Python3 Python3 Code: 123456789101112131415class Solution: def removePalindromeSub(self, s: str) -> int: if s == '': return 0 def isPalindrome(s): l = 0 r = len(s) - 1 while l < r: if s[l] != s[r]: return False l += 1 r -= 1 return True return 1 if isPalindrome(s) else 2 如果你觉得判断回文不是本题重点,也可以简单实现: Python3 Code: 12345class Solution: def removePalindromeSub(self, s: str) -> int: if s == '': return 0 return 1 if s == s[::-1] else 2 关键点解析 注意审题目,一定要利用题目条件“只含有 a 和 b 两个字符”否则容易做的很麻烦","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"字符串","slug":"数据结构/字符串","permalink":"https://lucifer.ren/blog/categories/数据结构/字符串/"},{"name":"回文","slug":"算法/回文","permalink":"https://lucifer.ren/blog/categories/算法/回文/"},{"name":"Easy","slug":"Easy","permalink":"https://lucifer.ren/blog/categories/Easy/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Easy","slug":"Easy","permalink":"https://lucifer.ren/blog/tags/Easy/"}]},{"title":"LeetCode一道令人发指的陷阱题","slug":"1297.maximum-number-of-occurrences-of-a-substring","date":"2020-01-09T16:00:00.000Z","updated":"2023-01-05T12:24:49.846Z","comments":true,"path":"2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/","link":"","permalink":"https://lucifer.ren/blog/2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/","excerpt":"LeetCode 有一些题目会给你设置陷阱,给你一些干扰信息。这个时候你需要小心,不要被他们带跑偏了。那么是什么样的陷阱呢?让我们来看一下!","text":"LeetCode 有一些题目会给你设置陷阱,给你一些干扰信息。这个时候你需要小心,不要被他们带跑偏了。那么是什么样的陷阱呢?让我们来看一下! 题目地址(1297. 子串的最大出现次数)https://leetcode-cn.com/problems/maximum-number-of-occurrences-of-a-substring 题目描述123456789101112131415161718192021222324252627282930313233给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数:子串中不同字母的数目必须小于等于 maxLetters 。子串的长度必须大于等于 minSize 且小于等于 maxSize 。示例 1:输入:s = "aababcaab", maxLetters = 2, minSize = 3, maxSize = 4输出:2解释:子串 "aab" 在原字符串中出现了 2 次。它满足所有的要求:2 个不同的字母,长度为 3 (在 minSize 和 maxSize 范围内)。示例 2:输入:s = "aaaa", maxLetters = 1, minSize = 3, maxSize = 3输出:2解释:子串 "aaa" 在原字符串中出现了 2 次,且它们有重叠部分。示例 3:输入:s = "aabcabcab", maxLetters = 2, minSize = 2, maxSize = 3输出:3示例 4:输入:s = "abcde", maxLetters = 2, minSize = 3, maxSize = 3输出:0提示:1 <= s.length <= 10^51 <= maxLetters <= 261 <= minSize <= maxSize <= min(26, s.length)s 只包含小写英文字母。 暴力法题目给的数据量不是很大,为 1 <= maxLetters <= 26,我们试一下暴力法。 思路暴力法如下: 先找出所有满足长度大于等于 minSize 且小于等于 maxSize 的所有子串。(平方的复杂度) 对于 maxLetter 满足题意的子串,我们统计其出现次数。时间复杂度为 O(k),其中 k 为子串长度 返回最大的出现次数 代码Pythpn Code: 1234567891011121314151617181920class Solution: def maxFreq(self, s: str, maxLetters: int, minSize: int, maxSize: int) -> int: n = len(s) letters = set() cnts = dict() res = 0 for i in range(n - minSize + 1): length = minSize while i + length <= n and length <= maxSize: t = s[i:i + length] for c in t: if len(letters) > maxLetters: break letters.add(c) if len(letters) <= maxLetters: cnts[t] = cnts.get(t, 0) + 1 res = max(res, cnts[t]) letters.clear() length += 1 return res 上述代码会超时。我们来利用剪枝来优化。 剪枝思路还是暴力法的思路,不过我们在此基础上进行一些优化。首先我们需要仔细阅读题目,如果你足够细心或者足够有经验,可能会发现其实题目中 maxSize 没有任何用处,属于干扰信息。 也就是说我们没有必要统计长度大于等于 minSize 且小于等于 maxSize 的所有子串,而是统计长度为 minSize 的所有字串即可。原因是,如果一个大于 minSize 长度的字串若是满足条件,那么该子串其中必定有至少一个长度为 minSize 的字串满足条件。因此一个大于 minSize 长度的字串出现了 n 次,那么该子串其中必定有一个长度为 minSize 的子串出现了 n 次。 代码代码支持 Python3,Java: Python Code: 12345678910 def maxFreq(self, s: str, maxLetters: int, minSize: int, maxSize: int) -> int: counter, res = {}, 0 for i in range(0, len(s) - minSize + 1): sub = s[i : i + minSize] if len(set(sub)) <= maxLetters: counter[sub] = counter.get(sub, 0) + 1 res = max(res, counter[sub]) return res;# @lc code=end Java Code: 12345678910111213141516171819 public int maxFreq(String s, int maxLetters, int minSize, int maxSize) { Map<String, Integer> counter = new HashMap<>(); int res = 0; for (int i = 0; i < s.length() - minSize + 1; i++) { String substr = s.substring(i, i + minSize); if (checkNum(substr, maxLetters)) { int newVal = counter.getOrDefault(substr, 0) + 1; counter.put(substr, newVal); res = Math.max(res, newVal); } } return res;}public boolean checkNum(String substr, int maxLetters) { Set<Character> set = new HashSet<>(); for (int i = 0; i < substr.length(); i++) set.add(substr.charAt(i)); return set.size() <= maxLetters;} 关键点解析 滑动窗口 识别题目干扰信息 看题目限制条件,对于本题有用的信息是1 <= maxLetters <= 26 扩展我们也可以使用滑动窗口来解决,感兴趣的可以试试看。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"陷阱题","slug":"陷阱题","permalink":"https://lucifer.ren/blog/tags/陷阱题/"},{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"}]},{"title":"掌握前缀表达式真的可以为所欲为!","slug":"1310.xor-queries-of-a-subarray","date":"2020-01-08T16:00:00.000Z","updated":"2023-01-05T12:24:49.558Z","comments":true,"path":"2020/01/09/1310.xor-queries-of-a-subarray/","link":"","permalink":"https://lucifer.ren/blog/2020/01/09/1310.xor-queries-of-a-subarray/","excerpt":"前缀表达式是一种非常常见和重要的知识点,如果你还不知道,那就赶紧点进来看看吧!","text":"前缀表达式是一种非常常见和重要的知识点,如果你还不知道,那就赶紧点进来看看吧! 题目地址(1310. 子数组异或查询)https://leetcode-cn.com/problems/xor-queries-of-a-subarray 题目描述123456789101112131415161718192021222324252627282930313233343536有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]。对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor ... xor arr[Ri])作为本次查询的结果。并返回一个包含给定查询 queries 所有结果的数组。示例 1:输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]输出:[2,7,14,8]解释:数组中元素的二进制表示形式是:1 = 00013 = 00114 = 01008 = 1000查询的 XOR 值为:[0,1] = 1 xor 3 = 2[1,2] = 3 xor 4 = 7[0,3] = 1 xor 3 xor 4 xor 8 = 14[3,3] = 8示例 2:输入:arr = [4,8,2,10], queries = [[2,3],[1,3],[0,0],[0,3]]输出:[8,0,4,4]提示:1 <= arr.length <= 3 * 10^41 <= arr[i] <= 10^91 <= queries.length <= 3 * 10^4queries[i].length == 20 <= queries[i][0] <= queries[i][1] < arr.length 暴力法思路最直观的思路是双层循环即可,果不其然超时了。 代码123456789101112class Solution: def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: res = [] for (L, R) in queries: i = L xor = 0 while i <= R: xor ^= arr[i] i += 1 res.append(xor) return res 前缀表达式思路比较常见的是前缀和,这个概念其实很容易理解,即一个数组中,第 n 位存储的是数组前 n 个数字的和。 对 [1,2,3,4,5,6] 来说,其前缀和可以是 pre=[1,3,6,10,15,21]。我们可以使用公式 pre[𝑖]=pre[𝑖−1]+nums[𝑖]得到每一位前缀和的值,从而通过前缀和进行相应的计算和解题。其实前缀和的概念很简单,但困难的是如何在题目中使用前缀和以及如何使用前缀和的关系来进行解题。 这道题是前缀对前缀异或,我们利用了异或的性质 x ^ y ^ x = y。 代码代码支持 Python3,Java,C++: Python Code: 1234567891011121314151617181920## @lc app=leetcode.cn id=1218 lang=python3## [1218] 最长定差子序列## @lc code=startclass Solution: def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: pre = [0] res = [] for i in range(len(arr)): pre.append(pre[i] ^ arr[i]) for (L, R) in queries: res.append(pre[L] ^ pre[R + 1]) return res# @lc code=end Java Code: 123456789101112131415161718public int[] xorQueries(int[] arr, int[][] queries) { int[] preXor = new int[arr.length]; preXor[0] = 0; for (int i = 1; i < arr.length; i++) preXor[i] = preXor[i - 1] ^ arr[i - 1]; int[] res = new int[queries.length]; for (int i = 0; i < queries.length; i++) { int left = queries[i][0], right = queries[i][1]; res[i] = arr[right] ^ preXor[right] ^ preXor[left]; } return res; } C++ Code: 123456789101112131415161718class Solution {public: vector<int> xorQueries(vector<int>& arr, vector<vector<int>>& queries) { vector<int>res; for(int i=1; i<arr.size(); ++i){ arr[i]^=arr[i-1]; } for(vector<int>temp :queries){ if(temp[0]==0){ res.push_back(arr[temp[1]]); } else{ res.push_back(arr[temp[0]-1]^arr[temp[1]]); } } return res; }}; 关键点解析 异或的性质 x ^ y ^ x = y 前缀表达式 相关题目 303. 区域和检索 - 数组不可变 1186.删除一次得到子数组最大和","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"},{"name":"前缀表达式","slug":"前缀表达式","permalink":"https://lucifer.ren/blog/tags/前缀表达式/"}]},{"title":"原来状态机也可以用来刷 LeetCode?","slug":"1262.greatest-sum-divisible-by-three","date":"2020-01-04T16:00:00.000Z","updated":"2023-01-05T12:24:49.817Z","comments":true,"path":"2020/01/05/1262.greatest-sum-divisible-by-three/","link":"","permalink":"https://lucifer.ren/blog/2020/01/05/1262.greatest-sum-divisible-by-three/","excerpt":"什么? 状态机还可以用来刷 LeetCode? 如果你还不知道,那么就快进来看看吧!","text":"什么? 状态机还可以用来刷 LeetCode? 如果你还不知道,那么就快进来看看吧! 题目地址: https://leetcode-cn.com/problems/greatest-sum-divisible-by-three/description/ 题目描述12345678910111213141516171819202122232425给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和。 示例 1:输入:nums = [3,6,5,1,8]输出:18解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。示例 2:输入:nums = [4]输出:0解释:4 不能被 3 整除,所以无法选出数字,返回 0。示例 3:输入:nums = [1,2,3,4,4]输出:12解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。 提示:1 <= nums.length <= 4 * 10^41 <= nums[i] <= 10^4 暴力法思路一种方式是找出所有的能够被 3 整除的子集,然后挑选出和最大的。由于我们选出了所有的子集,那么时间复杂度就是 $O(2^N)$ , 毫无疑问会超时。这里我们使用回溯法找子集,如果不清楚回溯法,可以参考我之前的题解,很多题目都用到了,比如78.subsets。 更多回溯题目,可以访问上方链接查看(可以使用一套模板搞定): 代码12345678910111213141516class Solution: def maxSumDivThree(self, nums: List[int]) -> int: self.res = 0 def backtrack(temp, start): total = sum(temp) if total % 3 == 0: self.res = max(self.res, total) for i in range(start, len(nums)): temp.append(nums[i]) backtrack(temp, i + 1) temp.pop(-1) backtrack([], 0) return self.res 减法 + 排序减法的核心思想是,我们求出总和。如果总和不满足题意,我们尝试减去最小的数,使之满足题意。 思路这种算法的思想,具体来说就是: 我们将所有的数字加起来,我们不妨设为 total total 除以 3,得到一个余数 mod, mod 可能值有 0,1,2. 同时我们建立两个数组,一个是余数为 1 的数组 one,一个是余数为 2 的数组 two 如果 mod 为 0,我们直接返回即可。 如果 mod 为 1,我们可以减去 one 数组中最小的一个(如果有的话),或者减去两个 two 数组中最小的(如果有的话),究竟减去谁取决谁更小。 如果 mod 为 2,我们可以减去 two 数组中最小的一个(如果有的话),或者减去两个 one 数组中最小的(如果有的话),究竟减去谁取决谁更小。 由于我们需要取 one 和 two 中最小的一个或者两个,因此对数组 one 和 two 进行排序是可行的,如果基于排序的话,时间复杂度大致为 $O(NlogN)$,这种算法可以通过。 以题目中的例 1 为例: 以题目中的例 2 为例: 代码12345678910111213141516171819202122232425class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [] two = [] total = 0 for num in nums: total += num if num % 3 == 1: one.append(num) if num % 3 == 2: two.append(num) one.sort() two.sort() if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 减法 + 非排序思路上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。 代码123456789101112131415161718192021222324252627282930313233class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [float('inf')] * 2 two = [float('inf')] * 2 total = 0 for num in nums: total += num if num % 3 == 1: if num < one[0]: t = one[0] one[0] = num one[1] = t elif num < one[1]: one[1] = num if num % 3 == 2: if num < two[0]: t = two[0] two[0] = num two[1] = t elif num < two[1]: two[1] = num if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 有限状态机思路我在数据结构与算法在前端领域的应用 - 第二篇 中讲到了有限状态机。 状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。 状态机使用非常广泛,比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。 拿本题中来说,我们从左到右扫描数组的过程,将会不断改变状态机的状态。 我们使用 state 数组来表示本题的状态: state[0] 表示 mod 为 0 的 最大和 state[1] 表示 mod 为 1 的 最大和 state[2] 表示 mod 为 1 的 最大和 我们的状态转移方程就会很容易。说到状态转移方程,你可能会想到动态规划。没错!这种思路可以直接翻译成动态规划,算法完全一样。如果你看过我上面提到的文章,那么状态转移方程对你来说就会很容易。如果你不清楚,那么请往下看: 我们从左往右不断读取数字,我们不妨设这个数字为 num。 如果 num % 3 为 0。 那么我们的 state[0], state[1], state[2] 可以直接加上 num(题目限定了 num 为非负), 因为任何数字加上 3 的倍数之后,mod 3 的值是不变的。 如果 num % 3 为 1。 我们知道 state[2] + num 会变成一个能被三整除的数,但是这个数字不一定比当前的 state[0]大。 代码表示就是max(state[2] + num, state[0])。同理 state[1] 和 state[2] 的转移逻辑类似。 同理 num % 3 为 2 也是类似的逻辑。 最后我们返回 state[0]即可。 代码123456789101112131415161718class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: if num % 3 == 0: state = [state[0] + num, state[1] + num, state[2] + num] if num % 3 == 1: a = max(state[2] + num, state[0]) b = max(state[0] + num, state[1]) c = max(state[1] + num, state[2]) state = [a, b, c] if num % 3 == 2: a = max(state[1] + num, state[0]) b = max(state[2] + num, state[1]) c = max(state[0] + num, state[2]) state = [a, b, c] return state[0] 当然这个代码还可以简化: 1234567891011class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: temp = [0] * 3 for i in range(3): temp[(i + num) % 3] = max(state[(i + num) % 3], state[i] + num) state = temp return state[0] 关键点解析 贪婪法 状态机 数学分析 扩展实际上,我们可以采取加法(贪婪策略),感兴趣的可以试一下。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"},{"name":"状态机","slug":"状态机","permalink":"https://lucifer.ren/blog/tags/状态机/"},{"name":"贪心","slug":"贪心","permalink":"https://lucifer.ren/blog/tags/贪心/"}]},{"title":"一行代码就可以通过 LeetCode?来看下我是怎么做到的!","slug":"1227.airplane-seat-assignment-probability","date":"2020-01-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.544Z","comments":true,"path":"2020/01/04/1227.airplane-seat-assignment-probability/","link":"","permalink":"https://lucifer.ren/blog/2020/01/04/1227.airplane-seat-assignment-probability/","excerpt":"这是一道 LeetCode 为数不多的概率题,我们来看下。 ​","text":"这是一道 LeetCode 为数不多的概率题,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/airplane-seat-assignment-probability/description/ 题目描述123456789101112131415161718192021222324252627有 n 位乘客即将登机,飞机正好有 n 个座位。第一位乘客的票丢了,他随便选了一个座位坐下。剩下的乘客将会:如果他们自己的座位还空着,就坐到自己的座位上,当他们自己的座位被占用时,随机选择其他座位第 n 位乘客坐在自己的座位上的概率是多少? 示例 1:输入:n = 1输出:1.00000解释:第一个人只会坐在自己的位置上。示例 2:输入: n = 2输出: 0.50000解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。 提示:1 <= n <= 10^5 暴力递归这是一道 LeetCode 为数不多的概率题,我们来看下。 思路我们定义原问题为 f(n)。对于第一个人来说,他有 n 中选择,就是分别选择 n 个座位中的一个。由于选择每个位置的概率是相同的,那么选择每个位置的概率应该都是 1 / n。 我们分三种情况来讨论: 如果第一个人选择了第一个人的位置(也就是选择了自己的位置),那么剩下的人按照票上的座位做就好了,这种情况第 n 个人一定能做到自己的位置 如果第一个人选择了第 n 个人的位置,那么第 n 个人肯定坐不到自己的位置。 如果第一个人选择了第 i (1 < i < n)个人的位置,那么第 i 个人就相当于变成了“票丢的人”,此时问题转化为 f(n - i + 1)。 此时的问题转化关系如图: (红色表示票丢的人) 整个过程分析: 代码代码支持 Python3: Python3 Code: 12345678910class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 res = 1 / n for i in range(2, n): res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n return res 上述代码会栈溢出。 暴力递归 + hashtable思路我们考虑使用记忆化递归来减少重复计算,虽然这种做法可以减少运行时间,但是对减少递归深度没有帮助。还是会栈溢出。 代码代码支持 Python3: Python3 Code: 123456789101112131415class Solution: seen = {} def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 if n in self.seen: return self.seen[n] res = 1 / n for i in range(2, n): res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n self.seen[n] = res return res 动态规划思路上面做法会栈溢出。其实我们根本不需要运行就应该能判断出栈溢出,题目已经给了数据规模是 1 <= n <= 10 ** 5。 这个量级不管什么语言,除非使用尾递归,不然一般都会栈溢出,具体栈深度大家可以查阅相关资料。 既然是栈溢出,那么我们考虑使用迭代来完成。 很容易想到使用动态规划来完成。其实递归都写出来,写一个朴素版的动态规划也难不到哪去,毕竟动态规划就是记录子问题,并建立子问题之间映射而已,这和递归并无本质区别。 代码代码支持 Python3: Python3 Code: 1234567891011121314class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 dp = [1, .5] * n for i in range(2, n): dp[i] = 1 / n for j in range(2, i): dp[i] += dp[i - j + 1] * 1 / n return dp[-1] 这种思路的代码超时了,并且仅仅执行了 35/100 testcase 就超时了。 数学分析思路我们还需要进一步优化时间复杂度,我们需要思考是否可以在线性的时间内完成。 我们继续前面的思路进行分析, 不难得出,我们不妨称其为等式 1: 1234f(n)= 1/n + 0 + 1/n * (f(n-1) + f(n-2) + ... + f(2))= 1/n * (f(n-1) + f(n-2) + ... + f(2) + 1)= 1/n * (f(n-1) + f(n-2) + ... + f(2) + f(1)) 似乎更复杂了?没关系,我们继续往下看,我们看下 f(n - 1),我们不妨称其为等式 2。 1f(n-1) = 1/(n-1) * (f(n-2) + f(n-3) + ... + f(1)) 我们将等式 1 和等式 2 两边分别同时乘以 n 和 n - 1 12n * f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(1)(n-1) * f(n-1) = f(n-2) + f(n-3) + ... + f(1) 我们将两者相减: 1n * f(n) - (n-1)*f(n-1) = f(n-1) 我们继续将 (n-1)*f(n-1) 移到等式右边,得到: 1n * f(n) = n * f(n-1) 也就是说: 1f(n) = f(n - 1) 当然前提是 n 大于 2。 既然如此,我们就可以减少一层循环, 我们用这个思路来优化一下上面的 dp 解法。这种解法终于可以 AC 了。 代码代码支持 Python3: Python3 Code: 123456789101112class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 dp = [1, .5] * n for i in range(2, n): dp[i] = 1/n+(n-2)/n * dp[n-1] return dp[-1] 优化数学分析思路上面我们通过数学分析,得出了当 n 大于 2 时: 1f(n) = f(n - 1) 那么是不是意味着我们随便求出一个 n 就好了? 比如我们求出 n = 2 的时候的值,是不是就知道 n 为任意数的值了。 我们不难想出 n = 2 时候,概率是 0.5,因此只要 n 大于 1 就是 0.5 概率,否则就是 1 概率。 代码代码支持 Python3: Python3 Code: 123class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: return 1 if n == 1 else .5 关键点 概率分析 数学推导 动态规划 递归 + mapper 栈限制大小 尾递归","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数学","slug":"算法/数学","permalink":"https://lucifer.ren/blog/categories/算法/数学/"},{"name":"概率","slug":"算法/概率","permalink":"https://lucifer.ren/blog/categories/算法/概率/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"递归","slug":"算法/递归","permalink":"https://lucifer.ren/blog/categories/算法/递归/"}],"tags":[{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"概率","slug":"概率","permalink":"https://lucifer.ren/blog/tags/概率/"},{"name":"递归","slug":"递归","permalink":"https://lucifer.ren/blog/tags/递归/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"数据结构快速盘点","slug":"basic-data-structure","date":"2020-01-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.811Z","comments":true,"path":"2020/01/03/basic-data-structure/","link":"","permalink":"https://lucifer.ren/blog/2020/01/03/basic-data-structure/","excerpt":"这篇文章不是讲解数据结构的文章,而是结合现实的场景帮助大家理解和复习数据结构与算法,如果你的数据结构基础很差,建议先去看一些基础教程,再转过来看。 本篇文章的定位是侧重于前端的,通过学习前端中实际场景的数据结构,从而加深大家对数据结构的理解和认识。","text":"这篇文章不是讲解数据结构的文章,而是结合现实的场景帮助大家理解和复习数据结构与算法,如果你的数据结构基础很差,建议先去看一些基础教程,再转过来看。 本篇文章的定位是侧重于前端的,通过学习前端中实际场景的数据结构,从而加深大家对数据结构的理解和认识。 线性结构数据结构我们可以从逻辑上分为线性结构和非线性结构。线性结构有数组,栈,链表等, 非线性结构有树,图等。 其实我们可以称树为一种半线性结构。 需要注意的是,线性和非线性不代表存储结构是线性的还是非线性的,这两者没有任何关系,它只是一种逻辑上的划分。比如我们可以用数组去存储二叉树。 一般而言,有前驱和后继的就是线性数据结构。比如数组和链表。其实一叉树就是链表。 数组数组是最简单的数据结构了,很多地方都用到它。 比如有一个数据列表等,用它是再合适不过了。其实后面的数据结构很多都有数组的影子。 我们之后要讲的栈和队列其实都可以看成是一种受限的数组, 怎么个受限法呢?我们后面讨论。 我们来讲几个有趣的例子来加深大家对数组这种数据结构的理解。 React HooksHooks 的本质就是一个数组, 伪代码: 那么为什么 hooks 要用数组? 我们可以换个角度来解释,如果不用数组会怎么样? 12345678910111213141516171819function Form() { // 1. Use the name state variable const [name, setName] = useState(\"Mary\"); // 2. Use an effect for persisting the form useEffect(function persistForm() { localStorage.setItem(\"formData\", name); }); // 3. Use the surname state variable const [surname, setSurname] = useState(\"Poppins\"); // 4. Use an effect for updating the title useEffect(function updateTitle() { document.title = name + \" \" + surname; }); // ...} 基于数组的方式,Form 的 hooks 就是 [hook1, hook2, hook3, hook4],我们可以得出这样的关系, hook1 就是[name, setName] 这一对,hook2 就是 persistForm 这个。 如果不用数组实现,比如对象,Form 的 hooks 就是 123456{ 'key1': hook1, 'key2': hook2, 'key3': hook3, 'key4': hook4,} 那么问题是 key1,key2,key3,key4 怎么取呢? 关于 React hooks 的本质研究,更多请查看React hooks: not magic, just arrays React 将如何确保组件内部hooks保存的状态之间的对应关系这个工作交给了开发人员去保证,即你必须保证 HOOKS 的顺序严格一致,具体可以看 React 官网关于 Hooks Rule 部分。 队列队列是一种受限的序列,它只能够操作队尾和队首,并且只能只能在队尾添加元素,在队首删除元素。 队列作为一种最常见的数据结构同样有着非常广泛的应用, 比如消息队列 “队列”这个名称,可类比为现实生活中排队(不插队的那种) 在计算机科学中, 一个 队列(queue) 是一种特殊类型的抽象数据类型或集合。集合中的实体按顺序保存。 队列基本操作有两种: 向队列的后端位置添加实体,称为入队 从队列的前端位置移除实体,称为出队。 队列中元素先进先出 FIFO (first in, first out)的示意: (图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/queue/README.zh-CN.md) 我们前端在做性能优化的时候,很多时候会提到的一点就是“HTTP 1.1 的队头阻塞问题”,具体来说就是 HTTP2 解决了 HTTP1.1 中的队头阻塞问题,但是为什么 HTTP1.1 有队头阻塞问题,HTTP2 究竟怎么解决的很多人都不清楚。 其实“队头阻塞”是一个专有名词,不仅仅这里有,交换器等其他都有这个问题,引起这个问题的根本原因是使用了队列这种数据结构。 对于同一个 tcp 连接,所有的 http1.0 请求放入队列中,只有前一个请求的响应收到了,然后才能发送下一个请求,这个阻塞主要发生在客户端。 这就好像我们在等红绿灯,即使旁边绿灯亮了,你的这个车道是红灯,你还是不能走,还是要等着。 HTTP/1.0 和 HTTP/1.1:在HTTP/1.0 中每一次请求都需要建立一个 TCP 连接,请求结束后立即断开连接。在HTTP/1.1 中,每一个连接都默认是长连接(persistent connection)。对于同一个 tcp 连接,允许一次发送多个 http1.1 请求,也就是说,不必等前一个响应收到,就可以发送下一个请求。这样就解决了 http1.0 的客户端的队头阻塞,而这也就是HTTP/1.1中管道(Pipeline)的概念了。但是,http1.1规定,服务器端的响应的发送要根据请求被接收的顺序排队,也就是说,先接收到的请求的响应也要先发送。这样造成的问题是,如果最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队头阻塞。可见,http1.1 的队首阻塞发生在服务器端。 如果用图来表示的话,过程大概是: HTTP/2 和 HTTP/1.1: 为了解决HTTP/1.1中的服务端队首阻塞,HTTP/2采用了二进制分帧 和 多路复用 等方法。二进制分帧中,帧是HTTP/2数据通信的最小单位。在HTTP/1.1数据包是文本格式,而HTTP/2的数据包是二进制格式的,也就是二进制帧。采用帧可以将请求和响应的数据分割得更小,且二进制协议可以更高效解析。HTTP/2中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。多路复用用以替代原来的序列和拥塞机制。在HTTP/1.1中,并发多个请求需要多个 TCP 链接,且单个域名有 6-8 个 TCP 链接请求限制。在HHTP/2中,同一域名下的所有通信在单个链接完成,仅占用一个 TCP 链接,且在这一个链接上可以并行请求和响应,互不干扰。 此网站可以直观感受HTTP/1.1和HTTP/2的性能对比。 栈栈也是一种受限的序列,它只能够操作栈顶,不管入栈还是出栈,都是在栈顶操作。 在计算机科学中, 一个 栈(stack) 是一种抽象数据类型,用作表示元素的集合,具有两种主要操作: push, 添加元素到栈的顶端(末尾);pop, 移除栈最顶端(末尾)的元素.以上两种操作可以简单概括为“后进先出(LIFO = last in, first out)”。 此外,应有一个 peek 操作用于访问栈当前顶端(末尾)的元素。(只返回不弹出) “栈”这个名称,可类比于一组物体的堆叠(一摞书,一摞盘子之类的)。 栈的 push 和 pop 操作的示意: (图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/stack/README.zh-CN.md) 栈在很多地方都有着应用,比如大家熟悉的浏览器就有很多栈,其实浏览器的执行栈就是一个基本的栈结构,从数据结构上说,它就是一个栈。这也就解释了,我们用递归的解法和用循环+栈的解法本质上是差不多。 比如如下 JS 代码: 1234567891011function bar() { const a = 1; const b = 2; console.log(a, b);}function foo() { const a = 1; bar();}foo(); 真正执行的时候,内部大概是这样的: 我画的图没有画出执行上下文中其他部分(this 和 scope 等), 这部分是闭包的关键,而我这里不是将闭包的,是为了讲解栈的。 社区中有很多“执行上下文中的 scope 指的是执行栈中父级声明的变量”说法,这是完全错误的, JS 是词法作用域,scope 指的是函数定义时候的父级,和执行没关系 栈常见的应用有进制转换,括号匹配,栈混洗,中缀表达式(用的很少),后缀表达式(逆波兰表达式)等。 合法的栈混洗操作,其实和合法的括号匹配表达式之间存在着一一对应的关系,也就是说 n 个元素的栈混洗有多少种,n 对括号的合法表达式就有多少种。感兴趣的可以查找相关资料 链表链表是一种最基本数据结构,熟练掌握链表的结构和常见操作是基础中的基础。 (图片来自: https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/linked-list/traversal) React Fiber很多人都说 fiber 是基于链表实现的,但是为什么要基于链表呢,可能很多人并没有答案,那么我觉得可以把这两个点(fiber 和链表)放到一起来讲下。 fiber 出现的目的其实是为了解决 react 在执行的时候是无法停下来的,需要一口气执行完的问题的。 图片来自 Lin Clark 在 ReactConf 2017 分享 上面已经指出了引入 fiber 之前的问题,就是 react 会阻止优先级高的代码(比如用户输入)执行。因此 fiber打算自己自建一个虚拟执行栈来解决这个问题,这个虚拟执行栈的实现是链表。 Fiber 的基本原理是将协调过程分成小块,一次执行一块,然乎将运算结果保存起来,并判断是否有时间(react 自己实现了一个类似 requestIdleCallback 的功能)继续执行下一块。如果有时间,则继续。 否则跳出,让浏览器主线程歇一会,执行别的优先级高的代码。 当协调过程完成(所有的小块都运算完毕), 那么就会进入提交阶段, 真正的进行副作用(side effect)操作,比如更新 DOM,这个过程是没有办法取消的,原因就是这部分有副作用。 问题的关键就是将协调的过程划分为一块块的,最后还可以合并到一起,有点像 Map/Reduce。 React 必须重新实现遍历树的算法,从依赖于内置堆栈的同步递归模型,变为具有链表和指针的异步模型。 Andrew 是这么说的: 如果你只依赖于[内置]调用堆栈,它将继续工作直到堆栈为空。。。 如果我们可以随意中断调用堆栈并手动操作堆栈帧,那不是很好吗?这就是 React Fiber 的目的。 Fiber 是堆栈的重新实现,专门用于 React 组件。 你可以将单个 Fiber 视为一个虚拟堆栈帧。 react fiber 大概是这样的: 12345678910111213let fiber = { tag: HOST_COMPONENT, type: \"div\", return: parentFiber, children: childFiber, sibling: childFiber, alternate: currentFiber, stateNode: document.createElement(\"div\"), props: { children: [], className: \"foo\" }, partialState: null, effectTag: PLACEMENT, effects: []}; 从这里可以看出 fiber 本质上是个对象,使用 parent,child,sibling 属性去构建 fiber 树来表示组件的结构树,return, children, sibling 也都是一个 fiber,因此 fiber 看起来就是一个链表。 细心的朋友可能已经发现了, alternate 也是一个 fiber, 那么它是用来做什么的呢?它其实原理有点像 git, 可以用来执行 git revert ,git commit 等操作,这部分挺有意思,我会在我的《从零开发 git》中讲解 想要了解更多的朋友可以看这个文章 如果可以翻墙, 可以看英文原文 这篇文章也是早期讲述 fiber 架构的优秀文章 我目前也在写关于《从零开发 react 系列教程》中关于 fiber 架构的部分,如果你对具体实现感兴趣,欢迎关注。 非线性结构那么有了线性结构,我们为什么还需要非线性结构呢? 答案是为了高效地兼顾静态操作和动态操作。大家可以对照各种数据结构的各种操作的复杂度来直观感受一下。 树树的应用同样非常广泛,小到文件系统,大到因特网,组织架构等都可以表示为树结构,而在我们前端眼中比较熟悉的 DOM 树也是一种树结构,而 HTML 作为一种 DSL 去描述这种树结构的具体表现形式。如果你接触过 AST,那么 AST 也是一种树,XML 也是树结构。。。树的应用远比大多数人想象的要得多。 树其实是一种特殊的图,是一种无环连通图,是一种极大无环图,也是一种极小连通图。 从另一个角度看,树是一种递归的数据结构。而且树的不同表示方法,比如不常用的长子 + 兄弟法,对于你理解树这种数据结构有着很大用处, 说是一种对树的本质的更深刻的理解也不为过。 树的基本算法有前中后序遍历和层次遍历,有的同学对前中后这三个分别具体表现的访问顺序比较模糊,其实当初我也是一样的,后面我学到了一点,你只需要记住:所谓的前中后指的是根节点的位置,其他位置按照先左后右排列即可。比如前序遍历就是根左右, 中序就是左根右,后序就是左右根, 很简单吧? 我刚才提到了树是一种递归的数据结构,因此树的遍历算法使用递归去完成非常简单,幸运的是树的算法基本上都要依赖于树的遍历。 但是递归在计算机中的性能一直都有问题,因此掌握不那么容易理解的”命令式地迭代”遍历算法在某些情况下是有用的。如果你使用迭代式方式去遍历的话,可以借助上面提到的栈来进行,可以极大减少代码量。 如果使用栈来简化运算,由于栈是 FILO 的,因此一定要注意左右子树的推入顺序。 树的重要性质: 如果树有 n 个顶点,那么其就有 n - 1 条边,这说明了树的顶点数和边数是同阶的。 任何一个节点到根节点存在唯一路径, 路径的长度为节点所处的深度 实际使用的树有可能会更复杂,比如使用在游戏中的碰撞检测可能会用到四叉树或者八叉树。以及 k 维的树结构 k-d 树等。 (图片来自 https://zh.wikipedia.org/wiki/K-d%E6%A0%91) 二叉树二叉树是节点度数不超过二的树,是树的一种特殊子集,有趣的是二叉树这种被限制的树结构却能够表示和实现所有的树,它背后的原理正是长子 + 兄弟法,用邓老师的话说就是二叉树是多叉树的特例,但在有根且有序时,其描述能力却足以覆盖后者。 实际上, 在你使用长子 + 兄弟法表示树的同时,进行 45 度角旋转即可。 一个典型的二叉树: 标记为 7 的节点具有两个子节点, 标记为 2 和 6; 一个父节点,标记为 2,作为根节点, 在顶部,没有父节点。 (图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/tree/README.zh-CN.md) 对于一般的树,我们通常会去遍历,这里又会有很多变种。 下面我列举一些二叉树遍历的相关算法: 94.binary-tree-inorder-traversal 102.binary-tree-level-order-traversal 103.binary-tree-zigzag-level-order-traversal 144.binary-tree-preorder-traversal 145.binary-tree-postorder-traversal 199.binary-tree-right-side-view 相关概念: 真二叉树 (所有节点的度数只能是偶数,即只能为 0 或者 2) 另外我也专门开设了二叉树的遍历章节, 具体细节和算法可以去那里查看。 堆堆其实是一种优先级队列,在很多语言都有对应的内置数据结构,很遗憾 javascript 没有这种原生的数据结构。不过这对我们理解和运用不会有影响。 堆的特点: 在一个 最小堆(min heap) 中, 如果 P 是 C 的一个父级节点, 那么 P 的 key(或 value)应小于或等于 C 的对应值.正因为此,堆顶元素一定是最小的,我们会利用这个特点求最小值或者第 k 小的值。 在一个 最大堆(max heap) 中, P 的 key(或 value)大于 C 的对应值。 需要注意的是优先队列不仅有堆一种,还有更复杂的,但是通常来说,我们会把两者做等价。 相关算法: 295.find-median-from-data-stream 二叉查找树二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。 二叉查找树具有下列性质的二叉树: 若左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若右子树不空,则右子树上所有节点的值均大于它的根节点的值; 左、右子树也分别为二叉排序树; 没有键值相等的节点。 对于一个二叉查找树,常规操作有插入,查找,删除,找父节点,求最大值,求最小值。 二叉查找树,之所以叫查找树就是因为其非常适合查找,举个例子,如下一颗二叉查找树,我们想找节点值小于且最接近 58 的节点,搜索的流程如图所示: (图片来自 https://www.geeksforgeeks.org/floor-in-binary-search-tree-bst/) 另外我们二叉查找树有一个性质是: 其中序遍历的结果是一个有序数组。有时候我们可以利用到这个性质。 相关题目: 98.validate-binary-search-tree 二叉平衡树平衡树是计算机科学中的一类数据结构,为改进的二叉查找树。一般的二叉查找树的查询复杂度取决于目标结点到树根的距离(即深度),因此当结点的深度普遍较大时,查询的均摊复杂度会上升。为了实现更高效的查询,产生了平衡树。 在这里,平衡指所有叶子的深度趋于平衡,更广义的是指在树上所有可能查找的均摊复杂度偏低。 一些数据库引擎内部就是用的这种数据结构,其目标也是将查询的操作降低到 logn(树的深度),可以简单理解为树在数据结构层面构造了二分查找算法。 基本操作: 旋转 插入 删除 查询前驱 查询后继 AVL是最早被发明的自平衡二叉查找树。在 AVL 树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 {\\displaystyle O(\\log {n})} O(\\log{n})。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在 1962 年的论文 An algorithm for the organization of information 中公开了这一数据结构。 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。 红黑树在 1972 年由鲁道夫·贝尔发明,被称为”对称二叉 B 树”,它现代的名字源于 Leo J. Guibas 和 Robert Sedgewick 于 1978 年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 {\\displaystyle O(\\log {n})} O(\\log{n})时间内完成查找,插入和删除,这里的 n 是树中元素的数目 字典树(前缀树)又称 Trie 树,是一种树形结构。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 (图来自 https://baike.baidu.com/item/%E5%AD%97%E5%85%B8%E6%A0%91/9825209?fr=aladdin)它有 3 个基本性质: 根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。 immutable 与 字典树immutableJS的底层就是share + tree. 这样看的话,其实和字典树是一致的。 相关算法: 208.implement-trie-prefix-tree 图前面讲的数据结构都可以看成是图的特例。 前面提到了二叉树完全可以实现其他树结构,其实有向图也完全可以实现无向图和混合图,因此有向图的研究一直是重点考察对象。 图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。 图的表示方法 邻接矩阵(常见) 空间复杂度 O(n^2),n 为顶点个数。 优点: 直观,简单。 适用于稠密图 判断两个顶点是否连接,获取入度和出度以及更新度数,时间复杂度都是 O(1) 关联矩阵 邻接表 对于每个点,存储着一个链表,用来指向所有与该点直接相连的点对于有权图来说,链表中元素值对应着权重 例如在无向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 可以看出在无向图中,邻接矩阵关于对角线对称,而邻接链表总有两条对称的边而在有向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 图的遍历图的遍历就是要找出图中所有的点,一般有以下两种方法: 深度优先遍历:(Depth First Search, DFS) 深度优先遍历图的方法是,从图中某顶点 v 出发, 不断访问邻居, 邻居的邻居直到访问完毕。 广度优先搜索:(Breadth First Search, BFS) 广度优先搜索,可以被形象地描述为 “浅尝辄止”,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"}]},{"title":"每日一荐 2020-01 汇总","slug":"daily-featured-2020-01","date":"2020-01-01T16:00:00.000Z","updated":"2023-01-07T14:01:24.667Z","comments":true,"path":"2020/01/02/daily-featured-2020-01/","link":"","permalink":"https://lucifer.ren/blog/2020/01/02/daily-featured-2020-01/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2020-012020-01-23[资料]尤雨溪在多伦多的演讲《Vue 3.0 设计原则》对于想要学习 Vue3.0 或者想要直接从零开发 Vue3.0 的人来说,绝对是必读的。PPT 内容不多,几分钟就可以读完,不建议看视频,视频大概有 50 多分钟。 PPT 地址: https://docs.google.com/presentation/d/1r0HcS4baHy2c106DsZ4jA7Zt0R9u2MnRmmKIvAVuf1o/edit#slide=id.p 2020-01-22[软件]安卓软件的开屏广告很烦人,严重影响效率。这里推荐一个安卓 APP 可以改善这种情况, 并且不需要 root,只需要开启权限即可。注意它不是万能的,不过实际使用情况来看,还是有用的。 地址: https://www.coolapk.com/apk/me.angeldevil.autoskip 2020-01-21[好文]可访问性真的是一个非常重要的东西,尤其是对于有障碍人士。但是整个行业在这方面做的还完全不够,不管是技术能力,还是重视程度上。 比如我在使用钉钉软件的时候,他们的快捷键很少,这一点非常困扰我,当然类似的应用还有很多。我觉得整个行业应该注重起来这一块,将可访问性做好,要知道这也是用户体验中很重要的组成。这里有一篇文章 《Debugging your application for accessibility》, 从浅层次上讲解了可访问性的内容,以及基本实践,同时还推荐了一个叫 axe 的 chrome 扩展工具帮助你分析网页存在的可访问性问题,类似于 网页性能分析之于 lighthouse。 文章地址: https://blog.logrocket.com/debugging-application-accessibility/ 2020-01-20[网站]我平时有 RSS 阅读的习惯,我使用的 Feedly 管理订阅内容。但是有的网站本身并不支持 RSS 订阅。那么一种黑科技,就是使用第三方服务帮我们转换一下,生成订阅。原理很简单,就是轮训内容变化,如果变化就通知你。当然前提你要知道“如何判断发布了新内容”,这部分 feed43 做的不错。我们利用 Feed43,将任意网页制作成 RSS 订阅源。 这里有一篇少数派的文章,大家可以参考一下: https://sspai.com/post/34320 你也可以使用 rsshub 来做同样的事情,rsshub 支持私有化部署,地址: https://docs.rsshub.app/。 2020-01-19[网站]给大家介绍一个 mac 软件下载网站,效果你懂的。类似的网站还有 xclient.info。 地址:https://www.macappdownload.com/ 2020-01-17[工具]如果你想开发一个 VSCode 插件,那么一个脚手架是有用的。我推荐使用官方的脚手架工具。顺便再推荐一个 vscode 插件开发指南,来自 sorrycc,地址 https://www.yuque.com/docs/share/cf6d9191-be02-4644-aef5-afc2f2f38297 地址: https://github.com/Microsoft/vscode-generator-code 2020-01-16[工具]不改变任何功能的情况下给你的 docker image 瘦身。 Github 地址: https://github.com/docker-slim/docker-slim 2020-01-13[插件]今天推荐两个关于 Github 的 chrome 插件。 一个是用来查看 Github 提交历史的,名字是Git History Browser Extension,安装之后 git 文件右上角信息会多一个按钮。 点开之后是这种画风: 另外一个插件是OctoLinker。这个插件你可以用来方便地进行文件跳转。 2020-01-09[好文]如果你关注 Node.js 社区,那么你一定记得 Node.js v12 一个非常重磅的功能就是,内核的 HTTP Parser 默认使用 llhttp,取代了老旧的 http-parser,性能提升了 156%。 但知其然也要知其所以然,llhttp 是如何做到这一点的呢?《llhttp 是如何使 Node.js 性能翻倍的?》进行了详细的阐述。 地址: https://zhuanlan.zhihu.com/p/100660049 2020-01-08[好文]昨天介绍了《当你在浏览器中输入 google.com 并且按下回车之后发生了什么?》,今天推荐一篇《图解浏览器的基本工作原理》。 讲的内容主要是浏览器渲染相关的,让你在更大的视角,更细的粒度了解浏览器原理,最可贵的是文章通俗易懂,图文并茂,对于想了解浏览器原理而又找不到好的入门资料的同学来说很有用。 其中还提到了很多延伸知识,比如事件冒泡更微观角度是什么?事件的 passive:true 做了什么?为什么很多时候我们绘图不流畅以及如何实现平滑绘图? 12345678window.addEventListener(\"pointermove\", (event) => { const events = event.getCoalescedEvents(); for (let event of events) { const x = event.pageX; const y = event.pageY; // draw a line using x and y coordinates. }}); (使用 getCoalescedEvents API 来获取组合的事件,从而绘制一条平滑的曲线) 文章地址: https://zhuanlan.zhihu.com/p/47407398 2020-01-07[好文]或许目前实际上最全的《当你在浏览器中输入 google.com 并且按下回车之后发生了什么?》。文档内容不仅局限于 DNS,TCP,HTTP,CDN。发送 HTML,解析 DOM 等过程,甚至包括了物理键盘和系统中断的工作原理,系统中断,ARP 等等更为详细的内容。 地址: https://github.com/skyline75489/what-happens-when-zh_CN 2020-01-06[框架]前端测试正在变得越来越重要,之前也写了一篇文章前端测试,那么拥有一个顺手的测试框架显得越来越重要。 我个人目前在使用的测试框架是 Jest,除了 Jest 还有很多优秀的测试框架,知己知彼,百战不殆。我们看看下: Mocha:非常老牌的测试框架,使用 Jest 之前我在用 Enzyme:一个 React 测试框架,后期我不再使用了,而是转向 Jest + react-dom/test-utils Ava Jasmine Cypress 另外你做自动化测试的话,推荐使用 Puppeteer,如果你做组件测试的话可以考虑 Jest 的快照或者 StoryBook(一个 2015 年以来一直关注并且看好的一个框架)。 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2020-01","slug":"每日一荐/2020-01","permalink":"https://lucifer.ren/blog/categories/每日一荐/2020-01/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"《一文看懂浏览器事件循环》","slug":"event-loop","date":"2019-12-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.636Z","comments":true,"path":"2019/12/11/event-loop/","link":"","permalink":"https://lucifer.ren/blog/2019/12/11/event-loop/","excerpt":"实际上浏览器的事件循环标准是由 HTML 标准规定的,具体来说就是由 whatwg 规定的,具体内容可以参考event-loops in browser。而 NodeJS 中事件循环其实也略有不同,具体可以参考event-loops in nodejs 我们在讲解事件模型的时候,多次提到了事件循环。 事件指的是其所处理的对象就是事件本身,每一个浏览器都至少有一个事件循环,一个事件循环至少有一个任务队列。循环指的是其永远处于一个“无限循环”中。不断将注册的回调函数推入到执行栈。 那么事件循环究竟是用来做什么的?浏览器的事件循环和 NodeJS 的事件循环有什么不同?让我们从零开始,一步一步探究背后的原因。","text":"实际上浏览器的事件循环标准是由 HTML 标准规定的,具体来说就是由 whatwg 规定的,具体内容可以参考event-loops in browser。而 NodeJS 中事件循环其实也略有不同,具体可以参考event-loops in nodejs 我们在讲解事件模型的时候,多次提到了事件循环。 事件指的是其所处理的对象就是事件本身,每一个浏览器都至少有一个事件循环,一个事件循环至少有一个任务队列。循环指的是其永远处于一个“无限循环”中。不断将注册的回调函数推入到执行栈。 那么事件循环究竟是用来做什么的?浏览器的事件循环和 NodeJS 的事件循环有什么不同?让我们从零开始,一步一步探究背后的原因。 为什么要有事件循环JS 引擎要回答这个问题,我们先来看一个简单的例子: 12345678function c() {}function b() { c();}function a() { b();}a(); 以上一段简单的 JS 代码,究竟是怎么被浏览器执行的? 首先,浏览器想要执行 JS 脚本,需要一个“东西”,将 JS 脚本(本质上是一个纯文本),变成一段机器可以理解并执行的计算机指令。这个“东西”就是 JS 引擎,它实际上会将 JS 脚本进行编译和执行,整个过程非常复杂,这里不再过多介绍,感兴趣可以期待下我的 V8 章节,如无特殊说明,以下都拿 V8 来举例子。 有两个非常核心的构成,执行栈和堆。执行栈中存放正在执行的代码,堆中存放变量的值,通常是不规则的。 当 V8 执行到a()这一行代码的时候,a 会被压入栈顶。 在 a 的内部,我们碰到了b(),这个时候 b 被压入栈顶。 在 b 的内部,我们又碰到了c(),这个时候 c 被压入栈顶。 c 执行完毕之后,会从栈顶移除。 函数返回到 b,b 也执行完了,b 也从栈顶移除。 同样 a 也会被移除。 整个过程用动画来表示就是这样的: (在线观看) 这个时候我们还没有涉及到堆内存和执行上下文栈,一切还比较简单,这些内容我们放到后面来讲。 DOM 和 WEB API现在我们有了可以执行 JS 的引擎,但是我们的目标是构建用户界面,而传统的前端用户界面是基于 DOM 构建的,因此我们需要引入 DOM。DOM 是文档对象模型,其提供了一系列 JS 可以直接调用的接口,理论上其可以提供其他语言的接口,而不仅仅是 JS。 而且除了 DOM 接口可以给 JS 调用,浏览器还提供了一些 WEB API。 DOM 也好,WEB API 也好,本质上和 JS 没有什么关系,完全不一回事。JS 对应的 ECMA 规范,V8 用来实现 ECMA 规范,其他的它不管。 这也是 JS 引擎和 JS 执行环境的区别,V8 是 JS 引擎,用来执行 JS 代码,浏览器和 Node 是 JS 执行环境,其提供一些 JS 可以调用的 API 即JS bindings。 由于浏览器的存在,现在 JS 可以操作 DOM 和 WEB API 了,看起来是可以构建用户界面啦。 有一点需要提前讲清楚,V8 只有栈和堆,其他诸如事件循环,DOM,WEB API 它一概不知。原因前面其实已经讲过了,因为 V8 只负责 JS 代码的编译执行,你给 V8 一段 JS 代码,它就从头到尾一口气执行下去,中间不会停止。 另外这里我还要继续提一下,JS 执行栈和渲染线程是相互阻塞的。为什么呢? 本质上因为 JS 太灵活了,它可以去获取 DOM 中的诸如坐标等信息。 如果两者同时执行,就有可能发生冲突,比如我先获取了某一个 DOM 节点的 x 坐标,下一时刻坐标变了。 JS 又用这个“旧的”坐标进行计算然后赋值给 DOM,冲突便发生了。 解决冲突的方式有两种: 限制 JS 的能力,你只能在某些时候使用某些 API。 这种做法极其复杂,还会带来很多使用不便。 JS 和渲染线程不同时执行就好了,一种方法就是现在广泛采用的相互阻塞。 实际上这也是目前浏览器广泛采用的方式。 单线程 or 多线程 or 异步前面提到了你给V8一段JS代码,它就从头到尾一口气执行下去,中间不会停止。 为什么不停止,可以设计成可停止么,就好像 C 语言一样? 假设我们需要获取用户信息,获取用户的文章,获取用的朋友。 单线程无异步由于是单线程无异步,因此我们三个接口需要采用同步方式。 123fetchUserInfoSync().then(doSomethingA); // 1sfetchMyArcticlesSync().then(doSomethingB); // 3sfetchMyFriendsSync().then(doSomethingC); // 2s 由于上面三个请求都是同步执行的,因此上面的代码会先执行fetchUserInfoSync,一秒之后执行fetchMyArcticlesSync,再过三秒执行fetchMyFriendsSync。 最可怕的是我们刚才说了JS执行栈和渲染线程是相互阻塞的。 因此用户就在这期间根本无法操作,界面无法响应,这显然是无法接受的。 多线程无异步由于是多线程无异步,虽然我们三个接口仍然需要采用同步方式,但是我们可以将代码分别在多个线程执行,比如我们将这段代码放在三个线程中执行。 线程一: 1fetchUserInfoSync().then(doSomethingA); // 1s 线程二: 1fetchMyArcticlesSync().then(doSomethingB); // 3s 线程三: 1fetchMyFriendsSync().then(doSomethingC); // 2s 由于三块代码同时执行,因此总的时间最理想的情况下取决与最慢的时间,也就是 3s,这一点和使用异步的方式是一样的(当然前提是请求之间无依赖)。为什么要说最理想呢?由于三个线程都可以对 DOM 和堆内存进行访问,因此很有可能会冲突,冲突的原因和我上面提到的 JS 线程和渲染线程的冲突的原因没有什么本质不同。因此最理想情况没有任何冲突的话是 3s,但是如果有冲突,我们就需要借助于诸如锁来解决,这样时间就有可能高于 3s 了。 相应地编程模型也会更复杂,处理过锁的程序员应该会感同身受。 单线程 + 异步如果还是使用单线程,改成异步是不是会好点?问题的是关键是如何实现异步呢?这就是我们要讲的主题 - 事件循环。 事件循环究竟是怎么实现异步的?我们知道浏览器中 JS 线程只有一个,如果没有事件循环,就会造成一个问题。 即如果 JS 发起了一个异步 IO 请求,在等待结果返回的这个时间段,后面的代码都会被阻塞。 我们知道 JS 主线程和渲染进程是相互阻塞的,因此这就会造成浏览器假死。 如何解决这个问题? 一个有效的办法就是我们这节要讲的事件循环。 其实事件循环就是用来做调度的,浏览器和NodeJS中的事件循坏就好像操作系统的调度器一样。操作系统的调度器决定何时将什么资源分配给谁。对于有线程模型的计算机,那么操作系统执行代码的最小单位就是线程,资源分配的最小单位就是进程,代码执行的过程由操作系统进行调度,整个调度过程非常复杂。 我们知道现在很多电脑都是多核的,为了让多个 core 同时发挥作用,即没有一个 core 是特别闲置的,也没有一个 core 是特别累的。操作系统的调度器会进行某一种神秘算法,从而保证每一个 core 都可以分配到任务。 这也就是我们使用 NodeJS 做集群的时候,Worker 节点数量通常设置为 core 的数量的原因,调度器会尽量将每一个 Worker 平均分配到每一个 core,当然这个过程并不是确定的,即不一定调度器是这么分配的,但是很多时候都会这样。 了解了操作系统调度器的原理,我们不妨继续回头看一下事件循环。 事件循环本质上也是做调度的,只不过调度的对象变成了 JS 的执行。事件循环决定了 V8 什么时候执行什么代码。V8只是负责JS代码的解析和执行,其他它一概不知。浏览器或者 NodeJS 中触发事件之后,到事件的监听函数被 V8 执行这个时间段的所有工作都是事件循环在起作用。 我们来小结一下: 对于 V8 来说,它有: 调用栈(call stack) 这里的单线程指的是只有一个 call stack。只有一个 call stack 意味着同一时间只能执行一段代码。 堆(heap) 对于浏览器运行环境来说: WEB API DOM API 任务队列 事件来触发事件循环进行流动 以如下代码为例: 12345678function c() {}function b() { c();}function a() { setTimeout(b, 2000);}a(); 执行过程是这样的: (在线观看) 因此事件循环之所以可以实现异步,是因为碰到异步执行的代码“比如 fetch,setTimeout”,浏览器会将用户注册的回调函数存起来,然后继续执行后面的代码。等到未来某一个时刻,“异步任务”完成了,会触发一个事件,浏览器会将“任务的详细信息”作为参数传递给之前用户绑定的回调函数。具体来说,就是将用户绑定的回调函数推入浏览器的执行栈。 但并不是说随便推入的,只有浏览器将当然要执行的 JS 脚本“一口气”执行完,要”换气“的时候才会去检查有没有要被处理的“消息”。如果于则将对应消息绑定的回调函数推入栈。当然如果没有绑定事件,这个事件消息实际上会被丢弃,不被处理。比如用户触发了一个 click 事件,但是用户没有绑定 click 事件的监听函数,那么实际上这个事件会被丢弃掉。 我们来看一下加入用户交互之后是什么样的,拿点击事件来说: 12345678910111213$.on(\"button\", \"click\", function onClick() { setTimeout(function timer() { console.log(\"You clicked the button!\"); }, 2000);});console.log(\"Hi!\");setTimeout(function timeout() { console.log(\"Click the button!\");}, 5000);console.log(\"Welcome to loupe.\"); 上述代码每次点击按钮,都会发送一个事件,由于我们绑定了一个监听函数。因此每次点击,都会有一个点击事件的消息产生,浏览器会在“空闲的时候”对应将用户绑定的事件处理函数推入栈中执行。 伪代码: 12345while (true) { if (queue.length > 0) { queue.processNextMessage(); }} 动画演示: (在线观看) 加入宏任务&微任务我们来看一个更复制的例子感受一下。 123456789101112131415console.log(1);setTimeout(() => { console.log(2);}, 0);Promise.resolve() .then(() => { return console.log(3); }) .then(() => { console.log(4); });console.log(5); 上面的代码会输出:1、5、3、4、2。 如果你想要非常严谨的解释可以参考 whatwg 对其进行的描述 -event-loop-processing-model。 下面我会对其进行一个简单的解释。 浏览器首先执行宏任务,也就是我们 script(仅仅执行一次) 完成之后检查是否存在微任务,然后不停执行,直到清空队列 执行宏任务 其中: 宏任务主要包含:setTimeout、setInterval、setImmediate、I/O、UI 交互事件 微任务主要包含:Promise、process.nextTick、MutaionObserver 等 有了这个知识,我们不难得出上面代码的输出结果。 由此我们可以看出,宏任务&微任务只是实现异步过程中,我们对于信号的处理顺序不同而已。如果我们不加区分,全部放到一个队列,就不会有宏任务&微任务。这种人为划分优先级的过程,在某些时候非常有用。 加入执行上下文栈说到执行上下文,就不得不提到浏览器执行JS函数其实是分两个过程的。一个是创建阶段Creation Phase,一个是执行阶段Execution Phase。 同执行栈一样,浏览器每遇到一个函数,也会将当前函数的执行上下文栈推入栈顶。 举个例子: 1234567891011function a(num) { function b(num) { function c(num) { const n = 3; console.log(num + n); } c(num); } b(num);}a(1); 遇到上面的代码。 首先会将 a 的压入执行栈,我们开始进行创建阶段Creation Phase, 将 a 的执行上下文压入栈。然后初始化 a 的执行上下文,分别是 VO,ScopeChain(VO chain)和 This。 从这里我们也可以看出,this 其实是动态决定的。VO 指的是variables, functions 和 arguments。 并且执行上下文栈也会同步随着执行栈的销毁而销毁。 伪代码表示: 12345const EC = { scopeChain: {}, variableObject: {}, this: {},}; 我们来重点看一下 ScopeChain(VO chain)。如上图的执行上下文大概长这个样子,伪代码: 12345678910111213141516171819202122232425262728global.VO = { a: pointer to a(), scopeChain: [global.VO]}a.VO = { b: pointer to b(), arguments: { 0: 1 }, scopeChain: [a.VO, global.VO]}b.VO = { c: pointer to c(), arguments: { 0: 1 }, scopeChain: [b.VO, a.VO, global.VO]}c.VO = { arguments: { 0: 1 }, n: 3 scopeChain: [c.VO, b.VO, a.VO, global.VO]} 引擎查找变量的时候,会先从 VOC 开始找,找不到会继续去 VOB…,直到 GlobalVO,如果 GlobalVO 也找不到会返回Referrence Error,整个过程类似原型链的查找。 值得一提的是,JS 是词法作用域,也就是静态作用域。换句话说就是作用域取决于代码定义的位置,而不是执行的位置,这也就是闭包产生的本质原因。 如果上面的代码改造成下面的: 123456function c() {}function b() {}function a() {}a();b();c(); 或者这种: 12345678function c() {}function b() { c();}function a() { b();}a(); 其执行上下文栈虽然都是一样的,但是其对应的 scopeChain 则完全不同,因为函数定义的位置发生了变化。拿上面的代码片段来说,c.VO 会变成这样: 123c.VO = { scopeChain: [c.VO, global.VO],}; 也就是说其再也无法获取到 a 和 b 中的 VO 了。 总结通过这篇文章,希望你对单线程,多线程,异步,事件循环,事件驱动等知识点有了更深的理解和感悟。除了这些大的层面,我们还从执行栈,执行上下文栈角度讲解了我们代码是如何被浏览器运行的,我们顺便还解释了作用域和闭包产生的本质原因。 最后我总结了一个浏览器运行代码的整体原理图,希望对你有帮助: 下一节浏览器的事件循环和NodeJS的事件循环有什么不同, 敬请期待~ 参考 Node.js event loop - logrocket event-loop - nodejs.org what-is-the-execution-context-in-javascript Event Loop in JS - youtube","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"浏览器","slug":"前端/浏览器","permalink":"https://lucifer.ren/blog/categories/前端/浏览器/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/tags/浏览器/"},{"name":"事件循环","slug":"事件循环","permalink":"https://lucifer.ren/blog/tags/事件循环/"}]},{"title":"每日一荐 2019-12 汇总","slug":"daily-featured-2019-12","date":"2019-12-01T16:00:00.000Z","updated":"2023-01-07T14:01:24.665Z","comments":true,"path":"2019/12/02/daily-featured-2019-12/","link":"","permalink":"https://lucifer.ren/blog/2019/12/02/daily-featured-2019-12/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-122019-12-31[见闻]今天是我的生日,祝我生日快乐 🎂 ~~~~ 一般公司的卫生间贴的都是公司信息或心灵鸡汤,但是谷歌卫生间贴的是如何找到代码 Bug,不得不感叹“这实在是太硬核了”! 2019-12-24[技巧]今天是平安夜,苹果 🍎 和圣诞礼物 🎁 都收到了么? 今天给大家推荐的是一个 linux 中非常常见的命令 grep 的常用用法。 图版本: 文字版本: Matches patterns in input text.Supports simple patterns and regular expressions. Search for an exact string:grep search_string path/to/file Search in case-insensitive mode:grep -i search_string path/to/file Search recursively (ignoring non-text files) in current directory for an exact string:grep -RI search_string . Use extended regular expressions (supporting ?, +, {}, () and |):grep -E ^regex$ path/to/file Print 3 lines of [C]ontext around, [B]efore, or [A]fter each match:grep -C|B|A 3 search_string path/to/file Print file name with the corresponding line number for each match:grep -Hn search_string path/to/file Use the standard input instead of a file:cat path/to/file | grep search_string Invert match for excluding specific strings:grep -v search_string 2019-12-23[学习方法]很多人问我如何保持高效率。 首先要说的是我的效率并不是很高,这也就是我为什么还在不断学习提高效率的原因之一。那么既然有人问了我就班门弄斧回答一下,大家有什么好的方法和技巧欢迎提出来交流。 为了让自己保持高效率,我自己开了一个仓库记录了自己保持“高效率”的方式。希望可以给大家启发,本仓库内容持续更新~ 仓库大纲: 仓库截图: 仓库地址:https://github.com/azl397985856/To-Be-Productive 2019-12-20[新闻]Facebook 发布 Hermes, 一个新的专门用于 React Native 的 JS 引擎。 文章地址:https://facebook.github.io/react-native/blog/2019/07/17/hermes 2019-12-19[好文]很多高级语言有自动的垃圾回收器,比如 JS,JAVA,Go 等。其会自动地进行垃圾回收工作,而不必像诸如 C 和 C++那样手动分配和清除内存。 对于 old space 的垃圾回收算法有一个是标记清除,从一个根对象开始对于所有可达的对象进行标记,剩下的就是不可达的,我们将其进行清除,本文讲解了三色标记法(黑色,白色和灰色),三色标记法本质上进行一次 DFS,并将内存对象分到三个部分,DFS 完成之后清除不可达的内存(白色)。这篇文章以动画形式讲解了三色标记法的具体过程。 文章(《一张图了解三色标记法》)地址:http://idiotsky.top/2017/08/16/gc-three-color/ 2019-12-18[教程]哈弗大学 CS50 系列,内容持续更新,现在最新的是 2019 年。 你可以跟着教程来重新学习 CS 基础。 地址:https://cs50.harvard.edu/college/ 2019-12-17[网站]Learn Git Branching 是一个交互式学习 Git 的网站。沙盒里你能执行相应的命令,还能看到每个命令的执行情况; 通过一系列刺激的关卡挑战,逐步深入的学习 Git 的强大功能,在这个过程中你可能还会发现一些有意思的事情。 地址: https://learngitbranching.js.org/ 2019-12-16[新闻]最新版本的 Chrome 和 Firefo 浏览器取消 EV 证书的显示。 只有用户点击了锁 🔒,才会显示出 EV 证书的信息。 为什么会这样?想要知道答案的可以点击原文阅读。 原文地址:Chrome and Firefox Changes Spark the End of EV Certificates 2019-12-13[类库]loki 是一个 React Storybook 组件回归测试工具。React Storybook 是一个我 15 年就开始关注的一个工具,本身的设计思想我比较喜欢。现在除了支持 React,也支持 React Native,Vue,Angular 等,甚至最新的 Svelte 也支持。 loki Github 地址: https://github.com/oblador/loki 2019-12-12[技巧]Angular 的 Commit Message Conventions 是一套很流行的 Commit Message 规约。简单方便,一目了然,更重要的是这种约定化如果形成一种默契,不管对于之后查看,还是生成各种外部资料(比如 CHNAGELOG)都是非常方便的。 详细信息: https://gist.github.com/stephenparish/9941e89d80e2bc58a153 相关工具也有很多,我个人使用的是Commitizen 2019-12-11[好文]文章标题 《花椒前端基于 WebAssembly 的 H.265 播放器研发》,本文从背景介绍,技术调研,实际方案到最后的实践效果,完整地讲述了通过 wasm 将 H.265 应用到不支持其的浏览器的过程。干货满满,其架构图画的也是我比较喜欢的风格。 文章地址: https://zhuanlan.zhihu.com/p/73772711 2019-12-10[技巧]我们有时候需要在终端访问一些国外的资源。我目前采取的措施主要是给终端设置 proxy。 12alias proxy='export all_proxy=socks5://127.0.0.1:1086'alias unproxy='unset all_proxy' 其中socks5://127.0.0.1:1086是我的本机的正向代理地址。 如下是使用效果: 如图显示我们代理成功了,而且我们可以方便的在不想要代理的时候去掉代理。 2019-12-09[类库]对于前端,我们经常需要将组件进行可视化的展示。在 Vue 中,我们通常会用 docsify 或者 vuepress 等。而对于 react 比较有名的有 storybook 和 docz。 当然这并不是绝对的,比如 storybook 也在支持 vue 和 webcomponents。 2019-12-06[技能]在分析 CPU、内存、磁盘等的性能指标时,有几种工具是高频出现的,如 top、vmstat、pidstat,这里稍微总结一下: CPU:top、vmstat、pidstat、sar、perf、jstack、jstat;内存:top、free、vmstat、cachetop、cachestat、sar、jmap;磁盘:top、iostat、vmstat、pidstat、du/df;网络:netstat、sar、dstat、tcpdump;应用:profiler、dump 分析。排查 Java 应用的线上异常或者分析应用代码瓶颈,可以使用阿里开源的 Arthas ,nodejs 应用可以使用 alinode 2019-12-05[好文]如果你想做微前端,一定要能够回答出这 10 个问题。 微应用的注册、异步加载和生命周期管理; 微应用之间、主从之间的消息机制; 微应用之间的安全隔离措施; 微应用的框架无关、版本无关; 微应用之间、主从之间的公共依赖的库、业务逻辑(utils)以及版本怎么管理; 微应用独立调试、和主应用联调的方式,快速定位报错(发射问题); 微应用的发布流程; 微应用打包优化问题; 微应用专有云场景的出包方案; 渐进式升级:用微应用方案平滑重构老项目。 今天推荐的这个文档,区别与别的微前端文章的点在于其更加靠近规范层面,而不是结合自己的业务场景做的探索。这篇文章来自于阿里团队。 文章地址: https://mp.weixin.qq.com/s/rYNsKPhw2zR84-4K62gliw 2019-12-04[工具]相信大家使用 shell 的时候,会经常碰到忘记的 option,或者某一个用法记不清楚。遇到这种问题通常我们会用 man 或者命令提供的—help 查看用法。 这里给大家介绍另外一种工具tldr, 它是一个将 man page 进行简化,将大家常用的用法总结出来的工具。 安全也非常简单,只需要 npm install -g(前提是你必须安装 node), 如果你不想安装也没有关系,它还提供了web 版。另外你也可以参考这里定制你的主题 仓库地址: https://github.com/tldr-pages/tldr 2019-12-03[技巧]今天给大家介绍的是Google高级搜索技巧。我们经常使用搜索引擎搜索一些东西,不管是遇到问题想寻求解决方案也好,想学习一些新东西也好,掌握一定的搜索技巧是可以让你搜索的过程事半功倍,尤其是常用的技巧一定要记住。 2019-12-02[软件]我们公司在使用的一个完全开源的堡垒机,是符合 4A 的专业运维审计系统。 地址: https://github.com/jumpserver/jumpserver 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-12","slug":"每日一荐/2019-12","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-12/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"每日一荐 2019-11 汇总","slug":"daily-featured-2019-11","date":"2019-11-03T16:00:00.000Z","updated":"2023-01-07T14:01:24.660Z","comments":true,"path":"2019/11/04/daily-featured-2019-11/","link":"","permalink":"https://lucifer.ren/blog/2019/11/04/daily-featured-2019-11/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-112019-11-29[网站]之前我在每日一题出了一道题 2019-08-16 - 怎么在浏览器中操作本地的文件。 一个可行的思路就是在本地创建一个服务器,比如 node 服务器,浏览器操作发送请求到服务器,然后 node 去操作本地文件。 现在 Chrome 支持 native fs api,使得这个过程原生支持,想要体验的可以访问这里 https://labs.vaadin.com/native-fs/ , 如果你愿意,你可以将它添加为 Chrome APP。 2019-11-28[工具]今天给大家推荐的工具是效率类软件 Alfred 的灵魂 workflows(工作流)。 以下是我使用频率比较高的 workflow,使用频率低的会被我定期清理掉。 我自己制作的工作流不在这里展示和推荐 下载地址: Chrome Bookmarks Colors Dash Kill Process RamdaDocs TerminalFinder Yosemite iOS Simulator Youdao 2019-11-27[软件]mac 端不能右键新建文件,这在某些时候不是很方便。 没有这个软件的时候,我是在编辑器中新建或者是使用 touch 命令。 使用了这个New File Menu软件之后多了一种更方便的选择,你可以直接右键新建,Just Like Windows Users。 地址: https://apps.apple.com/cn/app/new-file-menu/id1064959555?l=en&mt=12 2019-11-26[网站]一个网站,可以分析的 Github 仓库,采用手绘风格,对于厌倦了标准图形的我来说简直是耳目一新的感觉。 项目地址: https://repo-analytics.github.io/ 2019-11-25[技巧]Chrome 开发者工具有非常好用但是却鲜为人知的功能。今天给大家介绍一个功能 - 代码覆盖率。 指的是你下载的代码有多少是被执行了的,这在做性能优化的时候很有用。如果一些代码根本没执行,就可以延迟加载。 使用方法: Ctrl+Shift+P for windows CMD+Shift+P for mac 输入 coverage, 选择如图的选项,并确定。 然后你就能看到检测结果了: 2019-11-22[工具]Github Large File Storage (简称 git-lfs), 可以用来存储大文件,比如上 GB 的大文件,相比于传统的 Github 存储,这种方式空间更大,速度更快,并且工作流程和传统 Git flow 并无二致。 地址:https://git-lfs.github.com/ 2019-11-21[娱乐]文章标题 【The skeptic’s guide to interpreting developer marketing speak 🗺️】 - 暂翻译为【如何翻译开发人员的营销术语】 里面讲了各种开发人员常用的营销术语,以及对应我们应该怎么去解读 ta。比如: 现代化 就是说刚刚git init AI加持 就是说无数的if else switch case语句 最小化 就是说没有测试用例,没有例子 跨平台 就是说我听说Electron可以跨平台,于是我就用它写了 … 文章地址: https://changelog.com/posts/the-skeptics-guide-to-interpreting-developer-marketing-speak 2019-11-20[工具]微信的一个插件,功能有很多。 不过目前已经不再维护了。 消息自动回复 消息防撤回 远程控制(已支持语音) 微信多开 第二次登录免认证 聊天置底功能(类似置顶) 微信窗口置顶 会话多选删除 自动登录开关 通知中心快捷回复 聊天窗口表情包复制 & 存储 … 我用的比较多的功能恐怕就是双开和防撤回了。 消息防撤回 微信多开 仓库地址:https://github.com/TKkk-iOSer/WeChatPlugin-MacOS/tree/master 2019-11-19[工具]JS 依赖检测工具,可以用来生成图片,可视化程度很高,还可以做成自动化,集成到 CI CD ,支持 CommonJS,AMD 和 ES Module。 项目地址: https://github.com/pahen/madge 2019-11-18[娱乐]今天给大家推荐一个在线 nokia 短信图片生成器,可以自己输入短语,一键生成。 网站地址: https://zzkia.noddl.me:8020/ 2019-11-15[网站]有的什么我们需要在 Google Play 上下载软件,但是苦于没有通畅的网络(关于如何获取畅通的网络我在 2019-11-01 讲到,感兴趣可以翻过去看看)。因此一个 Google Play 镜像就很重要了。 这就如同我们 npm 和 cnpm 的关系。我们可以在这里直接下载 apkx。 apkx 需要特殊的安装工具,或者一些小技巧才能安装。 网站地址: https://apkpure.com/ 2019-11-14[技巧]很多时候我们会看到一些英文的简写。比如邮件,IM 等,这些简称能够帮我们提高沟通效率,如果你不知道一些常见的简写,沟通的时候就难免有障碍,以下是一些常见的简写,欢迎补充。 2019-11-13[技巧]今天要分享的是关于 Bash 中历史记录那些事。 第一个要介绍的是history, history is an alias for fc -l 1,你可以通过这个命令来查看最近你使用的命令。 然后你可以用!n(n 指的是 history 命令返回的命令编号)再次执行。其中有许多缩写,最有用的就是 !$, 用于指代上次键入的参数,!! 可以指代上次键入的命令。 第二个要介绍的是历史搜索ctrl + r, 然后输入你想搜索的关键字即可 第三个要介绍的是上下方向键,你可以通过他在历史记录中上下移动。即按下上返回当前上一个命名,按下下返回当前下一个命令。 还有一个小插曲,似乎和历史有那么一点点关系。 就是cd -,切换到上一次的工作路径 如果你还知道什么和历史记录相关的命令,欢迎大家补充。 2019-11-12[技巧]dig 命令是常用的域名查询工具,可以用来测试域名系统工作是否正常。 如下dig lucifer.ren, 可以发现很多信息,包括域名最终解析到了到了另外一个域名azl397985856.github.io, IP 是185.199.108.153. 这个工具在很多情况下非常有用,尤其是对于喜欢命令行的你来说。 其实 dig 是usr/bin下的一个可执行文件,更多用法请man dig查看。 12~ type dig# dig is /usr/bin/dig 2019-11-11[分享]今天是双十一,大家剁手快乐。 今天给大家分享一下前一段时间刚刚举行的大会React Conf 2019,这个是 React 最高规模的技术会议。喜欢 React 的小伙伴千万不要错过了,这里有全套视频。 地址:https://www.youtube.com/playlist?list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh 2019-11-08[好文]前几天读了一篇文章《Scaling webapps for newbs & non-techies》,文章从最简单的单体应用,逐步讲到大型应用架构,不仅讲的通俗易懂,并且图画的也非常好,是我喜欢的风格。 很期待他的第二篇《the cloud for newbs and non-techies》。 (A single server + database) (Adding a Reverse Proxy) 文章地址: https://arcentry.com/blog/scaling-webapps-for-newbs-and-non-techies/ 2019-11-07[学习方法]前一段时间看了一篇文章 -《如何构建自己的产品知识库》。这篇文章的亮点在于其所提到的技巧能够横向类比到任何领域。换句话说你可以按照它将的方法构建你自己的知识库。 里面有一句话产品知识体系是对产品知识搜集、筛选、整理后形成的知识组合,并且这些知识能够用于解决实际遇到的问题。 学习任何知识又何尝不是呢?很多人问我学习方法,其实这个东西非常地系统,很难通过几个技巧完成,也很难在短期内看到很明显的效果。大家可以看一下,说不定对你的学习和生活所有启发,即便你不是一个产品经理。 文章地址: https://www.toutiao.com/a6738596936057618951/ 后期如果有机会的话,我也会分享一下自己的学习方法 2019-11-06[工具]像 PS 和 Sketch 一样,figma 也是一个设计工具,和其他相比团队显得更简单,这点有点像蓝湖。做设计的同学要了解起来了。 地址: https://www.figma.com/ 2019-11-05[观点]VSCode 和 MDN 进行了官方联动,详情. 再也不用跳出 IDE 用 Dash 查了。 用 Alfred + Dash 虽然方便,但是不免有一种应用跳出的感觉。现在就很方便了,如果之后有更多的联动支持,相信体验会越来越好。 2019-11-04[好文]最近几年啊,我本人也看了很多关于微服务的介绍,理念,落地等技术文章,今天给大家推荐一篇阿里飞冰团队发布的技术文,这或许是最简单的微服务落地技术文章。这篇文章详细讲述了业务场景,并详细记录了解决问题的过程以及对比了业界的一些解决方案,管中窥豹,让读者慢慢走进微服务,从这篇文章可以学习到icestark这个微服务的解决方案是怎么从从到有再到落地产生实际业务价值的。 文章地址: https://zhuanlan.zhihu.com/p/88449415#h5o-9 2019-11-01[工具]身为一个程序员,科学上网是标配。市面上免费的软件大多不稳定,出了问题很难及时解决。 自建服务器虽然好,但是还是有一点繁琐的,尤其是碰到了“开会”,IP 端口就会被封锁,自己处理就比较麻烦了。 今天给大家推荐一下 SSNG 的订阅功能,有了这个订阅地址就相当于有了无数的自建服务器,然后你可以在不同的节点之间进行切换。一般而言,我会对服务器进行测速,然后选择速度最快,如果某一个服务器挂了,我只需要一键切换到另外一个即可,无需额外操作。 市面上有很多这种订阅服务,这里推荐一个付费的服务 KyCloud,挺便宜的,我订阅的是 45/季度,平均一个月 15,50G 流量,基本对于我来说非常够用了。 使用方式也非常简单,只需要以下三步即可。 下载对应客户端 点击复制订阅地址 将地址粘贴到客户端 提示: 你也可以像我一样测速,然后根据速度选择节点。 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-11","slug":"每日一荐/2019-11","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-11/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"每日一荐 2019-10 汇总","slug":"daily-featured-2019-10","date":"2019-10-09T16:00:00.000Z","updated":"2023-01-07T13:58:09.045Z","comments":true,"path":"2019/10/10/daily-featured-2019-10/","link":"","permalink":"https://lucifer.ren/blog/2019/10/10/daily-featured-2019-10/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-102019-10-31[技能]虽然我们不是专门的网络工程师,但是不免在实际工作以及面试中会需要这方面的知识,当然这是浅层的即可。如果完全不知道,那么对于一些网络性能优化问题肯定是没有概念,从而无从下手的。 网上关于 HTTP 协议的文章很多,面试的时候关于 HTTP 三大版本的差异也是热门考点。这篇文章就通俗易懂地解释了HTTP/2与HTTP/3 的新特性, 相比于之前,为什么要推出新的协议,核心解决了什么问题?有什么不足? 这些东西网上资料要么就是太专业,要么就是太笼统和人云亦云,这篇文章是相对比较适合新手读的一个文章。 文章地址: https://segmentfault.com/a/1190000020714686#articleHeader16 2019-10-30[类库]很多年前我自己写过一个简陋的pub/sub库, 这个仓库甚至被一些人在用。很多时候我们需要用到这种灵活的pub/sub功能,这个时候我们就会自己实现,或者用一些开源的。 今天给大家介绍的就是鼎鼎大名的 Jason Miller 写的一个tiny event pub sub implement,代码就几十行,不仅注释详实,而且给出了丰富的类型定义,代码简单易懂,非常适合学习。 代码地址: https://github.com/developit/mitt/blob/master/src/index.js 2019-10-29[网站]上一次给大家分享了一个常用正则的 VSCode 插件(2019-10-11 期),另外的《大前端面试宝典》也讲到了正则的学习,并给出了我认为非常不错的学习资料。 今天我继续给大家介绍几个正则学习&练习的网站。 Regex Golf 是一个有名的正则练习网站,会根据你的正则打分,难度偏高。 regexone 是一个交互友好,面向新手的一个正则练习网站,可以交互式地学习正则,右边还贴心地给出了 Notes,另外语言不同其实正则规范也不太一样,这个网站给出了不同语言的正则讲解,很用心。值得一题的是,里面的资料非常新,最新的/u- interpret the regular expression as unicode codepoints 都有。 regexr这个不是练习的, 是用来可视化的, 和之前的regexper有点像,就连域名都差不多,不过这个用户体验是真的棒。 The regular expression game 是一个过关类型的正则练习网站,有意思的是它可以根据你写的正则匹配程度进行打分,即使你没有全部匹配也是可以得分的。 2019-10-28[工具]这个是人称贺老的百姓网贺师俊Hax整理的一份中文技术活动日程, 这些活动有几个特点: 技术活动的主要语言是中文 技术活动的主要参与者是程序员 技术活动的主要日程接受公开报名 技术活动具有一定规模 目前这个仓库仅有个简陋的 yaml 数据文件,记录技术活动的时间和一些信息。后续希望能加入一个更好的查询界面。活动组织者可直接修改数据文件并提交 PR,或提交 issue 描述一下活动情况。活动组织者也可以 watch 本仓库,这样当有变动时(通常是会议信息更新),可以收到通知。 仓库地址:https://github.com/hax/chinese-tech-conf-schedule 2019-10-25[工具]今天给大家推荐的是一个我个人非常喜欢的一个好用且免费图床工具 - IPic。 这个工具不仅内置免费的微博图床,还可以自己定义,我本人还添加了腾讯云的 COS 使用起来也非常简单,直接复制图片,然后点击对应的图片即可,另外值得一提的是它本身还支持直接生成 MardDown 链接,这对经常用 MarkDown 写作的我来说绝对是一个非常实用的功能。 另外它还搭配了一个软件IPic Mover用来迁移图床,比如你的文章里面都是新浪图床,只需要一键就可以瞬间迁移到别的平台,比如腾讯云 COS。 不过这个搭配的工具是收费的,但是有免费体验时间。 2019-10-24[技能]今天是 1024 程序员节,祝广大的程序员们节日快乐。🎩🎩🎩🎩🎩🎩 之前给大家介绍了一款跨平台的 Web 平台技术栈检测工具 Wappalyzer。这几天我看了下他的源码,觉得很不错,于是就想着推荐给大家。 Wappalyzer 的整体架构非常有意思,这里讲一下我发现的主要特点,更多细节等待着你的探索。 平台相关的代码放在 drivers 文件夹下,公共的代码在 src/wappalyzer.js 特地写了 validate 脚本来检测代码。 将检测逻辑抽离到了 src/app.json 中, 以配置文件的形式存放(这个 json 文件结构设计地很精巧,应该是花了心思的) 主要采用正则来检测应用 考虑到间接引用,比如框架 A 引用了 B,那么检测到了 A 也会把 B 带上 如果想快速上手可以看下 ta 提供的测试用例,非常简洁。 Github 地址: https://github.com/AliasIO/Wappalyzer 2019-10-23[网站]今天给广大的前端朋友们介绍一个在线做题的网站,可以瞬间在线知道答案,而且不需要登陆,一共 58 道题目,不知道后续会不会更新。 这个网站的题目我看了其实没有什么新意,但是不需要登陆而是直接使用 LocalStorage 来存储你的答题情况对用户来说很轻量,给我的感觉很好,感兴趣的可以试一下。 (题目列表) (题目详情) 网址: https://quiz.typeofnan.dev/ 2019-10-22[观点]前一段时间王思聪的股权遭到了冻结,据中新经纬记者计算,王思聪名下冻结股权价值合计已经超过 8445 万元。 “这种情况一般是王思聪欠别人钱,别人追讨,作为诉讼保全措施冻结的。”金杜律师事务所律师李量接受虎嗅采访时说,“王思聪欠钱可以是直接欠,或和贾跃亭一样,给别人提供担保,承担了连带责任。” 但是实际上这种冻结对于王思聪来说根本起不到作用,他会有很多方法来免除所谓的强制执行,他只要将自己的股权先一步将股权质押给万达集团,这样质押权人就对被保全的股份有优先权, 换句话说这对王思聪来说这种冻结根本无效。 现实中有很多这样的事情,这些规则似乎是在限制那些“能力不足”的人,而对社会上这些“制造规则的人”无能为力。更可悲的是,很多人对这些不知道,不关注。其实越是贫穷的家庭,越是生活在社会底层的人,他们的后代,大概率还会是穷人。其实,我们奋斗的目标无非就是:让子女一出生就站在了别人的肩膀上! 2019-10-21[效率]我是一个有着轻微强迫症的人,社交软件的小红点有时候会打乱我的节奏,将我从专注模式(Focus Mode)强制转化为发散模式(Diffuse Mode)。 这两种模式适合我们做不同类型的工作,因此掌控小红点,避免这种不希望的模式切换是提高效率的一个有效途径。 我的做法是: 手机静音 电脑关闭红点提醒 mac 电脑可以在系统偏好设置 - 通知 - 对应 APP - 将标记应用程序的勾去掉 一般而言,你也不用担心会错过什么东西,因为右侧通知还是会有的,比如钉钉的会议提醒等。 经过这样的设置,你就可以自由切换两种模式,而不会被频繁打断,当然还是会有人来打断你,这个问题我们以后再讲。 2019-10-18[类库]UMI 的官方定位是可插拔的企业级 react 应用框架。其作者云谦也是 Ant-Design,dvajs 的核心贡献者,同时也是我早期关注的人之一。这个项目的价值绝对不亚于更受大家欢迎的 dvajs,是一个值得学习的项目。 说白了,Umi 和 create-react-app(cra)类似,就是现有技术栈的组合,封装细节,让开发者用起来更简单,只写业务代码就可以了,它有几个特点: 零配置就是默认选型都给你做好了。 开箱即用就是技术栈都固化了。 约定大于配置,开发模式也固化好了。 下图是云谦介绍 umi 的定位的时候贴的一张架构图: 项目地址:https://github.com/umijs/umi 2019-10-17[工具]之前分享过《2019-09-23 - 为什么一行 80 个字符是一个值得遵守的规范,即使你拥有 4k 显示器?》,里面提到了并排窗口的问题。 多个显示器确实可以提高效率,如果你能高效地利用每一个显示器,效果会更棒。 配合 4k 大屏显示器效果更棒 今天介绍的这款工具就是一款窗口布局工具,能够快速修改当前窗口大小并放置在指定位置,Moom 默认操作点设立在了窗口左上角的绿色按钮上,将鼠标 hover 在绿钮上就会弹出一个选择菜单,里面有五种尺寸可选,单击选项即可变化窗口大小,并能将窗口移动到指定位置。 搭配使用快捷键效果更棒 2019-10-16[工具]今天给大家推荐的是一款非常好用的 Chrome 插件,可以用来查看网站是由什么技术栈构建的。其实类似的软件也有别的,但是这个是我使用过的最好的一个。 (这个是其官网的检测结果) (这个是 GitHub 的检测结果) 项目主页: https://www.wappalyzer.com 2019-10-15[技能]今天给大家分享一个微信小技巧,据说有的人还不知道,所以今天就把它分享出来,大家如果有什么微信使用小技巧也欢迎在下方进行评论。 今天的小技巧是判断对方是否把你拉黑或者删除: 给对方转账,是好友会让你输入密码,不是好友都不用你输入密码,直接弹出下图,整个过程好友不知情的! 如果拉黑会提示: 请确认你和他(她)的好友关系是否正常 如果删除,则会提示: 你不是收款方的好友 点开好友名片,如果显示左图,说明她真的没有发过一条朋友圈。若显示右图,点开个人相册却什么也看不到,那么你有可能被删除、拉黑、朋友圈屏蔽,或者发过朋友圈但设为私密了。 为了搞清楚对方是删除还是屏蔽,你就可以用到开头转账的那一招啦! 2019-10-14[好文]如果想做一些高级的东西,编译是一个绕不过的坎,Babel 是一个前端的转义工具,Babel 有着自己的插件系统,这是个系列文章,通过这个系列你可以学到 AST,以及 Babel 插件相关的东西,并且你可以自己动手写一个 Babel 插件。 文章地址: Step-by-step guide for writing a custom babel transformation Creating custom JavaScript syntax with Babel 2019-10-12[工具]前端在调试兼容性样式的时候是一个很头疼的问题,各个浏览器以及同一个浏览器不同版本支持的 css 都是不同的,比如有些不支持 Grid,有些不支持 cal 函数。如果你自己根据这些去修改代码肯定是非常低效的,这个 Chrome 插件就是解决这样的问题,你可以在高级的浏览器上调试,自行禁用一些 css 特性来 debug。 仓库地址: https://github.com/keithclark/css-feature-toggle-devtools-extension chrome 插件地址: https://chrome.google.com/webstore/detail/css-feature-toggles/aeinmfddnniiloadoappmdnffcbffnjg 2019-10-11[工具]常用正则大全, 支持 vscode 扩展插件 值得一提的是它支持 VSCode 插件形式使用: 目前有 57 个正则: 插件地址: https://github.com/any86/any-rule 2019-10-10[技能]傅里叶变换是一种在各个领域都经常使用的数学工具。这个网站将为你介绍傅里叶变换能干什么,为什么傅里叶变换非常有用,以及你如何利用傅里叶变换干漂亮的事。傅立叶变换有很多实际的应用,比如 MP3 的原理,MP3 是如何将声波转化为二进制,并如何进行压缩的? 比如 JPEG 的原理等。 这个文章(网站)是我见过傅立叶变换最直观的一个解释之一,并且支持交互式操作。 网站地址: http://www.jezzamon.com/fourier/zh-cn.html 2019-10-09[工具]VSCode 是我经常使用的一个软件,结合自己的开发习惯我也会增加很多配置和插件等,如何将这些插件进行备份以便将来换电脑可以及时同步过来,这里关于 VScode 的配置我用的是 VSCode setting sync 插件。 这个需要结合 Gist 使用,具体使用方式请查看官方文档: 其实我有一个专门的开发常用配置文件备份仓库用来存放这些东西,这是我的仓库存放的配置,这样我即使换了电脑也能很快地用到我最舒服的配置。 如果大家没有更好的方式,不妨采用这种方式,如果你有更好的方式欢迎给我留言。 2019-10-08[工具]今天是国庆结束的第一天,大家假期玩的怎么样? 希望大家可以尽快从假期的状态中转变回来。今天给大家推荐一个我个人使用比较多的一个功能,就是剪贴板历史。 我在使用手机的时候(笔者使用的是安卓机),会经常复制一些文字或者图片,然后进行粘贴,有时候会需要粘贴之前复制的一个东西,因此剪贴板历史就显得很重要,手机上我用的就是搜索输入法自带的剪贴板历史功能。 而在电脑上我使用的是 Alfred 自带的剪贴板历史功能,只不过默认不开启,你需要去配置一下才行。 然后你就可以查看你的剪贴板历史了: 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-10","slug":"每日一荐/2019-10","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-10/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"《LeetCode题解攻略》 - 草稿目录","slug":"draft","date":"2019-10-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.655Z","comments":true,"path":"2019/10/03/draft/","link":"","permalink":"https://lucifer.ren/blog/2019/10/03/draft/","excerpt":"这个我为自己的新书写的一个目录,计划在一星期左右定下来大体目录,然后投入完善,希望大家多提意见,你的意见很可能会影响到这本书的内容,期待你以特别的方式参与进来,此致敬礼。","text":"这个我为自己的新书写的一个目录,计划在一星期左右定下来大体目录,然后投入完善,希望大家多提意见,你的意见很可能会影响到这本书的内容,期待你以特别的方式参与进来,此致敬礼。 1. 准备知识1.1 学习这本书之前需要什么基础很多人觉得算法很难,需要很多公式以及数学知识。 其实并不是这样的,除非你是做算法岗位,否则并不会要求你对数学,几何学,概率学有多深的造诣,其实更看重的是分析问题,解决问题的能力和基础编码能力。 但是我们不排除 LeetCode 有一些数学题目,我们会在后面的章节中讲到,但是说实话 LeetCode 的数学题目不会涉及很难的数学知识。而且通常我们也可以通过变通的方式解决,比如 LeetCode 有一道水壶倒水的问题,以下是题目描述: 1给你一个装满水的 8 升满壶和两个分别是 5 升、3 升的空壶,请想个优雅的办法,使得其中一个水壶恰好装 4 升水,每一步的操作只能是倒空或倒满。 这道题我们可以用 GCD(最大公约数)解决,如果你不知道这个数学知识点也没问题。 我们可以通过 BFS 来解决,其实学习算法尤其是刷 LeetCode 只需要我们掌握简单的数学知识,高中的数学知识通常来说就足够了。 另外一个大家需要掌握的数学知识是关于后面要讲的复杂度分析,这里需要一点简单的数学知识,不过不要担心,非常简单,不会有高等数学的内容。 学习本书最好你对一种编程语言比较熟悉,出于读者的受众群体和代码简洁性,我选取了 Python 作为本书的主要编程语言,如果大家对 Python 不熟悉也没有关系,我会尽量少使用语言特有的语法糖,从而减少大家对于语言层面的认知负担。 另外 Python 被誉为最容易入门的语言之一也是实至名归,大家可以放心。 退一步讲,语言不应该成为我们学习算法的障碍,不是么?那让我们一起进入 LeetCode 的世界吧! 1.2 基础数据结构和算法在真正的刷题之前,我们一定要先打好基础,学好基本的数据结构和算法,然后以练代学进行提升和消化。 从广义上来说,数据结构其实就是数据的存储结构,算法就是操作数据的方法。而平时以及本书所探讨的其实是更为狭义角度的数据结构和算法。其实指的是某些非常典型的数据结构和算法,比如数组,链表,栈,队列,树,图等数据结构,以及二分法,动态规划,快速排序等经典的算法。 数据结构是为算法所服务的,而算法是要建立在某一种或者几种数据结构之上才可以发挥作用,这两个是相辅相成的关系。某些算法一定要建立在某种数据结构之上才行,相信你读完这本书会对这句话产生更为深刻的印象。 本书要讲的内容就是在 LeetCode 上反复出现的算法,经过进一步提炼,抽取近百道题目在这里进行讲解,帮助大家理清整体结构,从而高效率地刷题。 我这里总结了 7 个常见的数据结构和 7 个常见的算法以及 5 个常见的算法思想。 7 个数据结构分别是: 数组,栈,队列,链表,二叉树,散列表,图 7 个算法分别是:二分法,递归,回溯法,排序,双指针,滑动窗口,并查集 5 个算法思想分别是:分治,贪心,深度优先遍历,广度优先遍历,动态规划 只有掌握了这些基础的数据结构和算法,更为复杂的算法才能得心应手,刷题才会事半功倍。而 LeetCode 的题目虽然不断出新,但是最终用到的算法永远是那几个,很多题目都是穿着新衣服的老面孔了。大家学好这些基础套路之后会更加明白这个道理。 1.3 如何刷 LeetCodeLeetcode 网站使用方法LeetCode 官网收录了许多互联网公司的算法题目,一度被称为刷题神器,今天我们就来介绍下如何使用 LeetCode 网站,以下所讲述的内容都是以力扣中国为例。 LeetCode 目前有 1000 多道题目,并且一直持续更新,其中有一些是带锁的,需要会员才能查看。 最上面标签栏的 Problems,给出了四个分类:Algorithms、Database、Shell 和 Concurrency,分别表示算法题、数据库题、Shell 和并发题,第一个就是我们所需要刷的算法题,并发是 2019 年才添加的新的模块。 点开 Algorithms 后,我们可以看到一列题目的列表,每个题目都有一个序号,后面的接受率(Acceptance)表示提交的正确率,Difficulty 表示难易程度。这里有个小技巧,衡量一道题目难不难除了看难度之外,还可以看下接受率,接受率越低代表题目越难,这个指标有时候比难度更靠谱。 LeetCode 按难易程度分成了三个级别,分别是 Easy、Medium 和 Hard。 Easy 通常不需要太多思考和也不会有复杂的细节,比较特别适合新手或者拿来热身。 Medium 级别就会有些难度,一般都会涉及到经典的算法,需要一定的思考。 Hard 级别是最难的,有些时候是算法本身的难度,有些时候特别需要你考虑到各种细节。 你可以对题目进行筛选和排序。 如果我们只想要找某一类型的题,可以通过 Tags 或 Company 来筛选。 另外我们在做某一题时,觉得还想再做一个类似的,可以点击题目描述下方 Show Similar Problems 或 Tags 来找到相似的问题。 每个题目都有各自的 Discuss 区域。在这里,许多人都把自己的思路和代码放到了上面,你可以发贴提问,也可以回复别人,里面大神很多,题解质量都很高,如果实在没有思路或者想看下有没有更好的思路可以来逛一下。通常来说我建议你优先看 Post 或者投票最多的。 点开某一个题目,会跳转到具体题目详情页面,你可以在右侧的代码区切换选择自己需要的编程语言。 代码编写完了之后,不要急着提交,先可以测试运行下(Run Code),你可以多写几个测试用力跑一下,没有问题再提交,要知道比赛的时候错误提交要加时间的。 我们可以点开 More Details 查看详细运行结果信息。 每道题旁边的 My Submissions 可以找到自己的对于该题的提交情况,这里可以看到自己过去所有的提交,点 Accepted 或 Wrong Answer 就可以查看自己过去提交的代码情况,包括代码是什么,跑的时间以及时间分布图等。 以上就是 LeetCode 的主要功能,希望通过这一节内容能让你对 LeetCode 网站有所了解,从而更快地进行刷题。 应该怎么刷 LeetCode我本人从开始准备算法以来刷了很多题,自己成长了很多,从刷题菜鸡,到现在对刷题套路,题型有了自己的理解,感受还是蛮多的。我本人不是算法高手,算是勤能补拙类型。不过经过几个月的学习和练习,不仅面试变得更加得心应手,而且在工作中写更容易写出干净优雅,性能好的代码。 对于我来说,刷题的过程其实就是学习数据结构和算法的过程, 不仅仅是为了刷题而刷题,这样你才能感受到刷题的乐趣。刷题至少要刷两遍,理想情况是根据自己的遗忘曲线刷多次,这个我后面也会讲到。 第一遍按 tag 刷 建议第一遍刷的时候可以先快速按照 tag 过一遍,快速感受一下常见数据结构和算法的套路,这样自己有一个感性的认识。 第二遍一题多解,多题同解 第二遍我们就不能像第一遍那样了,这个阶段我们需要多个角度思考问题,尽量做到一题多解,多题同解。我们需要对问题的本质做一些深度的理解,将来碰到类似的问题我们才能够触类旁通。 但是很多人做了几遍,碰到新题还是没有任何头绪,这是一个常见的问题,这怎么办呢? 总结并记忆是学习以及刷题过程中非常重要的一环, 不管哪个阶段,我们都需要做对应的总结,这样将来我们再回过头看的时候,才能够快读拾起来。 anki 就是根据艾宾浩斯记忆曲线开发的一个软件,它是一个使记忆变得更容易的学习软件。支持深度自定义。 对于我本人而言,我在 anki 里面写了很多 LeetCode 题目和套路的 Card,然后 anki 会自动帮我安排复习时间,大大减少我的认知负担,提高了我的复习效率。大家可以在书后的附录中下载 anki 卡片。 目前已更新卡片一览(仅列举正面) 二分法解决问题的关键点是什么,相关问题有哪些? 如何用栈的特点来简化操作, 涉及到的题目有哪些? 双指针问题的思路以及相关题目有哪些? 滑动窗口问题的思路以及相关题目有哪些? 回溯法解题的思路以及相关题目有哪些? 数论解决问题的关键点是什么,相关问题有哪些? 位运算解决问题的关键点是什么,相关问题有哪些? 大家刷了很多题之后,就会发现来来回回,题目就那么几种类型,因此掌握已有题目类型是多么重要。那样 LeetCode 出题的老师,很多也是在原有的题目基础上做了适当扩展(比如 two-sum,two-sum2,three-sum, four-sum 等等)或者改造(使得不那么一下子看出问题的本质,比如猴子吃香蕉问题)。 其中算法,主要是以下几种: 12345基础技巧:分治、二分、贪心排序算法:快速排序、归并排序、计数排序搜索算法:回溯、递归、深度优先遍历,广度优先遍历,二叉搜索树等图论:最短路径、最小生成树动态规划:背包问题、最长子序列 数据结构,主要有如下几种: 123456数组与链表:单 / 双向链表栈与队列哈希表堆:最大堆 / 最小堆树与图:最近公共祖先、并查集字符串:前缀树(字典树) / 后缀树 做到了以上几点,我们还需要坚持。这个其实是最难的,不管做什么事情,坚持都是最重要也是最难的。 为了督促自己,同时帮助大家成长,我在群里举办《每日一题》活动,每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块, 感兴趣的可以到书后的附录部分进群交流。 1.4 复杂度分析想学算法,首先要学的第一件事就是如何判断一个算法的好坏。 好的程序有很多的评判标准,包括但不限于可读性,扩展性性能等。 这里我们来看其中一种 - 性能。 坏的程序可能性能也很好,但是好的程序通常性能都比较好。那么如何分析一个算法的性能好坏呢?这就是我们要讲的复杂度分析,所有的数据结构教程都会把这个放在前面来讲,不仅仅是因为他们是基础,更因为他们真的非常重要。学会了复杂度分析,你才能够对你的算法进行分析,从而帮助你写出复杂度更优的算法。 那么怎么样才能衡量一个算法代码的执行效率呢? 如下是一个从 1 加到 n 的一个算法,这个算法用了一层循环来完成,并且借助了一个变量 res 来完成。 12345def sum(n): res = 0 for i in range(1, n + 1): res += i return res 我们将这个方法从更微观的角度来进行分析,上述代码会执行 n 次循环体的内容,每一次执行都是常数时间,我们不妨假设执行的时间是 x。我们假设赋值语句res = 0和return res的时间分别为 y 和 z 那么执行的总时间我们约等于 n _ x + y + z, 我们粗略将 x,y 和 z 都看成一样的,我们得出总时间为 (n + 2) _ x 换句话说算法的时间和数据的规模成正比。 实际上,这更是一种叫做大 O 表示法的基本思想, 它是一种描述算法性能的记法,这种描述和编译系统、机器结构、处理器的快慢等因素无关。 这种描述的参数是 n,表示数据的规模。 这里的 O 表示量级(order),比如说“二分查找是$O(logN)$的”,也就是说它需要“通过 logn 量级的操作去查找一个规模为 n 的数据结构(通常是数组)”。这种渐进地估计对算法的理论分析和大致比较是非常有价值,可以很快地对算法进行一个大致地估算。例如,一个拥有较小常数项的 $O(N^2)$算法在规模 n 较小的情况下可能比一个高常数项的$O(N)$算法运行得更快。但是随着 n 足够大以后,具有较慢上升函数的算法必然工作得更快,因此在采用大 O 标记复杂度的时候,可以忽略系数。 我们还应该区分算法的最好情况,最坏情况和平均情况,但是这不在本书的讨论范畴,本书的所有复杂度均指的是平均复杂度。 那么如何分析一个算法的复杂度呢?下面我们介绍几种常见时间复杂度,几乎所有的算法的复杂度都是以下中的一种 我对时间复杂度进行了一个小的分类。 第一类是常数阶。 一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是 Ο(1)。 1234cnt = 1l = 0r = len(list) - 1# 不管这种代码有多少行,都是常数复杂度,即$O(1)$,因此系数是被忽略的。 第二类是 n,n^2,n^3 … 一个简单的方法是关注循环执行次数最多的那一段代码就好了,这段执行次数最多的代码执行次数的 n 的量级,就是整个算法的时间复杂度。即如果是一层 N 的循环,那么时间复杂度就是$O(N)$, 如果嵌套了两层 N 的循环,那么时间复杂度就是$O(N^2)$,依次类推。 123456789101112class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: n = len(nums) mapper = {} for i in range(n): if (target - nums[i] in mapper): return [mapper[target - nums[i]], i] else: mapper[nums[i]] = i return [] 如上代码,我们进行了一层的循环,那么时间复杂度就是$O(N^2)$ 第三类是对数阶。 logn nlogn 这同样是一种非常常见的复杂度,多见于二分查找和一些排序算法。 123456789101112131415161718def numRescueBoats(self, people: List[int], limit: int) -> int: res = 0 l = 0 r = len(people) - 1 people.sort() while l < r: total = people[l] + people[r] if total > limit: r -= 1 res += 1 else: r -= 1 l += 1 res += 1 if (l == r): return res + 1 return res 上面的代码是一个典型的二分查找,其时间复杂度是 logn 第四类是指数阶 2^n 指数的增长已经非常恐怖了,一个典型的例子是 fabnicca 数列的递归实现版本。 1234def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) 如果你把上述的计算过程看成树的一个节点,那么整个计算过程就像是一颗很大的树。这棵树有很多的重复计算,大致算下来的话,是 2^n。 第五类是对数阶 n! 我们知道 n 个不相同的数字的全排列有 n!个。 1234def factorrail(n): if n == 1: return 1 return n * factorrail(n - 1) 很明显上面的代码就是 n! 下面给出上面列举的几种常见的时间复杂度的趋势图对比,大家直观感受一下。 (各种复杂度的渐进趋势对比) 从算法可以分析出时间复杂度,相反题目的时间复杂度要求,我们甚至可以猜测出可能用到的算法,比如算法要求 logn,那么就有可能是二分法。 空间复杂度分析比时间复杂度分析要简单地多,常见的空间复杂度有$O(1)$、$O(N)$、$O(N^2)$、$O(logN)$、$O(logN)$、$O(N!)$这样的对数阶复杂度基本不会有,关于空间复杂度这里不做更多介绍了。 总结时间复杂度是算法的基石,学习它对于我们学习后面的章节有很大的帮助。 我们引入了大 O 表示法来衡量算法的好坏。接着通过若干实例了解了各种时间复杂度,其实对于复杂度,掌握上面提到的几种常见的就已经够应付绝大多数场合了。 通过上面的学习,相信你对评估一个算法的时间复杂度有了初步的了解。随着学习的深入,相信你会对复杂度分析有着更为深入的理解。 2. 数学之美LeetCode 中有很多数学问题,截止到本书出版,LeetCode 中有数学标签的题目一共是 159,在所有标签的分类中排名第 3。这些题目中有些是包着数学外衣的伪数学问题,还有一些是真正数学问题。这需要大家有着极强的分辨能力。不要被数学两个字吓住了,本章不会讲非常复杂的数学概念和公式,实际上你只需要一些高中数学知识即可。 除非是面试算法岗位,笔试和面试题才会涉及到一些比较复杂度的数学知识,比如微积分,线性代数,概率论,信息论等。 虽然有的题目可以用数学公式轻松解决,但是这并不意味你需要对数学有很深的造诣。举例来说,LeetCode 69.实现开方,就是一道可以使用纯数学方法 - 牛顿迭代法来解决的一道题,但是你完全可以使用二分法解决,尽管效率远远不及牛顿迭代法,实际上现在的计算器计算开方也不是二分法计算的。但是这通常是一个加分项,如果你可以通过别的方法解决,也未尝不可。 很多题目一眼看上去就是个数学问题,如果你尝试使用数学的角度没有什么思路或者解不出来的时候,可以考虑换最常规,最符合直觉的做法,当然做之前要估算一下数据范围和时间,不要写完才发现超时。 有些题目只是涉及到一些数学名词,并且会在题目中详细地进行解释。 比如关于质数性质,阶乘性质的题目,还有一些造轮子题目,比如实现 Pow 等。还有一些干脆定义一个数学概念,让你去做。比如开心数,回文数,丑数等。 我们这章主要讲解纯数学问题,需要用到一些数学的性质类的题目,这或许是大家更想要看的。 2.1 N-SUM 题目LeetCode 上有很多经典的系列问题,今天我们就来看一下 N-SUM 系列问题。 2.2 连续整数和这是一个非常经典,被各种收录的一个题目,这道题好在虽然简单,但是却可以从多个纬度进行解决,非常适合用来查考一个人的真实水平,一些比较大的公司也会用它来进行算法面试的第一道题。 2.3 最大数2.4 分数到小数2.5 最大整除子集2.6 质数排列 质数 全排列 2.8 快乐数 这类题目是给定一个定义(情景),让你实现算法找出满足特定条件的数字 3. 回文的艺术回文是很多教材中被提到的一个题目,通常是用来学习栈的一个练习题,LeetCode 中有关回文的题目也是蛮多的,单从数据结构上来看就有字符串,数字和链表。今天我们就结合几个 LeetCode 题目来攻克它。 3.1 回文字符串3.2 回文链表3.3 回文数字3.4 回文总数4. 游戏之乐我很喜欢玩游戏,实际上很多游戏背后都是有很多算法存在的,我们通过 LeetCode 上一些关于游戏的题目来一窥究竟吧,虽然这里的题目和实际游戏用到的算法难度差很多,但是这里的基本思想是一样的。 4.1 生命游戏4.2 报数4.3 数独游戏5. BFS & DFS这是 LeetCode 后期新增的一个板块,题目还比较少。 6. 二分法二分法真的是一个非常普遍的算法了,更严格的说其实是一种思想,如果把二改成 N 其实就是一种分治思想。LeetCode 关于二分法的题目实在太多了,我们挑选几个代表性的来感受一下,LeetCode 到底是如何考察我们二分法的。 6.1 你真的了解二分法么?6.2 一些显然的二分6.3 隐藏的二分法二进制和二分法? 744 吃香蕉 循环数组 数学开方 等等 6.4 寻找峰值7. 神奇的比特前菜: 如何将一个 IP 地址用一个字节存储,支持序列化和反序列化操作。 计算机是用过二进制来表示信息的,有时候我们从二进制思考问题,会发现一个全新的世界。 7.1 那些特立独行的数字7.2 桶中摸黑白球7.3 实现加法7.4 二进制 1 的个数7.5 悲惨的老鼠8. 设计题有时候我们面对的不是一个算法题,而是一个设计题目,这种题目比较开放,让你自己设计数据结构和算法。这比限定数据结构和算法更能考察一个人综合运用知识的能力,是一个经常被拿来进行面试的一类题目。 8.1 设计 LRU8.2 设计 LFU8.3 最小栈8.4 队列实现栈8.5 设计 Trie 树9. 双指针双指针的题目真的非常多,可以看出这个是一个重要重要的知识点。在实际使用过程中,我将双指针问题分为两种,一种是头尾双指针,一种是快慢双指针。 9.1 头尾指针9.1.1 盛水问题9.1.2 两数相加 29.2 快慢指针9.2.1 删除有序数组的重复元素9.2.2 链表中的快慢指针10. 查表与动态规划如果说递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。 这句话需要一定的时间来消化, 如果不理解,可以过一段时间再来看。 递归的解决问题非常符合人的直觉,代码写起来比较简单。但是我们通过分析(可以尝试画一个递归树),可以看出递归在缩小问题规模的同时可能会 重复计算。 279.perfect-squares 中 我通过递归的方式来解决这个问题,同时内部维护了一个缓存 来存储计算过的运算,那么我们可以减少很多运算。 这其实和动态规划有着异曲同工的地方。 10.1 爬楼梯10.2 聪明的盗贼六(七)个版本,带你一步步进化,走向极致 10.3 不同路径10.4 硬币找零10.5 最短编辑距离11. 滑动窗口你可能听过 TCP 的滑动窗口,这里要讲的滑动窗口其实思想是一样的,这里要讲的滑动窗口通常被用在处理连续数组或者字符的问题上。 最长连续不重复子串最短子数组之和滑动窗口最大值12. 博弈博弈,词语解释是局戏、围棋、赌博。 现代数学中有博弈论,亦名“对策论”、“赛局理论”,属应用数学的一个分支, 表示在多决策主体之间行为具有相互作用时,各主体根据所掌握信息及对自身能力的认知,做出有利于自己的决策的一种行为理论。 这类问题通常没那么直接和好下手,需要你进行一定的推演才能发现问题的本质。 12.1 alec12.2 Nim12.3 486. 预测赢家13. 股票系列LeetCode 上有很多经典的系列问题,今天我们就来看一下这个股票系列问题。 13.1 股票买卖的最佳时机 113.2 股票买卖的最佳时机 213.3 股票买卖的最佳时机 313.4 股票买卖的最佳时机 414. 分治法分治是一种非常重要的算法思想,而不是一个算法。和具体算法不同,算法思想在任何数据结构下都可以使用。 14.1 合并 K 个排序链表14.2 数组中的第 K 个最大元素14.3 搜索二维矩阵15. 贪心法贪心或许是最难的一种算法思想了。 15.1 跳跃游戏15.2 任务调度器16. 回溯这是一种非常暴力的搜索算法,优点是书写简单有固定模板,且适用范围很广。 16.1 求组合数 116.2 求组合数 216.3 求所有子集16.4 全排列16.5 海岛问题17. 一些有趣的题目这里让我们来看一下 LeetCode 上那些惊人的算法。 17.1 求众数17.2 手撕排序17.3 星期几17.4 量筒问题17.5 实现开方17.6 4 的次方18. 一些通用解题模板不仅是平时做工程项目,刷题的过程也非常讲究风格一致,如果有一些非常优秀的模板可以直接拿来用,一方便减少做题时间和出错的可能,另一方面做题风格一致有利于自己回顾。 如果你是在面试,相信一定也会为你加分不少。 18.1 二分法18.2 回溯法18.3 递归18.4 并查集 朋友圈 计算小岛数 2 19. 融会贯通这里我们要把本书降到的知识进行融会贯通,纵向上我们不满足于一种解法,我们尝试使用多种解法去解决。 横向上我们需要去总结哪些题目和这道题目类似。 这通常被用在第二遍刷 LeetCode 的过程中。 19.1 最大子序列和问题19.2 循环移位问题19.3 k 问题20. 解题技巧&面试技巧在水平知识一样的情况下,如果能够 LeetCode 上效率更好?如何面试的时候加分,这是本章我们要探讨的主要内容。 一定要看限制条件,很多时候限制条件起到了提示的作用,并且可以帮助你过滤错误答案 21. 参考","categories":[{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/categories/书/"},{"name":"算法","slug":"书/算法","permalink":"https://lucifer.ren/blog/categories/书/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/tags/书/"},{"name":"草稿","slug":"草稿","permalink":"https://lucifer.ren/blog/tags/草稿/"}]},{"title":"数据结构与算法在前端领域的应用 - 第四篇","slug":"algorthimn-fe-4","date":"2019-09-20T16:00:00.000Z","updated":"2023-01-05T12:24:49.906Z","comments":true,"path":"2019/09/21/algorthimn-fe-4/","link":"","permalink":"https://lucifer.ren/blog/2019/09/21/algorthimn-fe-4/","excerpt":"前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶) 这一次我们顺着前面的内容,讲一些经典的数据结构与算法,本期我们来讲一下时下比较火热的React fiber。 这部分内容相对比较硬核和难以消化,否则 React 团队也不会花费两年的时间来搞这一个东西。建议大家多读几遍。","text":"前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶) 这一次我们顺着前面的内容,讲一些经典的数据结构与算法,本期我们来讲一下时下比较火热的React fiber。 这部分内容相对比较硬核和难以消化,否则 React 团队也不会花费两年的时间来搞这一个东西。建议大家多读几遍。 fiber - 一个用于增量更新的数据结构前面我的文章提到过 fiber 是一种数据结构,并且是一种链式的数据结构。 fiber 是为了下一代调和引擎而引入的一种新的数据结构,而下一代调和引擎最核心的部分就是“增量渲染”。为了明白这个“增量渲染”,我们需要打一点小小的基础。 分片执行为了做到上面我提到的“增量渲染”,我们首先要能够停下来。之前 React 的更新 UI 的策略是自顶向下进行渲染,如果没有人工的干涉,React 实际上会更新到所有的子组件,这在大多数情况下没有问题。 然而随着项目越来越复杂,这种弊端就非常明显了。单纯看这一点,Vue 在这方面做的更好,Vue 提供了更加细粒度的更新组件的方式,甚至无需用户参与。 这是两者设计理念上的差异,不关乎好坏,只是适用场景不一样罢了。 值得一提的是,Vue 的这种细粒度监听和更新的方式,实际上是内存层面和计算层面的权衡。社区中一些新的优秀框架,也借鉴了 Vue 的这种模式,并且完成了进一步的进化,对不同的类型进行划分,并采取不同的监听更新策略,实际上是一种更加“智能“的取舍罢了。 言归正传,我们如何才能做到”增量更新“呢? 首先你要能够在进行到中途的时候停下来 你能够继续刚才的工作,换句话说可以重用之前的计算结果 实现这两点靠的正是我们今天的主角 fiber,稍后我们再讲。 比如之前 React 执行了一个 100ms 的更新过程,对于新的调和算法,会将这个过程划分为多个过程,当然每一份时间很可能是不同的。 由于总时间不会减少,我们设置还增加了调度(上面我提到的两条)的代码,因此单纯从总时间上,甚至是一种倒退。但是为什么用户会感觉到更快了呢?这就是下面我们要讲的调度器。 三大核心组件 - Scheduler, Reconciliation, Renderer事实上, React 核心的算法包含了三个核心部分,分别是Scheduler,, Reconciliation,Renderer。 scheduler 用于决定何时去执行。 前面提到了,整个更新过程要比之前的做法要长。总时间变长的情况下,用户感觉性能更好的原因在于scheduler。 对于用户而言,界面交互,动画,界面更新的优先级实际上是不一样的。 通过保证高优先级的事件的执行,比如用户输入,动画。 可以让用户有性能很好的感觉。 做到这一点实际上原理很简单,即使前面提到的 chunks,再加上我们给每一个任务分配一个优先级,从而保证 chunks 的执行顺序中高优先级的在前面。 浏览器实际上自己也会对一些事件区分优先级。 Reconciliation 决定哪部分需要更新,以及如何“相对最小化”完成更新过程。这部分算法主要上衣基于VDOM这种数据结构来完成的。 这部分的算法实际上就是一个“阉割版的最小编辑树算法”。 renderer 使用 Reconciliation 的计算结果,然后将这部分差异,最小化更新到视图。可以是 DOM,也可以是native, 理论上可以是任何一种渲染容器。 在 DOM 中,这部分的工作由 React-DOM 来完成。它会生成一些 DOM 操作的 API,从而去完成一些副作用,这里指的是更新 DOM。 fiber - 一个虚拟调用栈实际上,fiber 做的事情就是将之前 react 更新策略进行了重构。 之前的更新策略是从顶向下,通过调用栈的形式保存已经更新的信息。这本身没有问题, 但是这和我们刚才想要实现的效果是不匹配的,我们需要 chunks 的效果。而之前的策略是从顶到下一口气执行完,不断 push stack,然后 pop stack,直到 stack 为空。 fiber 正是模拟了调用栈,并且通过链表来重新组织,一方面使得我们可以实现 chunks 的功能。另一方面可以和 VDOM 进行很好的对应和映射。 v = f(d)这是我从 React 官方介绍 fiber 的一个地方抄来的公式。 它想表达的是 react 是一个视图管理框架,并且是数据驱动的,唯一的数据会驱动产生唯一的视图。 我们可以把每一个组件都看成一个 view,然而我们的工作就是计算所有的组件的最新的 view。 那么 fiber 是如何完成“增量更新”呢? 秘诀就是它相当于“重新实现了浏览器调用栈”。 我们来看一下,fiber 是如何实现传统调用栈的功能的。 fiber 和 传统调用栈的区别传统的调用栈,我们实际上将生成 view 的工作放到栈里面执行,浏览器的栈有一个特点就是“你给它一个任务,它必须一口气执行完”。 而 fiber 由于是自己设计的,因此可以没有这个限制。 具体来说,两者的对应关系如下: 1234567传统调用栈 fiber 子函数 component type 函数嵌套 child 参数 props 返回地址 parent 返回值 DOM elements 用图来表示的话,大概是这样: 其中具体的算法,我预计会在我的从零开始开发一个 React 中更新。 总结本篇文章介绍了fiber,fiber其实是一种用于增量更新的数据结构。是为了下一代调和引擎而引入的一种新的数据结构,而下一代调和引擎最核心的部分就是“增量渲染”。 我们介绍了几个基本概念和组件,包括分片执行, react三大核心组件 - Scheduler, Reconciliation, Renderer。 最后我们说了“fiber实际上就是一个虚拟调用栈”,并结合传统调用栈的特点和弊端,讲解了fiber是如何组织,从而改进了传统调用栈带来的问题的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"数据结构与算法在前端领域的应用 - 第三篇","slug":"algorthimn-fe-3","date":"2019-09-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.781Z","comments":true,"path":"2019/09/20/algorthimn-fe-3/","link":"","permalink":"https://lucifer.ren/blog/2019/09/20/algorthimn-fe-3/","excerpt":"这是本系列文章的第三篇,这里我将带你从新的视角来看当前的前端应用,虽然这其中涉及到的道理很简单,但是这部分知识很少被人看到,更不要说推广和应用了。 这里新的视角指的是我们从进程和线程的角度来思考我们前端应用的运行,从而从更高的层次去审视和优化我们的应用,甚至整个前端生态。 希望你看完之后从思维上也好,工作应用中也好能够有所收获。 前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶)","text":"这是本系列文章的第三篇,这里我将带你从新的视角来看当前的前端应用,虽然这其中涉及到的道理很简单,但是这部分知识很少被人看到,更不要说推广和应用了。 这里新的视角指的是我们从进程和线程的角度来思考我们前端应用的运行,从而从更高的层次去审视和优化我们的应用,甚至整个前端生态。 希望你看完之后从思维上也好,工作应用中也好能够有所收获。 前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶) 关于我我是一个对技术充满兴趣的程序员, 擅长前端工程化,前端性能优化,前端标准化等。 做过.net, 搞过 Java,现在是一名前端工程师。 除了我的本职工作外,我会在开源社区进行一些输出和分享,GitHub 共计获得 1.5W star。比较受欢迎的项目有leetcode 题解 , 宇宙最强的前端面试指南 和我的第一本小书 浏览器的进程模型我们首先来看下浏览器的进程模型,我们以 chrome 为例。 Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。 (图来自 https://zhuanlan.zhihu.com/p/47407398) 这也是为什么 chrome 明明只打开了一个 tab,却出现了 4 个进程的原因。 这部分不是我们本节的主要内容,大家了解这么多就够了,接下来我们看下今天的主角 - 渲染进程。 浏览器的渲染进程渲染进程几乎负责 Tab 内的所有事情,渲染进程的核心目的在于转换 HTML CSS JS 为用户可交互的 web 页面。 渲染进程由以下四个线程组成:主线程 Main thread , 工作线程 Worker thread,光栅线程 Raster thread和排版线程 Compositor thread。 我们的今天的主角是主线程 Main thread 和 工作线程 Worker thread。 主线程 Main thread主线程负责: 构建 DOM 和网络进程(上文提到的)通信获取资源 对资源进行解析 JS 代码的执行 样式和布局的计算 可以看出主线程非常繁忙,需要做很多事情。 主线程很容易成为应用的性能瓶颈。 当然除了主线程, 我们的其他进程和线程也可能成为我们的性能瓶颈,比如网络进程,解决网络进程瓶颈的方法有很多,可以使用浏览器本身缓存,也可以使用 ServiceWorker,还可以通过资源本身的优化等。这个不是我们本篇文章的讨论重点,这里只是让你有一个新的视角而已,因此不赘述。 工作线程 Worker thread工作线程能够分担主线程的计算压力,进而主线程可以获得更多的空闲时间,从而更快地响应用户行为。 工作线程主要有 Web Woker 和 Service Worker 两种。 Web Worker以下摘自MDN Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,他们可以使用 XMLHttpRequest 执行 I/O(尽管 responseXML 和 channel 属性总是为空)。一旦创建, 一个 worker 可以将消息发送到创建它的 JavaScript 代码, Service Worker以下摘自MDN Service workers 本质上充当 Web 应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步 API。 重新思考我们的前端应用工作线程尤其是 Web Worker 的出现一部分原因就是为了分担主线程的压力。 整个过程就像主线程发布命令,然后工作线程执行,执行完毕将执行结果通过消息的形式传递给主线程。 我们以包工头包工程,然后将工作交给各个单位去做的角度来看的话,大概是这样的: 实际上工作工作进程,尤其是 WebWorker 已经出现很长时间了。但是很多时候我们并没有充分使用,甚至连使用都没使用。 下面以 Web Worker 为例, 我们来深度挖掘一下工作线程的潜力。 前面的文章,我们谈了很多前端领域的算法,有框架层面的也有应用层面的。 前面提到了 React 的调和算法,这部分代码耗时其实还是蛮大的,React16 重构了整个调和算法,但是总体的计算成本还是没有减少,甚至是增加的。 关于调和算法可以参考我的另外一篇文章前端领域的数据结构与算法解读 - fiber 我们有没有可能将这部分内容抽离出主线程,交给工作进程,就像上面的图展示的那样呢?我觉得可以, 另外我前面系列文章提到的所有东西,都可以放到工作线程中执行。比如状态机,时光机,自动完成,差异比对算法等等。 如果将这些抽离出我们主线程的话,我们的应用大概会是这样的: 这样做主线程只负责 UI 展示,以及事件分发处理等工作,这样就大大减轻了主线程的负担,我们就可以更快速地响应用户了。然后在计算结果完成之后,我们只需要通知主线程,主线程做出响应即可。可以看出,在项目复杂到一定程度,这种优化带来的效果是非常大的。 我们来开一下脑洞, 假如流行的前端框架比如 React 内置了这种线程分离的功能,即将调和算法交给 WebWorker 来处理,会给前端带来怎么样的变化? 假如我们可以涉及一个算法,智能地根据当前系统的硬件条件和网络状态,自动判断应该将哪部分交给工作线程,哪部分代码交给主线程,会是怎么样的场景? 这其实就是传说中的启发式算法, 大家有兴趣可以研究一下 挑战上述描述的场景非常美好,但是同样地也会有一些挑战。 第一个挑战就是操作繁琐,比如 webworker 只支持单独文件引入,再比如不支持函数序列化,以及反复序列化带来的性能问题, 还有和 webworker 通信是异步的等等。 但是这些问题都有很成熟的解决方案,比如对于操作比较繁琐这个问题我们就可以通过使用一些封装好 web worker 操作的库。comlink 就是一个非常不错的 web worker 的封装工具库。 对于不支持单文件引入,我们其实可以用Blob, createObjectURL的方式模拟,当然社区中其实也有了成熟的解决方案,如果你使用 webpack 构建的话,有一个worker-loader可以直接用。 对于函数序列化这个问题,我们无法传递函数给工作线程,其实上面提到的Comlink, 就很好地解决了这个问题,即使用 Comlink 提供的proxy,你可以将一个代理传递到工作线程。 对于反复序列化带来的性能问题,我们其实可以使用一种叫对象转移(Transferable Objects)的技术,幸运的是这个特性的浏览器兼容性也不错。 对于异步的问题,我们可以采取一定的取舍。 即我们本地每次保存一份最近一份的结果拷贝,我们只需要每次返回这个拷贝,然后在 webworker 计算结果返回的时候更新拷贝即可。 总结这篇文章的主要目的是让大家以新的视角来思考当前的前端应用,我们站在进程和线程的角度来看现在的前端应用,或许会有更多的不一样的理解和思考。 本文先是讲了浏览器的进程模型,然后讲了浏览器的渲染进程中的线程模型。 我们知道了渲染进程主要有四个线程组成,分别是主线程 Main thread , 工作线程 Worker thread,光栅线程 Raster thread和排版线程 Compositor thread。 然后详细介绍了主线程和工作线程,并以 webworker 为例,讲述了如何利用工作线程为我们的主线程分担负担。为了消化这部分知识,建议你自己动手实践一下。 虽然我们的愿望很好,但是这其中在应用的过程之中还是有一些坑的,我这里列觉了一些常见的坑,并给出了解决方案。 我相信工作线程的潜力还没有被充分发挥出来,希望可以看到前端应用真正的挖掘各个进程和线程潜力的时候吧,这不但需要前端工程师的努力,也需要浏览器的配合支持,甚至需要标准化组织去推送一些东西。 关注我最近我重新整理了下自己的公众号,并且我还给他换了一个名字《脑洞前端》,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解前端是我的目标。 之后我的文章同步到微信公众号 脑洞前端 ,您可以关注获取最新的文章,或者和我进行交流。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"数据结构与算法在前端领域的应用 - 第二篇","slug":"algorthimn-fe-2","date":"2019-09-18T16:00:00.000Z","updated":"2023-01-05T12:24:49.738Z","comments":true,"path":"2019/09/19/algorthimn-fe-2/","link":"","permalink":"https://lucifer.ren/blog/2019/09/19/algorthimn-fe-2/","excerpt":"这是一个我即将做的一个《数据结构与算法在前端领域的应用》主题演讲的一个主菜。如果你对这部分内容比较生疏,可以看我的数据结构和算法在前端领域的应用(前菜) 这里我会深入帮助大家如何根据业务抽离出纯粹的模型,从而转化为算法问题,","text":"这是一个我即将做的一个《数据结构与算法在前端领域的应用》主题演讲的一个主菜。如果你对这部分内容比较生疏,可以看我的数据结构和算法在前端领域的应用(前菜) 这里我会深入帮助大家如何根据业务抽离出纯粹的模型,从而转化为算法问题, 以上帝角度来看前端让我们以更高的层次来看一下,从大的范围上前端领域都在做什么? 从业务上来说,我们会去做各个端的开发、网关、接口、工程化。从技术上说,则是基于 WEB、Node 的通用技术,以及各平台(最常见的就是安卓和 IOS)的专有技术。 在这里我以自己的标准总结了以下三点: 架构和平台 其实平台建设也是架构中的一环,之所以列出来单独讲是因为这块内容相对比较大。每个公司,部门,项目都有自己的架构设计和规范。它们环环相套组成了整个公司的架构体系。 很多公司在做工具链,在做跨端方案,在做底层融合等,这些都属于这个范畴。比如最近比较火的 Serverless 也是属于这个范畴。 规范和标准化 前端行业规范目前来看的话就两个,一个是 ECMA 的规范,一个是 W3C 的规范。前端行业规范是非常重要的,不然前端会非常混乱,想一下前端刚刚诞生出来的时候就知道了。 公司内部也会有一些规范,但是很难上升到标准层次。 目前国内没有一个行业认可的标准化组织,这算是一个遗憾吧。 好消息是国人在标准化组织的参与感越来越强,做了更多的事情。 其实这部分我们的感知是比较弱的,一个原因就是我们一直在努力对接行业的标准,很少去自己创造一些标准。原因有几点,一方面自己做标准,维护更新标准很难,另一方面自己做标准需要学习成本和转换成本。 但是这并不意味这做公司标准或者行业领域规范就没有用,相反非常有用。我之前做过一个《标准化能给我们带来什么》的分享,详细介绍了标准化对于我们的重要性。 生态体系 其实前端的工作就是人机交互,这其中涉及的东西很多,相关领域非常广泛。 比如智能手表、智能 TV、智能眼镜、头戴 AR,VR 等新的交互模式我们如何去融入现有开发体系中 ?人工智能在前端开发可以发挥怎么样的作用 ? 这些其实很多公司已经在尝试,并且取得了非常不错的效果。 比如 IDE 是开发过程非常重要的工具,我们是否可以去做标准化的 IDE,甚至放到云端。 无处不在的算法上面我们从多个方面重新审视了一下前端,除了人工智能部分,其他部分根本没有提到算法。是不是算法在前端领域应用很少呢? 不是的。 一方面就像上一节介绍的,我们日常开发中使用的很多东西都是经过数据结构和算法的精心封装,比如 DOM 和 VDOM,以及 JSON。 JSON的序列化和反序列化是我们无时无刻使用的方法,比如我们需要和后端进行数据交互,需要和其他线程(比如webworker)进行数据交互都要经过序列化和反序列化,如何减少数据传输,如何提高序列化和反序列化的效率,如何在两者之间寻求一种平衡都是我们需要研究的。 JSON 也是一种树结构 甚至还有很多框架以数据结构直接命名,比如 GraphQL,就是用图这种数据结构来命名,从而体现其强大的关联查询能力。 比如 tensorflow 以张量(tensor)来加深大家对上面两点的印象命名, TensorFlow™ 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。 上面提到的各个环节都或多或少会用到算法。首先网络部分就涉及到很多算法,比如有限状态机,滑动窗口,各种压缩算法,保障我们信息不泄漏的各种加密算法等等,简直不要太多。虽然这些网络部分很多都是现成的,但是也不排除有一些需要我们自己根据当前实际场景自己去搭建一套的可能。这在大公司之中是非常常见的。 我们再来看下执行我们代码的引擎,以 V8 为例,其本身涉及的算法不算在内。但是当我们基于 V8 去做一些事情,我们就需要了解一些编译相关的原理。这里我举个例子,下图是支付宝的小程序架构。 如果我们不懂一些算法的话,是很难像支付宝一样结合自己的业务去做一些突破的。 (图片来自 https://www.infoq.cn/article/ullETz7q_Ue4dUptKgKC) 另外一些高层的架构中也会有很多算法方面的东西,比如我需要在前端做增量更新的功能。增量更新在APP中早已不是新鲜的东西了,但是真正做JS等静态资源的实时增量更新还比较少,这里面会涉及非常复杂的交互和算法。 上面提到的更多的是高层面上,事实上即使是业务层面也有很多值得挖掘的算法模型。我们需要从复杂的业务中提炼出算法模型,才能得到实际应用。可惜的是很多时候我们缺乏这种抽象能力和意志。 除了上一节我讲述的常见场景之外,我还会在下一节介绍几个实际业务场景,从而加深大家的理解。希望大家看了之后,能够在自己的实际业务中有所帮助。 性能和优雅,我全都要从表象上看,使用合适的数据结构和算法有两方面的好处。 第一个是性能,这个比较好理解一点,我们追求更好的时间复杂度和空间复杂度,并且我们需要不断地在两者之间做合理的取舍。 第二点是优雅,使用合适的数据结构和算法。能让我们处理问题更加简洁优雅。 下面我会举几个我在实际业务场景中的例子,来加深大家对上面两点的印象。 权限系统假如你现在开发一款类似石墨文档的多人在线协作编辑文档系统。 这里面有一个小功能是权限系统。 用户可以在我们的系统中创建文件夹和文件,并且管理角色,不同的角色可以分配不同的文件权限。 比如查看,下载,编辑,审批等。 我们既可以给文件夹分配权限,又可以给文件分配权限,如果对应文件该角色没有权限,我们需要递归往上搜索,看有没有相应权限,如果有,则这个角色有文件的该操作权限。 如图,fileA 的权限就需要从 fileA 开始看有没有对应权限,如果有,则返回有权限。如果没有,则查找 app 文件夹的权限,重复这个过程,直到根节点。 如果你是这个系统的前端负责人,你会如何设计这个系统呢? 其实做这个功能的方案有很多,我这里参考了 linux 的设计。我们使用一个二进制来标示一个权限有还是没有。 这样的话,一方面我们只需要 4 个 bit 就可以存储权限信息,存储已经是极限了。另一方面我们通过位运算即可算出有没有权限,二进制运算在计算性能上也是极限了。 另外代码写起来,也会非常简洁,感兴趣的可以自己试试。 扩展: 假如文件权限不是只有两种可能,比如有三个取值怎么办? 状态机什么是状态机状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。 我们以现实中广泛使用的有限状态机(以下简称 FSM)为例进行讲解 FSM 应用非常广泛, 比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。 其中正则中使用的是一种特殊的 FSM, 叫 DFA(Deterministic Finite Automaton), 通过分裂树形式来运行。 为什么要使用状态机第一个原因,也是大家感触最深的一个原因就是通过状态机去控制系统内部的状态以及状态流转,逻辑会比较清晰,尤其在逻辑比较复杂的时候,这种作用越发明显。 第二个原因是通过状态机,我们可以实现数据以及系统的可视化。刚才我提到了正则表达式用到了状态机,那么正则是否可以可视化呢? 答案是肯定的,这里我介绍一个可视化正则表达式的一个网站。 实际业务中如果使用状态机来设计系统也可以进行可视化。类似这样子: (图来自 https://statecharts.github.io/xstate-viz/) 可以看出,逻辑流转非常清晰,我们甚至可以基于此进行调试。当然,将它作为文档的一部分也是极好的,关于状态机的实际意义还有很多,我们接下来举几个例子说明。 状态机的实际应用场景匹配三的倍数实现一个功能,判断一个数字是否是三的倍数。 数字可以非常大,以至于超过 Number 的表示范围,因此我们需要用 string 来存储。 一个简单直观的做法是直接将每一位都加起来,然后看加起来的数字是否是三的倍数。但是如果数字大到一定程度,导致加起来的数字也超过了 Number 的表示范围呢? 一个方法是使用状态机来解决。 我们发现一个数字除以 3 的余数一共有三种状态,即 0,1,2。 基于此我们可以构建一个 FSM。0,1,2 之间的状态流转也不难得出。 举个例子,假设当前我们是余数为 0 的状态,这时候再来一个字符。 如果这个字符是 0,3 或者 9,那么我们的余数还是 0 如果这个字符是 1,4 或者 7,那么我们的余数是 1 如果这个字符是 2,5 或者 8,那么我们的余数还是 2 用图大概是这个样子: 如果用代码大概是这样的: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374function createFSM() { return { initial: 0, states: { 0: { on: { read(ch) { return { 0: 0, 3: 0, 9: 0, 1: 1, 4: 1, 7: 1, 2: 2, 5: 2, 8: 2 }[ch]; } } }, 1: { on: { read(ch) { return { 0: 1, 3: 1, 9: 1, 1: 2, 4: 2, 7: 2, 2: 0, 5: 0, 8: 0 }[ch]; } } }, 2: { on: { read(ch) { return { 0: 2, 3: 2, 9: 2, 1: 0, 4: 0, 7: 0, 2: 1, 5: 1, 8: 1 }[ch]; } } } } };}const fsm = createFSM();const str = \"281902812894839483047309573843389230298329038293829329\";let cur = fsm.initial;for (let i = 0; i < str.length; i++) { if (![\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"].includes(str[i])) { throw new Error(\"非法数字\"); } cur = fsm.states[cur].on.read(str[i]);}if (cur === 0) { console.log(\"可以被3整除\");} else { console.log(\"不可以被3整除\");} 其实代码还可以简化,读者可以下去尝试一下。 可以看出,我们这种方式逻辑清晰,且内存占用很少,不会出现溢出的情况。 正则是基于自动机实现的,那么使用正则匹配会是怎么样的呢?大家可以自己试一下。 答题活动经过上面的热身,我们来一个真实的项目来练练手。 有这样一个业务场景,我们需要设计一款答题活动,让用户过来进行答题,我们预先设置 N 道题目。 规则如下: 初始状态用户会进入欢迎页面 答对之后可以直接进入下一个题目 答错了可以使用复活卡重新答,也可以使用过关卡,直接进入下一题 用户可以通过其他途径获取复活卡和过关卡 答对全部 N 道题之后用户过关,否则失败 不管是过关还是失败都展示结果页面,只不过展示不同的文字和图片 这其实是一个简化版本的真实项目。 如果要你设计这样的一个系统,你会如何设计? 相信你肯定能想出很多种方法来完成这样的需求,接下来我会用 FSM 来实现。 我们很容易画出整理的流程图: 对于答题部分则稍微有一点麻烦,但是如果你用状态机的思维去思考就很容易,我们不难画出这样的图: JS 中有很多 FSM 的框架, 大家都可以直接拿过来使用。 笔者之前所在的项目也用到了这样的技术,但是笔者是自己手写的简化版本 FSM,基本思想是一致的。 其他事实上,还有很多例子可以举。 假设我们后端服务器是一主一备,我们将所有的数据都同时存储在两个服务器上。假如某一天,有一份数据丢失了,我们如何快速找到有问题的服务器。 这其实可以抽象成【Signle Number问题】。 因此很多时候,不是缺乏应用算法的场景,而是缺乏这种将现实业务进行抽象为纯算法问题的能力。我们会被各种细枝末节的问题遮蔽双眼,无法洞察隐藏在背后的深层次的规律。 编程最难是抽象能力,前几年我写了一篇文章《为什么我们的代码难以维护》,其中一个非常重要的原因就是缺乏抽象。 从现在开始,让我们来锻炼抽象能力吧。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"数据结构与算法在前端领域的应用 - 第一篇","slug":"algorthimn-fe-1","date":"2019-09-17T16:00:00.000Z","updated":"2023-01-05T12:24:49.990Z","comments":true,"path":"2019/09/18/algorthimn-fe-1/","link":"","permalink":"https://lucifer.ren/blog/2019/09/18/algorthimn-fe-1/","excerpt":"这是一个我在公司内部做的一个《数据结构与算法在前端领域的应用》主题演讲的一个前菜。希望通过这个分享让大家认识到其实前端领域也有很多算法的,从而加深前端同学对算法的认识。","text":"这是一个我在公司内部做的一个《数据结构与算法在前端领域的应用》主题演讲的一个前菜。希望通过这个分享让大家认识到其实前端领域也有很多算法的,从而加深前端同学对算法的认识。 为什么大家觉得算法没用在我的职业生涯中,碰到很多非算法岗的研发同学吐槽“算法在实际业务中没什么用”,甚至在面试官也问过我这个问题。我们姑且不来判断这句话正确与否,我们先来看下为什么大家会有这样的想法。 我发现很多人喜欢用冰山这个图来表示这种只看到总体的一小部分的情况。我也来借用一下这个创意。 根据我的经验,我们写的业务代码通常不到总包体的 5%, 下面是我以前做过的一个实际项目的代码分布。 12$ du -sh node_modules # 429M$ du -sh src # 7.7M 大家可以拿自己的实际项目看一下,看是不是这样的。 其实不难看出业务代码在整个应用的比例是很小的,软件工程有一个至理名言,“软件开发的 90%的工作是由 10%的人完成的”,这句话很对,那么剩下的 10 的工作却由剩下的 90%来完成。 因此我们感觉算法没用,是因为我们没用深入剩下的“90%”很多场景我们接触不到,并且没有思考过,就很容易“井底之蛙”,到头来就变成“只会用别人造好的轮子组装代码”的“前端打字员”。 那剩下的 90% 究竟有哪些涉及到算法呢?是否可以举例说明呢?那接下来让我们来继续看一下。 前端中算法的应用场景都有哪些说实话,这部分内容实在太多啦,为了让大家有一个直观的感受,我画了一个图。 图中黄色的代表我自己实现过。 这些都是前端开发过程的一些东西, 他们多多少少涉及到了数据结构和算法的知识 下面我们来简单分析一下。 VDOM事实上 VDOM 就是一种数据结构,但是它并不是我们在《数据结构与算法》课程中学习到的一些现成的数据结构。 逻辑上 VDOM 就是用来抽象 DOM 的,底层上 VDOM 普遍实现是基于 hash table 这种数据结构的。 一个典型的 VDOM 可以是: 123456789101112{ type: 'div', props: { name: 'lucifer' }, children: [{ type: 'span', props: {}, children: [] }]} 不难看出,VDOM 是一种递归的数据结构,因此使用递归的方式来处理是非常直观和容易的。 我上面提到了 VDOM 是 DOM 的抽象(ye, a new level of abstraction)。根据 VDOM 我们可以创建一个对应的真实 DOM。 如果我们只是做了这一层抽象的话,我们相当于引入了一种编程模式,即从面向 DOM 编程,切换到面向 VDOM 编程,而现在 VDOM 又是由数据驱动的,因此我们的编程模式切换到了“数据驱动”。 事实上,VDOM 部分还有一个 VDOM diff 算法,相信大家都听说过。关于DOM diff 的算法,以及它是如何取舍和创新的,我之前在一个地方回答过,这里给一个链接地址:https://juejin.im/post/5d3d8cf15188256ac355a9f0 HooksHooks 是 React16 添加的一个新功能, 它要解决的问题是状态逻辑复用。 Hooks 逻辑上解决了纯函数无法持久化状态的“问题”,从而拓宽了纯函数组件的适用范围。 底层上 Hooks 使用数据来实现状态的对应关系,关于这部分可以参考我的[第一期]实现一个简化版的 React Hook - useState FiberFiber 也是 React16 添加的一个新功能。 事实上 Fiber 类似 VDOM,也是一个数据结构,而且同样也是一个递归的数据结构。 为了解决 React 之前一次全量更新的”问题”, React 引入了 fiber 这种数据结构,并重写了整个调和算法,并且划分了多个阶段。 关于这部分内容,我只推荐一篇文章,Inside Fiber: in-depth overview of the new reconciliation algorithm in React 其实之前我的从零开始实现 React 系列教程 也欠了 fiber 😄, 等我开心的时候补充上去哈。 Git我之前写过一个 Git 终端(代码被我 rm -rf 啦)。 这过程还是用到了很多数据结构和算法的,我也学到了很多东西, 甚至 React16 新的调和算法也有 Git 思想。 很直观的,Git 在推送本地仓库到远程的时候会进行压缩,其实这里就用到了最小编辑距离算法。Leetcode 有一个题目72. Edit Distance,官方难度hard, Git 的算法要是这个算法的复杂版本。 另外 Git 其实很多存储文件,数据的时候也用到了特殊的数据结构,我在这里进行了详细的描述,感兴趣的可以去看看。 WebpackWebpack 是众所周知的一个前端构建工具,我们可以用它做很多事情。至今在前端构建领域还是龙头老大 🐲 的位置。 Webpack 中最核心的 tapable 是什么,是如何配合插件系统的? webpack 是如何对资源进行抽象的,webpack 是如何对依赖进行处理的?更复杂一点 Tree Shaking 如何做,分包怎么做,加速打包怎么做。 其实 webpack 的执行过程是基于事件驱动的,tapable 提供了一系列钩子,让 plugin 深入到这些过程之中去。听起来有点像事件总线,其实其中的设计思想和算法细节要复杂和精妙很多。 关于这部分细节,我在我的从零实现一个 Webpack 之后会加入更多特性,比如 tapable ASTAST(抽象语法树)是前端 编译(严格意义上是转义)的理论基础,你如果想深入前端编译,就一定不能不会这个知识点。 和 AST 相似的,其实还有 CST,prettier 等格式化工具会用到, 有兴趣可以搜一下。 这个网站 可以让你对 AST 有一个直观的认识。 AST 厉害就厉害在它本身不涉及到任何语法,因此你只要编写相应的转义规则,就可以将任何语法转义到任何语法。这就是babel, PostCSS, prettier, typescript 等的原理,除此之外,还有很多应用场景,比如编辑器。 之前自己写过一个小型的生成 AST 的程序,源代码忘记放哪了。😅 Browser History像浏览器中的历史页面,移动端 webview 的 view stack, 都用到了栈这种数据结构。 剩下的我就不一一说了。其实都是有很多数据结构和算法的影子的。 OK,说了那么多。 这些都是“大牛”们做的事情,好像和我日常开发没关系啊。我只要用他们做好的东西,调用一下,一样可以完成我的日常工作啊。让我们带着这个问题继续往下看。 算法在日常开发中的应用都有哪些123大神: “你可以先这样,再这样,然后就会抽象为纯算法问题了。”我: “哇,好厉害。” 其实就是你没有掌握,或者“再思考”,以至于不能融汇贯通。 比如你可以用 vue 组件写一个递归,实现一些递归的功能,也是可以的,但是大多数人都想不到。 接下来,我会举几个例子来说明“算法在日常开发中的应用”。注意,以下所有的例子均来自我的实际业务需求。 第一个例子 - 撤销与重做业务描述某一天,可(gai)爱(si)的产品提了一个需求,”我们的系统需要支持用户撤销和重做最近十次的操作。“ 让我们来回忆一下纯函数。 纯函数有一个特性是给定输入,输出总是一样的。 关于函数式编程可以参考我之前的几篇文章和一个分享 我们对问题进行一些简化,假设我们的应用是纯粹的数据驱动,也就是说满足纯的特性。 我们继续引入第二个知识点 - reducer. reducer 是一个纯函数,函数签名为(store1, action1) => store2。即给定 state 和 action,一定会返回确定的新的 state。 本质上 reducer 是 reduce 的空间版本。 假设我们的应用初始 state 为 state1, 我们按照时间先后顺序分别发送了三个 action,action1, action2, action3。 我们用一个图来表示就是这样的: 运用简单的数据知识,我们不难推导出如下关系: 如果对这部分知识点还比较迷茫,可以看下我之前的一篇文章,从零实现一个 Redux 解决方案基础知识铺垫完了,我们来看一下怎么解决这个问题。 第一种方案,我们可以将每次的store,即store1, store2, store3都存起来。比如我想回退到第二步,我们只需要将store2取出来,覆盖当前store,然后重新渲染即可。这种方案很直观,可以满足我们的业务需求。但是缺点也很明显,store在这里被存储了很多。 每次发送一个action都会有一个新的store被存起来。当我们应用比较大的时候,或者用户触发了很多action的时候,会占据大量内存。实际场景中性能上我们很难接受。 第二种方案,有了上面的铺垫,我们发现, 事实上我们没必要存储所有的store。因为store可以被计算出来。因此我们只要存储action即可。比如我们要回退到第二步,我们拿出来store1,然后和action运算一次,得到store2,然后将store2覆盖到当前的store即可。 这种做法,只需要存储一个store1, 以及若干个action。 action相对于store来说要小很多。这是这种做法相比与上一种的优势。同时由于每次都需要从头(store1)开始计算,因此是一种典型的“时间换空间”的优化手段。 实际上这种做法,我们可以进行小小的优化。比如我们设置多个snapshot,然后我们就不必每次从头开始计算,而是算出最近的一个snapshot,然后计算即可。 无疑这种做法可以减少很多计算量,但是会增加空间占用。这是典型的“空间换时间”, 如果根据实际业务进行取舍是关键。 第三种方案,我们可以用树来表示我们的store。每次修改store,我们不是将整个store销毁然后创建一个新的,而是重用可以重用的部分。 如图我要修改 store.user.age。我们只需要将root和user的引用进行修改,同时替换age节点即可。 如果大家对immutable研究比较深的话应该能发现,这其实就是immutable的原理 第二个例子 - 巨型Mapper的优化业务描述由于业务需要,我们需要在前端缓存一些HTTP请求。我们设计了如下的数据结构,其中key表示资源的URL,value会上次服务端的返回值。 现在我们的项目中已经有上千个接口,当接口多起来之后,缓存占用会比较大,我们如何对此进行优化? 注: 我们的key中的前缀是有规律的,即有很多重复的数据在。 返回值也有可能是有很多重复的。 这是一个典型的数据压缩算法。数据压缩算法有很多,我这里就不介绍了,大家可以自行了解一下。 对数据压缩算法感兴趣的,可以看下我之前写的游程编码和哈夫曼编码 第三个例子 - 实现自动联想功能业务描述现在很多输入框都带了自动联想的功能, 很多组件库也实现了自动填充组件。 现在需要你完成这个功能,你会怎么做? 我们可以用前缀树,很高效的完成这个工作。 对这部分原理感兴趣的可以看下我的这个题解 第四个例子 - 相似度检测业务描述由于业务需要,我们需要对字符串进行相似度检测。对于相似度超过一定阀值的数据,我们认为它是同一个数据。 关于相似度检测,我们其实可以借助“最小编辑距离”算法。对于两个字符串a和b,如果a和b的编辑距离越小,我们认为越相似,反之越不相似。 特殊情况,如果编辑距离为0表示是相同的字符串,相似度为100%。 我们可以加入自己的计算因子,将相似度离散在0 - 100%之间。 这部分的内容,我在介绍Git的时候介绍过了,这里不再重复。 其实我们可以进一步扩展一下,比如对于一些无意义的词不计入统计范围,我们可以怎么做? 算法不好会怎么样这恐怕是很多人最关心的问题。 我虽然知道了算法有用,但是我不会怎么办?会有什么样的影响呢? 这就回到了我们开头的问题,“为什么很多人觉得算法没用”。事实上,我们日常开发中真正用到算法的场景很少,大部分都被别人封装好了。即使真正需要用到一些算法,我们也可以通过一些“低劣”的手段完成,在很多对性能和质量要求不高的业务场景都是没有问题的。 这就是为什么“前端同学更加觉得算法没用”的原因之一。 那既然这么说,是不是真的算法就没用呢? 或者说算法不好也不会怎么样了么?当然不是, 如果算法不好,会很难创新和突破。 想想如今前端框架,工具的演进,哪一个不是建立在无数的算法之上。 将视角聚焦到我们当下的业务上,如果算法不好,我们也同样很难让业务不断精进,不断赋能业务。 React框架就是一个非常典型的例子,它的出现改变了传统的编程模式。Redux的作者,React团队现任领导者 dan 最近发表了一篇个人博客 Algebraic Effects for the Rest of Us这里面也有很多算法相关的东西,大家有兴趣的可以读读看。 另外我最近在做的一个 stack-visualizer,一个用于跟踪浏览器堆栈信息,以便更好地调试地工具, 这个也是和算法有关系的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"每日一荐 2019-09 汇总","slug":"daily-featured-2019-09","date":"2019-09-04T16:00:00.000Z","updated":"2023-01-05T12:24:49.674Z","comments":true,"path":"2019/09/05/daily-featured-2019-09/","link":"","permalink":"https://lucifer.ren/blog/2019/09/05/daily-featured-2019-09/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-092019-09-30[工具]今天是 9 月的最后一天,明天就是十一了,提前祝大家国庆节快乐 ~~~ 🎉🎉🎉🎉🎉🎉🎉 ! 今天再来给大家安利 6 个 chrome 扩展程序,排名不分先后。 Proxy SwitchyOmega 此扩展为 SwitchySharp 的升级版,可替代 SwitchyPlus 或 Proxy Switchy. 可轻松快捷地管理和切换多个代理设置,是一个我使用多年的一个插件,配合 PAC 食用味道更好。 下载地址 OneTab 节省高达 95%的内存,并减轻标签页混乱现象。 有些标签关了舍不得,不关太多看的很乱并且更关键的是占用内存。有了这个工具就不存在这些问题了。 下载地址 AdBlock Plus Adblock Plus 是世界上最流行的浏览器扩展,世界各地有数百万用户在使用它。这是一个社区驱动的开源项目,有数百名志愿者为 Adblock Plus 的成功作出了贡献,以实现所有烦人的广告被自动阻挡。 下载地址 Multi-highlight 普通的网页搜索只能一个一个搜索,不能同时搜索多个关键字,这个扩展提供了这种功能上的扩展。 下载地址 HTML5 Outliner 我平时再看文章或者文档的时候习惯先看一遍目录或者大纲,然后再决定我到底要不要看,看哪里。我相信很多人和我一样有着同样的习惯。但是很多网站,包括 infoq,知乎等大网站这方面都做的比较差。下图是我的个人博客的大纲功能: 因此这款工具对于我这种人来说就非常重要了,他能根据当前网页的结果快速生成大纲,并且支持锚点功能,当然它也有很多覆盖不到的情况,因为标题的种类实现太多了,大家完全可以写一个div class = 'header'从而让这个工具无用武之地。 这也从侧面说明了语义化的重要性,不仅对于障碍人士重要,对于无障碍人士也有很大的意义。 下载地址 眼睛护航 把网页背景变成眼睛保护色或适合夜晚阅读的夜间模式,一些无法变色的小区块可以通过单击进行变色。到了晚上将自动从绿色护眼模式变为夜间阅读模式。当然,也可以手动强制使用绿色模式或夜间模式。 这在一些体验差的网站上极为重要,还有一些大量使用亮瞎眼的颜色网站也有很好的作用,类似提升阅读体验的扩展还有简悦。 下载地址 2019-09-29[工具]如果你是一个站长,那么你一定需要一个网站速度测试工具。 你的用户如果都是中国用户,那么用站长工具-国内测试应该就够用了。 如果你的用户有海外的话,可以试试站长工具-国际测试 (国内测速) (国际测试) 不得不吐槽下,网站体验做的不太好,而且广告有点多。 另外还有一个网站,不过这个只能够测试国内的网速,17ce的体验做的稍微好一点,广告也没有那么显眼,如果你的客户只是国内,不妨考虑这个。 最后介绍一个国外的网站pingdom,如果客户是全球的,可以考虑用这个,这个是这几个中用户体验做的最好的。给出的技术指标比较多一点,但是他没有区域分布热力图, 并且支持的区域也不多。 2019-09-27[类库]之前我写了一篇 【前端测试】 的草稿,一直搁置到现在,之前我做后端的时候,写测试尤其是单元测试是很普遍的。但是做前端这么久发现大家对这方面不是很重视, 其实前端也是非常需要做测试的。 今天给大家推荐的是一个非常流行的前端测试框架 jest 的 GUI 工具majestic (⚡ Zero config GUI for Jest) 2019-09-26[工具]你一定有想用某一个功能的快捷键,却想不出来的情况。也一定面临过多个软件快捷键不统一带来的烦恼,比如 A 软件CMD + S是保存, 另外一个软件 B 是Shift + S。 对于第一种问题,我们可以用一个叫 cheatsheet 的 mac 软件,有了它你就可以通过长按 command 键,从而查看当前应用的快捷键。 cheatsheet 下载地址: https://cheatsheet-mac.en.softonic.com/mac 顺便吐槽一下,cheatsheet 官网用户体验这块做的不怎么样 对于按键不统一的问题,我们可以直接修改对应软件的快捷键即可,毕竟大多数软件都是支持快捷键定制的,还有一些服务快捷键我们可以去系统偏好设置 - 键盘 - 服务中修改。 另外给大家安利一个软件Karabiner-Elements, 它是一个 mac 上好用的键盘定制工作,可以用来改键,定制超级键等,更多功能等你挖掘。 配合 HHKB 效果更佳 Karabiner-Elements 下载地址: https://github.com/tekezo/Karabiner-Elements 2019-09-25[技能]熟练使用命令行是一种常常被忽视,或者被认为难以掌握的技能,一旦掌握,会极大提高你工作的效率。当你能够熟练掌握这里列出的所有技巧时,你就学会了很多关于命令行的东西了。 今天介绍的这个仓库,首发于 Quora, 后面迁移到了 Github,并由众多高手做出了许多改进,现在已经有 6W+ Star 了。 仓库目录(目录是我用工具自己抓的,非官方提供): 仓库地址: https://github.com/jlevy/the-art-of-command-line/blob/master/README-zh.md 2019-09-24[工具]今天给大家分享的是 VSCode 前一段时间推出的 SSH 扩展,实际使用效果非常棒,除了延迟,让你感觉不到你是在操作远程的文件。虽然有延迟,但是也仅仅限于你和服务器有 IO 交互的情况下才会有感知,结合我的使用体验来说,是“几乎”感觉不到差异(当然也有可能我的网比较快)。 VSCode SSH 扩展允许你连接到远程的主机,虚拟机或者是容器。而你所需要做的仅仅是点击 SSH 面板,然后配置一下就好了,配置也极其简单,对于经常使用 SSH 的你来说千万不要错过了。 下面是官方提供的原理架构图: 地址: https://code.visualstudio.com/docs/remote/ssh 2019-09-23[好文]为什么一行 80 个字符是一个值得遵守的规范,即使你拥有 4k 显示器? 我个人一直是 80 字符的践行者,不仅仅是因为是这大家都普遍采用的标准,更重要的是我个人更习惯多窗口平铺的方式来展示我的窗口,这样效率更高一点,因此太大肯定会影响窗口平铺,太小又不方便阅读,80 对我来说其实刚刚好,其他比较常见的还有 100 字符等, 现在就让我们来看下为什么一行 80 个字符是一个值得遵守的规范吧。 文章地址: https://nickjanetakis.com/blog/80-characters-per-line-is-a-standard-worth-sticking-to-even-today 2019-09-20[工具]我开启了个人博客,增加了评论,分类,统计,RSS,歌单等功能, 之后的文章会在博客首发。 感兴趣的可以 RSS 订阅我的博客。订阅方法我画了个图。 RSS 是一种消息来源格式规范,用以聚合经常发布更新数据的网站,例如博客文章、新闻、音频或视频的网摘。RSS 文件包含全文或是节录的文字,再加上发布者所订阅之网摘数据和授权的元数据。 简单来说只要提供了符合 RSS 格式的数据源,你就可以订阅,然后在 RSS 阅读器上进行查看更新内容。 关于 RSS 订阅,今天我推荐的就是一个 RSS 的聚合器 feedly。https://feedly.com Feedly 是一个 RSS 聚合器应用程序,支持各种网页浏览器和运行 iOS 或 Android 的移动设备,也是一个基于云端的服务。其从各种在线资源聚合用户自定的新闻订阅源,并可与他人分享。 后续有机会我会向大家推荐我的 RSS 订阅源。 2019-09-19[工具]今天给大家推荐一款 MarkDown 编辑器。 MarkDown 在程序员中的使用频率是非常高的,Github 是最早一批对 MarkDown 语法支持度比较好的平台之一。我日常写文档,记笔记等都采用 MarkDown 格式来书写。 它不仅书写方便,而且由于其格式比较规范,因此理论上可以通过一些“转换规则”将其转化为各种表现形式,市面上也有很多基于 Markdown 的渲染器,比如markdown-it,也有很多基于这些渲染器制作的产品,比如docsify。 早些时候,我使用的比较多的是MacDown和 VSCode 自带的 Markdown 功能。这两个功能非常简单,但是却能满足我当时的需求,之后我开始经常用 Markdown 更新文章之类的,因此这些就显得不太够用了,现在我用的是 Yu Writer, 算是一个值得推荐的国人开发的 MarkDown 编辑器,功能非常强大而且免费。 你可能听过 MWeb,但是它是收费的,功能和这个比起来也并不占优势。 下载地址:https://ivarptr.github.io/yu-writer.site/ 2019-09-18[工具]前天分享了我的 chrome 插件管理器,今天我们就来分享我的《娱乐插件》。 listen1 娱乐插件第一个要分享的是一个听歌的插件,各个平台都有一些独家的音乐,就像视频网站一样,这就可怜了我们这些消费者。如果想要听所有的音乐就要办理各个 APP 的会员,或者在多个音乐 APP 中切换。 这个插件能让我们听到所有国内几个主流大平台的所有音乐,足不出户畅享所有的音乐,并且值得称赞的是它支持会员系统,你可以保存你的歌单,甚至可以直接登陆你的 Github 账户同步多端的数据。 仓库地址:https://github.com/listen1/listen1 Video Downloader Professional 我主要用它来下载 Youtube 的视频,据说可以下载任何视频网站的视频,但是我亲测了几个网站不可以。 扩展下载地址:https://chrome.google.com/webstore/detail/jpaglkhbmbmhlnpnehlffkgaaapoicnk Bilibili 全家桶 经常看番的朋友怎么能少的了几个好用的插件护体呢? 这几个插件的功能基本满足了我看番的所有需求,包括弹幕合并,查找弹幕,自动签到,一键直达,猜你喜欢等等,大家可以安装下自己体验。 bilibili 助手 pakku 哔哩哔哩弹幕过滤器 bilibili 猜你喜欢 2019-09-17[学习方法]很多人想要问我“你的成长史是怎么样的?能不能分享一下你的菜鸡成长史”。 开始我是抵触的,这种东西写的不好大家会骂你,写的“太好”也会骂你。 今天我就来做个“lucifer”系列的开篇吧,用图来描述“lucifer 的一天”。 lucifer 的早晨: lucifer 搬砖的一天开始了: lucifer 的晚上: 2019-09-16[工具]经常有同学问我“你的这个扩展看着不错,叫什么”, “有什么好用的扩展程序推荐么?”。 因此我打算出一个《工具推荐》专题, 然后细分一个类别《工具推荐 - chrome 插件》。 这个算是这个系列的开篇之作,我默默翻开自己的 chrome 插件列表来看,有什么好用的推荐给大家。突然灵机一动,干脆把这个“扩展插件管理器”安利给大家好了。之后我会向大家推荐更多好用好玩的插件,有“工具”,“效率”, “娱乐”,“前端”等等。 我的 chrome 插件差不多有 60 多个,插件多起来的时候,良好的分类,开启关闭,禁用,卸载等管理就变得非常重要了。毕竟谁也不想在众多插件中寻寻觅觅的感觉,也不想因为开启太多插件吃我们宝贵的内存吧?这个插件的名字是扩展管理器(Extension Manager) 对于没有梯子的同学,我还贴心地给大家准备了我从官方下载的扩展文件。 链接 2019-09-12[类库]今天给大家推荐的是一个在给 Git 提交记录增加 emojis 的仓库。 或许你知道AngularJS Git Commit Message Conventions , 现在很多开源项目和内部项目都在使用,是一道非常流行的 git commit message 规范。 它的好处就是你可以很轻松地通过提交信息,看出这次提交干的什么,影响范围等,比如是新特性,修复 bug,增加文档, 重构代码等。 这个项目可以说更进一步,通过图的形式,让你更快地感知到这些信息,可视化形式的沟通毫无疑问是最最有效的。因为人的大脑更擅长处理图像。 项目提供了几十种 emoji,几乎覆盖了所有的场景。 仓库地址: https://gitmoji.carloscuesta.me/ 2019-09-11[技能]Google 内部有很多通用的最佳实践,在这我推荐一个项目,这是挂在 google group 下的一套通用的工程实战指南,被各个项目广泛使用,覆盖全部的编程语言。 这个仓库分成两部分: 这部分是给 Code Reviewer(代码评审者)的指南 这部分是给 Change Author(CL 作者)的指南 代码评审者指南本来是一个完整的文档,一共分为 6 部分,读者可根据需要阅读。 修改列表(Change List/CL)制定者指南包括一些浏览代码评审的最佳方式,开发者可以快速处理评审结果。 项目地址: https://github.com/google/eng-practices 2019-09-10[类库]今天给大家推荐的是一个打包平台,不知道大家有没有听说过“polyfill.io”,用法有点像。 这个仓库是 fork 自 packed,并进行了魔改,你可以将多个包打包成一个单独的 ESM,支持多种 options, 仓库地址: https://github.com/webcomponents-dev/packd-es 2019-09-09[类库]一个可以将草稿转化 HTML 的工具,利用了机器学习来生成页面。 你可以手画一些东西,然后将其直接生成静态页面。缺点也很明显,一方面是静态的,因此没有什么交互,对于交互强的应用没什么用。其次就是生成的是 HTML,可维护性会比较差,如果生成类似 JSX 这样的中间产物可能会好一点。当然市面上其实已经有了生成 JSX 产物的开源框架了。 地址:https://github.com/ashnkumar/sketch-code 2019-09-06[学习方法, 好文]如何培养自己的程序员思维。- Problem-solving is the meta-skill. 文章地址: https://learnworthy.net/how-to-think-like-a-programmer/?utm_source=quora&utm_medium=referral 2019-09-05[类库]这是微软开源的内部用来构建大型应用的工具库,包括接口管理,文档管理,代码仓库管理等。 地址: https://github.com/microsoft/web-build-tools 历史汇总 暂无历史汇总 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-09","slug":"每日一荐/2019-09","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-09/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"布隆过滤器","slug":"bloom-filter","date":"2019-09-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.851Z","comments":true,"path":"2019/09/04/bloom-filter/","link":"","permalink":"https://lucifer.ren/blog/2019/09/04/bloom-filter/","excerpt":"假设你现在要处理这样一个问题,你有一个网站并且拥有很多访客,每当有用户访问时,你想知道这个 ip 是不是第一次访问你的网站。你会怎么做呢?","text":"假设你现在要处理这样一个问题,你有一个网站并且拥有很多访客,每当有用户访问时,你想知道这个 ip 是不是第一次访问你的网站。你会怎么做呢? hashtable 可以么一个显而易见的答案是将所有的 ip 用 hashtable 存起来,每次访问都去 hashtable 中取,然后判断即可。但是题目说了网站有很多访客,假如有 10 亿个用户访问过,每个 ip 的长度是 4 byte,那么你一共需要 4 * 1000000000 = 4000000000Bytes = 4G , 如果是判断 URL 黑名单,由于每个 URL 会更长,那么需要的空间可能会远远大于你的期望。 bit另一个稍微难想到的解法是 bit, 我们知道 bit 有 0 和 1 两种状态,那么用来表示存在,不存在再合适不过了。 加入有 10 亿个 ip,我们就可以用 10 亿个 bit 来存储,那么你一共需要 1 * 1000000000 = (4000000000 / 8) Bytes = 128M, 变为原来的 1/32,如果是存储 URL 这种更长的字符串,效率会更高。 基于这种想法,我们只需要两个操作,set(ip) 和 has(ip) 这样做有两个非常致命的缺点: 当样本分布极度不均匀的时候,会造成很大空间上的浪费 我们可以通过散列函数来解决 当元素不是整型(比如 URL)的时候,BitSet 就不适用了 我们还是可以使用散列函数来解决, 甚至可以多 hash 几次 布隆过滤器布隆过滤器其实就是bit + 多个散列函数, 如果经过多次散列的值再 bit 上都为 1,那么可能存在(可能有冲突)。 如果有一个不为 1,那么一定不存在(一个值经过散列函数得到的值一定是唯一的),这也是布隆过滤器的一个重要特点。 布隆过滤器的应用 网络爬虫判断某个 URL 是否已经被爬取过 K-V 数据库 判断某个 key 是否存在 比如 Hbase 的每个 Region 中都包含一个 BloomFilter,用于在查询时快速判断某个 key 在该 region 中是否存在。 钓鱼网站识别 浏览器有时候会警告用户,访问的网站很可能是钓鱼网站,用的就是这种技术 从这个算法大家可以对 tradeoff(取舍) 有更入的理解。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"布隆过滤器","slug":"算法/布隆过滤器","permalink":"https://lucifer.ren/blog/categories/算法/布隆过滤器/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"}]},{"title":"从老鼠试毒问题来看二分法","slug":"laoshushidu","date":"2019-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.555Z","comments":true,"path":"2019/06/13/laoshushidu/","link":"","permalink":"https://lucifer.ren/blog/2019/06/13/laoshushidu/","excerpt":"很多人对于二分法的理解比较片面,之前碰到一个题目,从一个先升序后降序的数列中,比如 1 2 3 7 4 3 2 中运用二分法去查找一个给定的元素,很多人说根本不能二分,因为没有排序。其实 这道题完全可以使用二分查找进行解答, 如果你觉得不可以的话,很可能对二分法理解还比较片面。 这里以另外一个更加有趣(至少我认为)的例子来讲解一下二分法。","text":"很多人对于二分法的理解比较片面,之前碰到一个题目,从一个先升序后降序的数列中,比如 1 2 3 7 4 3 2 中运用二分法去查找一个给定的元素,很多人说根本不能二分,因为没有排序。其实 这道题完全可以使用二分查找进行解答, 如果你觉得不可以的话,很可能对二分法理解还比较片面。 这里以另外一个更加有趣(至少我认为)的例子来讲解一下二分法。 题目面试题: 有 1000 个一模一样的瓶子,其中有 1 瓶是毒药,老鼠喝了有毒的会在 24h 之后死亡。求最少需要多少老鼠才能在 24h 里找到有毒的那瓶。 解法这道题的解法有很多,今天我们来聊下用二分法来解这道题。 这道题似乎和我们看的的常见的二分法有很大的区别,但是仔细想一下, 二分法本质是将问题的规模缩小到原有的一半,带着这样的思想我们再来看一下。类似的,三分法就是将问题规模缩小为原来的 1/3. 我们先对 1000 个瓶子进行编号,从 1-1000 这样子。 不过我们不是通过我们大家平时生活中使用的十进制,而是使用再计算机中使用的二进制, 同时让大家感受一下二进制的魅力。 为了方便讲解,我们假设不是 1000 个瓶子,而是 4 个。 我们来编一下号: 123400 // #101 // #210 // #311 // #4 我们的目标是找到哪个瓶子有毒,换句话说我们目标是找到有毒瓶子的编号,再换句话说我们的目标是找到有毒瓶子的 3 个 bit 分别是什么,是 0 还是 1. 比如有毒的是 3 号瓶子,那么我们就是想确认第一个 bit 是 0,第二个 bit 是 1,第三个 bit 是 1,即 011,转化为 10 进制就是 3 号。 那么如何确定每一个 bit 是什么呢? 回想一下,我们手上有老鼠,老鼠有两个 state,alive 或者 died,这就是我们拥有的全部。 接下来我们逐一对瓶子进行分组,分组的依据就是每一个 bit 的值。 比如: 12345// 00 01 #g1:1 第一个bit是0// 10 11 #g1:2 第一个bit是1// 00 10 #g2:1 第二个bit是0// 01 11 #g2:2 第二个bit是1 我们来找第一个老鼠#1 来喝 g:1:1, 如果他死了,那么毒就在这一组,也就是说毒的第一个 bit 是 0,否则是 1 我们来找第二个老鼠#2 来喝 g:2:1, 如果他死了,那么毒就在这一组,也就是说毒的第二个 bit 是 0,否则是 1 所以我们可以看出, 两只老鼠就搞定了,我们按照这个思路,可以推到出 1000 个瓶子只需要 10 个瓶子, 即 log2 1000, 2 的 10 次方是 1024,因此 10 个老鼠够了,如果 1025 个瓶子的话,就需要 11 个老鼠了。 如果你仔细思考的话,不难看出,我们是在用老鼠喝了水之后的反应(生或死)来进行判断每一个 bit 的数字,不管生死,我们总能得出这个 bit 的值,是 0 还是 1. 因此每使用一只老鼠我们都将问题规模缩小为原来的 1/2. 这是典型的二分法。 这是最优解么是的,这是最优解,如果你愿意用严格的数学来证明的话,你可以试一下数学归纳法。 如果你想感性的判断一下的话,可以继续往下读。 什么是最优解? 最优解就是要让未知世界无机可乘,也就是说在最坏的情况下得到最优(现实世界都是未知的)。上面的例子,不管小老鼠是生还是死,我们都可以将问题规模缩小到 1/2. 也就是说最坏的情况就是最好的情况,也就是说没有最坏情况。 那么我们是否可以将问题规模缩小的 1/3 或者更小呢? 我们可以三分么简单来说,不可以, 因为老鼠只有两种 observable state, 即 alive, died. 假如我们有 10 个小球,其中有一个是异常的,其他 9 个都是一样的,我们怎么才能通过最少的称量来确定是哪一个异常,是重还是轻? 这个时候我们就可以使用三分法了,为什么?因为天平有三个 state, 平衡,左倾,右倾,使得我们”有可能“ 将问题规模缩小为 1/3, 事实上,确实可以实现将问题规模缩小到 1/3。 我会在之后的文章中进行讲解小球的问题最优策略, 并解释为什么这是最优策略。 Bonus基于比较的排序都无法逃脱 nlogn 时间复杂度的命运,这是为什么?能否利用本篇文章的思想进行解释?","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"二分法","slug":"算法/二分法","permalink":"https://lucifer.ren/blog/categories/算法/二分法/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"}]},{"title":"《数据结构与算法之美》-为工程师量身打造的数据结构与算法私教课","slug":"ad","date":"2010-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.710Z","comments":true,"path":"2010/03/04/ad/","link":"","permalink":"https://lucifer.ren/blog/2010/03/04/ad/","excerpt":"很多人问我如何系统地学习数据结构与算法,是看书,刷题还是看视频? 这个问题没有一个放之四海而皆准的答案,这是一个因人而异的东西,我之前给初学者推荐过邓俊辉老师免费的《数据结构与算法》课程,以及为《算法图解》这本书。 然而这些只是适合初学者,真正想要掌握数据结构与算法还是不够的,学习了基础之后如何进阶呢?像《算法导论》这些经典书籍,虽然很全面,但是过于缺乏重点。很多人学起来都非常困难。而市面很多在线课程或者线下课程,大多是为了“应试”,只讲一些看似通用,实则脱离真实开发场景的内容。因此这里给大家推荐一本书《数据结构与算法之美》。","text":"很多人问我如何系统地学习数据结构与算法,是看书,刷题还是看视频? 这个问题没有一个放之四海而皆准的答案,这是一个因人而异的东西,我之前给初学者推荐过邓俊辉老师免费的《数据结构与算法》课程,以及为《算法图解》这本书。 然而这些只是适合初学者,真正想要掌握数据结构与算法还是不够的,学习了基础之后如何进阶呢?像《算法导论》这些经典书籍,虽然很全面,但是过于缺乏重点。很多人学起来都非常困难。而市面很多在线课程或者线下课程,大多是为了“应试”,只讲一些看似通用,实则脱离真实开发场景的内容。因此这里给大家推荐一本书《数据结构与算法之美》。 程序员必会的数据结构与算法 订阅量 TOP1这本书是订阅量 Top1,50000+程序员的算法课堂,整个专栏会涵盖 100 多个算法真实项目场景案例,更难得的是它跟市面上晦涩的算法书籍不同的是,还手绘了一些清晰易懂的详解图(总共有 300 多张),市面上的大多数的算法教程都看过,走心的说,这个专栏是市面上唯一一门真正适用于工程师的专栏,作者是前 Google 工程师王争,相信会开启你的趣味学习算法之旅。 作者简介本书作者王争,前 Google 工程师,从事 Google 翻译相关的开发工作,深入研究算法数十年。现任某金融公司资深系统架构师,核心业务接口平台负责人,负责公司核心业务的架构设计、开发,以及可用性、高性能等相关技术问题的解决。 你能获得什么?1、掌握数据结构与算法的核心知识 我根据自己研读数十本算法书籍和多年项目开发的经验,精选了 20 个最实用数据结构和算法结合具体的软件开发实例,由浅入深进行讲解背后的设计思想,并适时总结一些实用“宝典”,保证你印象深刻,并且能够迅速对应到实际工作场景中。 2、提升算法思维,训练解决实际开发工作难题的强能力 这部分我会讲一些不是那么常用的数据结构和算法。虽然不常用,但是并不是说他们没用。设置这一部分的目的,是为了让你开拓视野,强化训练算法思维、逻辑思维。如果说学完基础部分可以考 80 分,那掌握这一部分就能让你成为尖子生。再回过来说,不管是现在流行的区块链技术还是人工智能,核心代码实现中都会涉及到这些算法。 3、学习开源框架、底层系统的设计原理,提升工作实战技能 最后我会通过实战部分串讲一下前面讲到的数据结构和算法,并且结合 Redis、Disruptor 这样的开源项目,剖析它们背后的数据结构和算法,帮你提升读懂源码的能力(JDK 很多源码,不乏大量的数据结构,例如大家喜闻乐见的面试题 HashMap)。 我掰着指头数了下,整个专栏会涵盖 100 多个算法真实项目场景案例。我还手绘了一些清晰易懂的详解图,帮你理解核心概念和实现过程,展示每个知识点的框架逻辑,让晦涩难懂的算法变得轻松有趣。 课程目录 订阅福利扫描下方二维码订阅即可,新人立减 30 元,另外我本人提供返现 11 元(到手 88 元),直接加我微信DevelopeEngineer即可。另外再送你 199 元限时学习礼包,你可以先领券再购买,领券地址:http://gk.link/a/108qc","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"}]},{"title":"搬迁声明","slug":"oschina","date":"2008-12-31T16:00:00.000Z","updated":"2023-01-05T12:24:49.521Z","comments":true,"path":"2009/01/01/oschina/","link":"","permalink":"https://lucifer.ren/blog/2009/01/01/oschina/","excerpt":"我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:lucifer210,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply","text":"我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:lucifer210,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply","categories":[],"tags":[]}]} \ No newline at end of file +{"meta":{"title":"lucifer的网络博客","subtitle":"LeetCode 前端","description":"lucifer的个人博客,用来记录LeeCode刷题过程和心得,以及构建大前端知识体系","author":"lucifer","url":"https://lucifer.ren/blog","root":"/blog/"},"pages":[{"title":"404 Not Found","date":"2021-03-09T10:48:39.754Z","updated":"2021-03-09T10:48:39.754Z","comments":true,"path":"404.html","permalink":"https://lucifer.ren/blog/404.html","excerpt":"","text":"**404 Not Found** **很抱歉,您访问的页面不存在** 可能是输入地址有误或该地址已被删除"},{"title":"关于我","date":"2023-01-07T13:51:59.268Z","updated":"2023-01-07T13:51:59.268Z","comments":true,"path":"about/index.html","permalink":"https://lucifer.ren/blog/about/index.html","excerpt":"","text":"我是一个 Github 40K star 的前端架构师,leetcode 刷题插件 leetcode-cheatsheet 作者,掌握各种算法套路,写了十几万字的算法刷题套路电子书,公众号回复电子书获取。 除了我的本职工作外,我会在开源社区进行一些输出和分享,比较受欢迎的有宇宙最强的前端面试指南 和我的第一本小书 目前本人正在写一本关于《leetcode 题解》的实体书,因此可能更新会比较慢,如果有人想要做些贡献或者合作的也可以直接用下面的邮箱联系我, azl397985856@gmail.com。 我的前端干货,比如性能优化,工程化,架构思想以及前端领域的算法都会在公众号《脑洞前端》同步。 刷题困难?关注公众号《力扣加加》就够了。"},{"title":"所有分类","date":"2021-03-09T10:48:39.816Z","updated":"2021-03-09T10:48:39.816Z","comments":true,"path":"categories/index.html","permalink":"https://lucifer.ren/blog/categories/index.html","excerpt":"","text":""},{"title":"技术大佬和他们的博客","date":"2022-03-09T07:15:42.874Z","updated":"2022-03-09T07:15:42.874Z","comments":true,"path":"friends/index.html","permalink":"https://lucifer.ren/blog/friends/index.html","excerpt":"","text":"我的友链卡片博客名称:lucifer 的网络博客博客网址:https://lucifer.ren/blog/博客头像:https://tva1.sinaimg.cn/large/006tNbRwly1ga7ognflh9j30b40b4q3w.jpg 需要是 https 链接哦~ 博客介绍:一个脑洞很大的程序员,Github 40K LeetCode https://github.com/azl397985856/leetcode ,公众号《力扣加加》。 加入我如需添加友链,请添加微信(DevelopeEngineer)备注“友链交换”,格式如上。 除了提供上面必须的基本信息之外,你还可以提供: PV 和 UV 数据 feedly 订阅地址 以上数据是为了计算你的排名,为了你的排名更加靠前,鼓励大家提供。"},{"title":"","date":"2021-03-09T10:48:39.817Z","updated":"2021-03-09T10:48:39.817Z","comments":true,"path":"mylist/index.html","permalink":"https://lucifer.ren/blog/mylist/index.html","excerpt":"","text":""},{"title":"所有标签","date":"2021-03-09T10:48:39.817Z","updated":"2021-03-09T10:48:39.817Z","comments":true,"path":"tags/index.html","permalink":"https://lucifer.ren/blog/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"How To Make Monney","slug":"make-money","date":"2024-03-09T16:00:00.000Z","updated":"2024-03-10T13:11:10.453Z","comments":true,"path":"2024/03/10/make-money/","link":"","permalink":"https://lucifer.ren/blog/2024/03/10/make-money/","excerpt":"有哪些赚钱的方法?具体如何操作?它们的赚钱逻辑是什么?什么情况下会亏钱?","text":"有哪些赚钱的方法?具体如何操作?它们的赚钱逻辑是什么?什么情况下会亏钱? 1. 了解你赚的是什么钱首先你要知道你赚的是什么钱。即什么情况下会赚钱,什么情况下会赔钱。 这个问题可以很简单,也可以很复杂。 了解赚的什么钱可以很简单。比如赌马,你赌对了就赚钱,赌错了就赔钱。再比如做生意,利润大于 0 就赚钱,否则就赔钱。再比如做股票,你买了股票,股票涨了你赚钱,股票跌了你赔钱。 了解赚的什么钱也可以很复杂。 比如做空股票,你融券卖了股票,股票跌了你赚钱,股票涨了你赔钱,因为你是借的股。做空外汇也是一样的。 再比如结构化理财产品,你买了一个结构化理财产品,产品的收益是由多种金融工具的表现决定的,比如股票、债券、商品、汇率等。这时候你就需要了解这些金融工具的表现,才能知道你赚的是什么钱。除此之外,结构化产品还会有很多附加条件。 后面我们了解赚钱的手段的时候,一定先要搞清楚赚的是什么钱。即什么时候我会赚钱,什么时候我会赔钱。 2. 赚钱的手段存款,撸空投,新币挖矿基本是无风险的。而铭文和基金是有风险的。其中撸空投,只需要时间(有手就行)。 利用信息差则是一个很大的话题,这里不做详细介绍。只是有一些有意思的信息差例子的时候会提一下,帮助大家打开思路。 2.0 存款将钱存到银行,银行会给你利息。利息是你赚的钱。利息是由存款金额、存款期限、利率决定的。 也可以存货币基金,可以 T+0 支取。部分货币基金,比如支付宝,微信,招商银行都提供随时消费的货币基金。 另外你可以转化为 u,然后存活期。部分时间 u 的利率会特别高,比如最近(2024-02) u 的利率普遍都是年化 10%。 如果不考虑国家和社会的重大变故,这类理财几乎没有风险,因此风险账面上不会赔钱。 什么时候我会赚钱?账面上一定会赚钱,不过有可能是约定日子才能取。由于这种理财方式时间会比较久,因此可能需要考虑通货膨胀的影响。这样的话存款利率大于通货膨胀率的时候会赚钱。 什么时候我会赔钱?存款利率小于通货膨胀率的时候。 2.1 撸空投撸空投是一种赚钱的手段。空投是指项目方免费发放代币给用户。撸空投就是指用户通过参与项目方的活动,获取免费的代币。 比如之前参与的 strk 空投,就可以通过绑定 github 免费获取 111 strk 代币。 什么时候我会赚钱?空投的代币上线交易所,且价格大于 0 的时候。什么时候我会赔钱?理论上不存在,因为你没有任何资金投入。 也有的空投是直接不需要你参与,叫零交互空投。直接给你转 token 的,这种空投是最好的,因为你不需要任何成本(时间成本都没有),直接就有钱了。 那么如何提前埋伏零交互空投呢? 首先要储备头部资产: 蓝筹 NFT(人数多,共识高),如 NodeMonkey、Bitcoin Frog、Bitcoin Puppets 等。 BRC 20 头部铭文,如 ORDI、SATS。 各个协议的头部资产,如 ARC 20 中的 Atom 和 Quark,BRC 420 中的蓝盒子等。 其次,要做好资产分钱包,通过上述的例子发现,各个钱包空投的资产近乎相同,为了体现出比特币生态的公平性,未来大部分空投还是会设定一个下界限(像 Runestone 的空投规则一样,只要满足要求就可以获得等额的空投份额)。 最后,做好资产隔离,避免不同协议资产混用。 Reference: 空投大年,一文梳理值得参与的项目 2024 测试网交互大全:埋伏下一个空投大毛 2.2 铭文铭文是一种利用新技术在比特币上创建的数字印记。如果将比特币比作数字黄金,那么铭文便是黄金饰品,它们共享相同的本质。 有人为铭文起了一个好名字——BRC-20,称其为新的代币分发方式,没有项目方、没有跑路、没有 rug pull 风险,让大家机会均等。然而,事实真的如此吗?? BRC20 代币是一种在比特币链上铸造的实验性 meme 代币,利用 ordinals 协议把代币部署在链上,任何人都可以铸造,按照先到先得原理,不支持智能合约。 ERC-20 的出现才是重头戏,解决了高昂 gas 费,提高了出快速度,完美解决了 BRC-20 的弊端,现在铭文市场乱七八糟什么项目都来凑一下热闹,但把问题简单化,BRC-20 提出了概念,ERC-20 解决了问题,也就是说目前只有这两个具备实际价值,其它都是蹭热度! 整个打铭文的流程简单来讲就分为三个步骤。 一、准备好 web3 钱包并充好 btc 也就是 gas 费,一般选择 unisat 或者 uni web3 钱包。二、查询要打的铭文的铭刻情况看是否已经被打完,在 unisat 网站都可以查到。三、选择一个手续费比较低的时间点进行 mint,一般零晨会低一点 流程看似是很简单的,但是注意事项目,小伙伴们一定要搞清,以免出现一些不必要损失。打铭文最大的开支就是 gas 费也就是手续费,一旦参与不管你是否最后能不能够打到,手续费都会花掉,不会退回的。所在,选择适当的手续费设置是很重要的。第二个注意事项,就是要打一些龙头的项目,有热度的项目,这样才能让你打的铭有价值可以流通。 铸造好的铭文可以在铭文市场进行出售,价格相差非常大,涨跌幅也非常大,价格就是铭文市场的交易价格,当然如果铸造失败,就可以理解为你的价格是 0。 铸币的成本约等于 gas 费。 什么时候会赚钱?铭文的代币上线交易所,且价格大于铸币成本。什么时候会赔钱?铸币成本大于代币价格。 2.3 新币挖矿新币挖矿是一种赚钱的手段。新币挖矿是指用户通过参与项目方的活动,获取代币。新币挖矿的活动有很多种,比如质押、流动性挖矿、空投等。 你可以将 bnb 存到活期,当有新币可以挖的时候,就会自动挖矿。挖矿结束一般一天左右就可以交易,你可以将其转化为 usdt 等其他币种。 什么时候我会赚钱?赚的钱是新币,付出的是 bnb 等其他货币。因此 bnb 等其他货币的跌幅小于新币的卖出价的时候会赚钱。反之会赔钱。 2.4 基金刚刚提到了货币基金。除此之外,也可以投资股票基金、债券基金、混合基金、指数基金等。 目前我买的比较多的是指数基金和股票基金。指数基金是一种被动投资的方式。指数基金的收益是由指数的表现决定的。比如沪深 300 指数基金,就是跟踪沪深 300 指数的表现。而股票基金是一种主动投资的方式。股票基金的收益是由基金经理的投资决策决定的。 债券基金基本上就是起到对冲的作用,债券基金的收益是由债券的表现决定的。比如债务人还不上钱,债券基金就会亏钱。 什么时候我会赚钱?基金的收益大于 0 的时候会赚钱。什么时候我会赔钱?基金的收益小于 0 的时候会赔钱。 那什么是基金的收益,如何看基金的收益呢?基金的收益是由基金的净值决定的。比如你买了一个基金,基金的净值是 1 ,过了一段时间,基金的净值变成了 1.1 ,那么你赚了 0.1 。反之亏了 0.1 。 那什么影响基金的净值。答案是基金经理持有的股票的表现。比如你买了一个股票基金,基金经理持有的股票价格都涨了,那么基金的净值就会涨。 那什么影响股票的价格?答案是股票的成交价格。具体来说就是出价中最低的。比如你卖了一个股票,你出价 10 元,别人出价 9 元,那么交易所优先处理 9 元的,如果有人愿意用大于等于 9 元的买就可能成交了,成交后这个股票的价格就是 9 元。 相反,如果你买了一个股票,你出价 10 元,别人出价 11 元,那么交易所优先处理 11 元的,如果有人愿意用小于等于 11 元的卖就可能成交了,成交后这个股票的价格就是 11 元。 这个成交价格的变动就是股价的变动。股价的变动就是基金的净值的变动。基金的净值的变动就是基金的收益的变动。 可以看出基金的收益其实就是大家的心理预期。比如大家都认为股票会涨,那么股票的价格就会涨,基金的净值就会涨,基金的收益就会涨。那么什么时候大家会认为股票会涨呢?答案是有利好的消息。这也是大家都对一些利好和利空消息特别敏感的原因。 利好和利空消息在短期上基本上和股价是正相关的。长期上,还是要看公司的盈利能力。因此如果你是短期投资者,那么你就要关注利好和利空消息。如果你是长期投资者,那么你就要关注公司的盈利能力。 2.5 股票(或等价产品)这里的等价产品指的是金融衍生品,比如虚拟币,期权,期货,ETF 等。 什么是网格交易? 2.6 利用信息差比如最近 sora 特别火,很多人就在卖 sora 内测账号,卖 sora 课程。(虽然 sora 截止到目前还没开放内测,但这并不影响他们赚钱,因为韭菜不知道,这也是一种信息差) 当然信息差并不一定是这种消极的。比如很多网站都有推荐返佣,比如推荐返佣 10%,那么你可以通过推荐别人来赚钱。这个时候你可以搞一些路边的扫码送礼品的活动,比如你可以在路边发放一些小礼品,然后让别人扫码,这样你就可以赚到推荐返佣。相信大家在街上也遇到过这种人。 2.7 杠杆和合约杠杆和合约是两种风险比较大的赚钱的手段,两者有很多相似的地方。 杠杆是一种利用小额的资金来进行数倍于原始金额的投资,以期望获取相对投资标的物波动的数倍收益率,抑或亏损。 合约是买方同意在一段指定时间之后按特定价格接收某种资产,卖方同意在一段指定时间之后按特定价格交付某种资产的协议。 杠杆和合约有什么区别? 【1】.操作方法不一样:杠杆是通过平台借币的方式,来在现货市场中超额配置资产,操作过程就会包含借币费率+交易费率。合约是采用交割合约的模式,意味着在进行交易前就可以选择产品本身的杠杆倍数,这种模式去除了现货市场中需要借币才能进行杠杆的操作。 【2】.定义不一样: 杠杆交易就是利用小额的资金来进行数倍于原始金额的投资,以期望获取相对投资标的物波动的数倍收益率,抑或亏损。合约是买方同意在一段指定时间之后按特定价格接收某种资产,卖方同意在一段指定时间之后按特定价格交付某种资产的协议。 【3】.规则不一样:杠杆交易是投资者用自有资金作为担保,从银行或经纪商处提供的融资放大来进行外汇交易,也就是放大投资者的交易资金。期货合约是由交易所设计,经国家监管机构审批上市的标准化的合约。期货合约的持有者可借交收现货或进行对冲交易来履行或解除合约义务。 【4】.特点不一样:杠杆交易有 24 小时交易、全球性市场、交易品种少、风险可灵活控制,双向交易,操作灵活,高杠杆比例、交易费用低、入市门槛低等特点。期货合约的特点则是以小博大、双向交易、不必担心履约问题、市场透明以及组织严密,效率高。 什么时候我会赚钱? 如果做多,价格上涨超过手续费和交易费,你会赚钱。 如果做空,价格下跌超过手续费和交易费,你会赚钱。 需要特别注意的是,杠杆和合约都可能会发生爆仓,风险很大。 3. 庄家对散户的影响(你是如何亏钱的)TBD","categories":[{"name":"理财","slug":"理财","permalink":"https://lucifer.ren/blog/categories/理财/"}],"tags":[{"name":"理财","slug":"理财","permalink":"https://lucifer.ren/blog/tags/理财/"}]},{"title":"kuma - css-in-js 的未来?","slug":"kuma","date":"2024-03-06T16:00:00.000Z","updated":"2024-03-10T13:06:01.037Z","comments":true,"path":"2024/03/07/kuma/","link":"","permalink":"https://lucifer.ren/blog/2024/03/07/kuma/","excerpt":"kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。","text":"kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。 什么是 css-in-jsCSS-in-JS 是一种将 CSS 代码嵌入到 JavaScript 代码中的技术。它可以提供一些优势,例如更好的组件化、更好的性能、更好的开发体验等。 以 emotion 为例,如下代码就是一个典型的 css-in-js 的例子: 12345678910111213141516import React from \"react\";import { css } from \"@emotion/react\";const buttonStyles = css` padding: 10px 20px; border: none; border-radius: 4px; background-color: #007bff; color: #fff; font-size: 16px; cursor: pointer;`;function Button({ children }) { return <button css={buttonStyles}>{children}</button>;} 相对地,就是传统的 css 代码: 123456789.button { padding: 10px 20px; border: none; border-radius: 4px; background-color: #007bff; color: #fff; font-size: 16px; cursor: pointer;} 123456import React from \"react\";import \"./button.css\";function Button({ children }) { return <button className=\"button\">{children}</button>;} css-in-js 的优缺点首先我们来看下 css-in-js 的优点: 更好的组件化:css-in-js 可以让你将样式和组件放在一起,这样可以更好地组织代码。(想象一下引入 antd 等组件库的时候,我们需要自己单独引入一下 css 文件。而如果组件库是用 css-in-js 写的,就不会有这个问题了) 某些情况下有更好的性能:css-in-js 可以减少网络请求数量,因此某些情况下可以提供更好的性能。 由于 css-in-js 是运行时的,所以可以根据不同的条件动态生成样式。 然后我们来看下 css-in-js 的缺点: 学习成本:css-in-js 有一定的学习成本,因为它需要学习新的语法和工具。 某些情况下有性能问题:如果 css 很多的话,css-in-js 可能会有性能问题,因为它可能会增加 JavaScript 代码的大小。 kuma 是什么,它是如何解决传统 css-in-js 的问题的?kuma 的核心卖点是零运行时的 css-in-js 技术(zero-runtime CSS-in-JS)。同时也利用了运行时 CSS-in-JS 的表达能力。这两种技术的结合可以提供强大的样式能力,同时也保持了良好的性能。 “Zero-runtime CSS-in-JS”是一种在构建时生成 CSS 的技术,而不是在运行时。这意味着所有的 CSS 都在 JavaScript 代码执行之前就已经被生成和插入到页面中,这可以减少运行时的性能开销。 kuma 使用 Babel 插件或 Webpack 加载器来实现。babel 插件可以将 css-in-js 的代码转换成 css 代码。这样就可以在构建时生成 css 代码。也就是说你写的是 css-in-js 的代码,但是构建后生成的是 css 文件。这样可以同时获得 css-in-js 的优点,又避免了 css-in-js 的缺点。 但是核心的点在于并不是所有的 css 都可以在静态地生成,有些 css 是需要在运行时生成的。这如何处理呢? kuma 的解决方案是静态提取可以在构建时确定的样式,并对可能动态更改的样式执行静态“脏检查”,并在运行时注入它们。 而这一切对开发者来说是透明的,你只需要写 css-in-js 的代码,然后构建后就会生成 css 文件。 kuma 的核心原理假设我们写了如下的 css-in-js 代码: 123456789101112131415function App() { return ( <Heading as=\"h3\" className={css` color: red; @media (max-width: sm) { color: blue; } `} > Kuma UI </Heading> );} 经过 kuma 的处理,会变成如下的 css 代码: 12345678._1 { color: red;}@media (max-width: sm) { ._1 { color: blue; }} 和如下的 jsx 代码: 123function App() { return <Heading as=\"h3\" className=\"_1\">Kuma UI</Heading>;} lucifer 提示:如果让你自己实现这个转化,你会吗? 了解到 kumaui 做了什么之后,我们接下来看它是如何完成这样的转化的。 kumaui 的 css 方法本质上是接受一个模板字符串,然后将其转化成 css 代码。这个过程是通过 Babel 插件来完成的。 比如核心代码 kuma-ui/packages/babel-plugin/src/transform.ts 1234567891011121314151617181920212223import { transformSync } from \"@babel/core\";import { compile } from \"@kuma-ui/compiler\";import pluin from \".\";import { sheet } from \"@kuma-ui/sheet\";export function transform(code: string, id: string) { const result = transformSync(code, { filename: id, sourceMaps: true, plugins: [pluin], }); if (!result || !result.code) return; const bindings = ( result.metadata as unknown as { bindings: Record<string, string> } ).bindings; const compiled = compile(result.code, id, bindings); result.code = compiled.code; (result.metadata as unknown as { css: string }).css = sheet.getCSS() + compiled.css; sheet.reset(); return result;} 如上代码就是就是一个 babel 插件,核心逻辑写在了 compile 函数里面。compile 函数接受一个 css-in-js 的代码,然后将其转化成 css 代码。 kuma-ui/packages/compiler/src/compile.ts 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667import { Project, Node, SyntaxKind, JsxOpeningElement, JsxSelfClosingElement,} from \"ts-morph\";import { collectPropsFromJsx } from \"./collector\";import { extractProps } from \"./extractor\";import { componentList } from \"@kuma-ui/core/components/componentList\";import { optimize } from \"./optimizer/optimize\";import { processTaggedTemplateExpression } from \"./processTaggedTemplateExpression\";const project = new Project({});const compile = ( code: string, id: string, bindings: Record<string, string>,) => { const css: string[] = []; const source = project.createSourceFile(id, code, { overwrite: true }); source.forEachDescendant((node) => { if ( node.getKind() === SyntaxKind.JsxElement || node.getKind() === SyntaxKind.JsxSelfClosingElement ) { let openingElement: JsxOpeningElement | JsxSelfClosingElement; if (node.getKind() === SyntaxKind.JsxElement) { const jsxElement = node.asKindOrThrow(SyntaxKind.JsxElement); openingElement = jsxElement.getOpeningElement(); } else { openingElement = node.asKindOrThrow(SyntaxKind.JsxSelfClosingElement); } const jsxTagName = openingElement.getTagNameNode().getText(); // Check if the current JSX element is a Kuma component const originalComponentName = Object.keys(bindings).find( (key) => bindings[key] === jsxTagName && Object.values(componentList).some((c) => c === key), ); if (!originalComponentName) return; const componentName = originalComponentName as (typeof componentList)[keyof typeof componentList]; const extractedPropsMap = collectPropsFromJsx(openingElement); const result = extractProps( componentName, openingElement, extractedPropsMap, ); if (result) css.push(result.css); optimize( componentName, openingElement, extractedPropsMap[\"as\"] as string | undefined, ); } if (Node.isTaggedTemplateExpression(node)) { processTaggedTemplateExpression(node, bindings); } }); return { code: source.getFullText(), id, css: css.join(\" \") };};export { compile }; 可以看出遇到 kuma 组件就会调用 extractProps 函数,然后将其中的 css-in-js 代码转化成 css 代码。 上面的代码忽略细节后其实非常简单,不做过多解释。 而关于大家比较关心的 dirty check,其实和 vue 的 block tree 有点类似,对于没有 JS表达式的节点,可以完整提取出 css(就像我们前面举的例子一样)。而对于动态的比如如下代码: 12345678910111213function App() { const useRed = localStorage.getItem(\"useRed\"); return ( <Heading as=\"h3\" className={css` color: ${useRed ? \"red\" : \"blue\"}; `} > Kuma UI </Heading> );} 由于无法在构建时确定 color 是 red 和 blue,那就还是需要在运行时注入它们。(这个只是为了方便大家理解举的例子,并不意味着 kuma 是对这种非常简单的动态处理也没有做“静态化”处理)。 幸运的是,静态的应用场景还是比较多的,因此总体上 kuma 还是比较快的。 关于更深入的 dirty check 原理内容完全可以开一期文章来讲,这里就不展开了。 总结kuma 是一个 css-in-js 的解决方案,它利用了”zero-runtime CSS-in-JS”技术,同时也利用了运行时 CSS-in-JS 的表达能力。这两种技术的结合可以提供强大的样式能力,同时也保持了良好的性能。 kuma 的核心原理是通过 Babel 插件将 css-in-js 的代码转化成 css 代码。这样可以在构建时生成 css 代码。也就是说你写的是 css-in-js 的代码,但是构建后生成的是 css 文件。这样可以同时获得 css-in-js 的优点,又避免了 css-in-js 的缺点。 kuma ui 仓库地址:https://github.com/kuma-ui/kuma-ui kuma ui 官网:https://www.kuma-ui.com/docs","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"css-in-js","slug":"前端/css-in-js","permalink":"https://lucifer.ren/blog/categories/前端/css-in-js/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"css-in-js","slug":"css-in-js","permalink":"https://lucifer.ren/blog/tags/css-in-js/"}]},{"title":"关于 Error Boundaries, 你需要知道的一切","slug":"error-boundaries","date":"2024-01-27T16:00:00.000Z","updated":"2024-01-28T08:36:22.853Z","comments":true,"path":"2024/01/28/error-boundaries/","link":"","permalink":"https://lucifer.ren/blog/2024/01/28/error-boundaries/","excerpt":"在我们的应用中,难免会遇到一些异常情况,比如网络请求失败,或者是用户输入了一些非法的数据等等。这些异常情况如果没有得到处理,就会导致应用崩溃,从而影响用户体验。而 Error Boundaries 就是用可以处理这些异常情况中的一部分。","text":"在我们的应用中,难免会遇到一些异常情况,比如网络请求失败,或者是用户输入了一些非法的数据等等。这些异常情况如果没有得到处理,就会导致应用崩溃,从而影响用户体验。而 Error Boundaries 就是用可以处理这些异常情况中的一部分。 什么是 Error BoundariesError Boundaries 是 React 16 引入的一个新特性,用于捕获子组件树在渲染过程中抛出的异常,从而避免整个组件树的崩溃。 使用方法很简单,只需要定义一个组件,实现 componentDidCatch 和 render 方法即可。 一般而言,我们会在 getDerivedStateFromError 中将状态设置为 true, 然后 render 中根据这个设置的状态来渲染不同内容。如下代码所示: 1234567891011121314151617181920212223242526272829class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } componentDidCatch(error, info) { // Example \"componentStack\": // in ComponentThatThrows (created by App) // in ErrorBoundary (created by App) // in div (created by App) // in App logErrorToMyService(error, info.componentStack); } render() { if (this.state.hasError) { // You can render any custom fallback UI return this.props.fallback; } return this.props.children; }} Error Boundaries 的好处与限制Error Boundaries 的好处是显而易见的,它可以避免一个组件错误导致整个页面无法正常渲染的问题。 尽管 Error Boundaries 可以捕获子组件树中的异常,从而避免一个组件错误导致整个页面无法正常渲染的问题。但是它也有一些限制: 只能捕获子组件树中的异常,不能捕获自身组件树中的异常。 不能捕获事件处理器中的异常。 不能捕获异步渲染中的异常。 不能捕获服务端渲染中的异常。 不能捕获自身组件的构造函数中的异常。 对于这些问题,我们必须结合其他的异常处理机制来解决,本文不再赘述。 Error Boundaries 的底层实现原理Error Boundaries 的底层实现原理是什么呢? 遇到错误的时候,React 首先做的是捕获到这个错误,接下来将这个错误交给 componentDidCatch 来处理。类似我们在 JavaScript 中使用 try/catch 来捕获异常,然后在 catch 中处理异常。 我们可以通过源码来一探究竟。 1234567891011121314151617181920function handleError(root, errorInfo) { // Call componentDidCatch or handleError on the boundaries below us let boundary = root; while (boundary) { const inst = getInst(boundary); if (inst !== null && typeof inst.componentDidCatch === 'function') { invokeGuardedCallbackAndCatchFirstError( 'componentDidCatch', inst, errorInfo ); return; } else if (typeof boundary.tag !== 'number') { // Host components don't have lifecycle methods // so we don't need to try to invoke them return; } boundary = boundary.return; }} 可以看到,当一个组件抛出异常时,接下来 React 会从当前组件开始,向上遍历整个组件树,直到找到一个 Error Boundaries 组件,然后调用它的 componentDidCatch 方法。 Error Boundaries 的异常恢复我们期望在用户遇到错误的时候, 可以通过一定的方式来恢复应用的正常运行。比如,我们可以在 componentDidCatch 中调用 setState 来更新组件的状态,从而重新渲染组件。 由于 Error Boundaries 本质上是一个普通高阶 React 组件,因此我们可以通过重新给 children 设置 key 来触发组件的重新渲染。类似这样: 1234567891011121314151617181920212223242526272829303132333435363738394041class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { error: null, errorInfo: null }; } componentDidCatch(error, errorInfo) { // Catch errors in any components below and re-render with error message this.setState({ error: error, errorInfo: errorInfo }); // You can also log error messages to an error reporting service here } retry() { this.setState({ error: null, errorInfo: null, key: Math.random() }); } render() { if (this.state.errorInfo) { // Error path return ( <div> <h2 onClick={this.retry}>Something went wrong. Click to recover</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {this.state.error && this.state.error.toString()} <br /> {this.state.errorInfo.componentStack} </details> </div> ); } // Normally, just render children return <React.Fragment key={this.state.key}>{this.props.children}</React.Fragment> }} 组件的数据来源主要有 state 和 props。 另外我们也会用一些 store 中的数据,甚至有的人使用全局变量。 对于 state 和 props,甚至是 store,我们都可以很容易地使用上次没有报错的数据来重新渲染组件。不过对于全局变量,我们就无能为力了。因此我们尽量不要在组件中使用全局变量。这也是我们使用数据管理框架的原因。因为这样可以使得数据是可预见的。 Error Boundaries 的上报当我们遇到错误的时候,我们可以将错误信息上报到服务器,这样我们就可以知道用户遇到了什么问题,从而可以及时修复。 除了业务数据外,我们还可以将: 组件树的结构上报到服务器,这样我们就可以知道用户遇到问题的时候,页面的结构是怎样的,从而可以更好地定位问题。 组件的状态和属性传到后端,包括 hooks 数据等。 页面上可以展示出一个 hash,然后将 hash 上报到后端。 当用户反馈问题到我们的时候,我们可以通过这个 hash 来找到这个问题,并定位问题原因。 如何处理错误边界我们可以将 Error Boundaries 用于整个应用,也可以将 Error Boundaries 用于某个组件。如何决定呢? 一个原则是将相同业务功能的放到一起,用一个 Error Boundaries 来处理。这样一个功能挂了,相关功能也应该不可用。而不相关功能则期望是不受影响。这需要大家根据自己的业务来决定。 如何测试我们的应用的 Error Boundaries 是否合理?我建议手动将每一个组件都抛出异常,然后看看 Error Boundaries: 是否可以捕获到异常 是否可以恢复应用的正常运行 一个组件挂了后是否符合上一节我们提到的原则 如果觉得效率低,也可以写一个脚本来自动化测试。 总结本文介绍了 Error Boundaries 的基本用法,以及它的好处与限制。同时我们也介绍了 Error Boundaries 的底层实现原理,以及如何在遇到错误的时候,恢复应用的正常运行。最后我们还介绍了如何将错误信息上报到服务器。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"异常处理","slug":"异常处理","permalink":"https://lucifer.ren/blog/tags/异常处理/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第 12 期)","slug":"91algo-12","date":"2023-11-01T16:00:00.000Z","updated":"2023-11-02T05:39:32.733Z","comments":true,"path":"2023/11/02/91algo-12/","link":"","permalink":"https://lucifer.ren/blog/2023/11/02/91algo-12/","excerpt":"第十二期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"第十二期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第 12 期的正式开启的时间为 2023-11-16。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第十二期和往期的不同。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 目前正在和力扣官方合作, 讲义部分内容也在做相应调整。 预计之后出版后大家可以直接使用力扣会员免费无限查看讲义。 由于后期会发布到力扣官方,因此质量上还在进一步打磨。这对于大家来说是一个好消息。 等到我的讲义出版到力扣,力扣会员除了可以免费查看外。力扣会员还有很多其他用处。比如公司题库,leetbook等。 力扣免费题目已经有了很多经典的了,也覆盖了所有的题型,只是很多公司的真题都是锁定的。个人觉得如果你准备找工作的时候,可以买一个会员。另外会员很多leetbook 也可以看,结合学习计划,效率还是蛮高的。如果你要买力扣会员的话,这里有我的专属力扣折扣:https://leetcode.cn/premium/?promoChannel=lucifer (年度会员多送两个月会员,季度会员多送两周会员) 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2023-11-16 至 2024-02-14 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 新的一期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行。 参与方式:发 30 元红包给 lucifer 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 进群填写一个表单(表单地址在群公告),接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 另外也提供了免费参与的方式。 购书免费参与如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q: 题目每一期都是一样的么? A: 大部分是一样的,少部分会更新。比如第九期我们就更换了 5 道打卡题目。而 5 道占比全部打卡题目(91道)还是不算多的。除了打卡题目, 我们也有作业题目,第九期同样我们增加了几道作业题目。后续我们也会按照类似的节奏进行。 Q:第十二期和上一期内容一样吗? A:参考文章开头的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。另外大家可以使用前面介绍的技巧,使用 chatgpt 辅助,这样即使只有一门编程语言基础也完全可以应对。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"如何自己实现一个自动化框架?","slug":"how-to-make-automator","date":"2023-08-26T16:00:00.000Z","updated":"2023-08-29T03:56:58.860Z","comments":true,"path":"2023/08/27/how-to-make-automator/","link":"","permalink":"https://lucifer.ren/blog/2023/08/27/how-to-make-automator/","excerpt":"大家眼里的自动化框架一般都和测试进行绑定,这也可以理解, 毕竟自动化的目的就在于模拟用户行为,确认是否正常工作,代替传统的手工测试。 但实际上自动化和测试是两个问题,我们完全可以单独实现。 比如我可以将自动化框架 A 和 测试框架 B 结合起来使用。也可以将自动化框架 A 和 测试框架 C 一起使用。二者本应该是独立的。 因此本文将只聚焦自动化部分。如果需要扩展自动化测试功能,那么只需要集成一个测试框架进行简单接入就好了,不算复杂。","text":"大家眼里的自动化框架一般都和测试进行绑定,这也可以理解, 毕竟自动化的目的就在于模拟用户行为,确认是否正常工作,代替传统的手工测试。 但实际上自动化和测试是两个问题,我们完全可以单独实现。 比如我可以将自动化框架 A 和 测试框架 B 结合起来使用。也可以将自动化框架 A 和 测试框架 C 一起使用。二者本应该是独立的。 因此本文将只聚焦自动化部分。如果需要扩展自动化测试功能,那么只需要集成一个测试框架进行简单接入就好了,不算复杂。 什么是自动化框架? 用户角度 从用户角度上讲,自动化框架是一个能够自动操作软件的框架,其提供了一套用于编脚本的语言,用户使用这套语言就可以模拟各种各样使用软件的行为。 输入输出角度 从输入输出的角度上讲,自动化框架就是给定一段测试脚本,能够根据其脚本内容执行对应行为的系统。也就是说,输入是一段脚本,输出可能 IO 操作,返回一段特定的文本等等。 自动化框架需要支持什么功能?从前面的解释很容易看出,自动化框架核心就是模拟用户行为。靠什么模拟?靠脚本代码。 模拟用户操作比如我们想模拟用户打开浏览器,在地址栏输入 https://lucifer.ren/blog 我们可以将上面的步骤具体定义为几步: 打开浏览器 输入 https://lucifer.ren/blog 这就是一个用户操作序列。 如果我们想要实现自动化框架,如果支持类似这样的用户操作序列? 主流的做法是面向对象编程, 先抽象几个类。比如 Browser, Page, Element 等等,然后给每个类定义细粒度的 API。通过 API 的组合完成上面的功能。 比如的自动化框架名字叫 my-automator,我们就可以实现如下几个 API。 一段示意测试脚本,这里我们的脚本采用 JS 语言编写(下同,不再赘述)。 123456const automator = require(\"my-automator\");const browser = automator.launch(options);browser.open({ url: \"https://lucifer.ren/blog\",}); browser 就是 Browser 类的一个实例,我们定义了一个 open 方法。 类似的,如果我们模拟这样的一个操作序列。 打开浏览器 输入 https://lucifer.ren/blog 找到第一篇文章 点击阅读全文 找到页面下方的留言框 输入留言内容:赞! 这里有一些前置知识。 这些知识需要自动化框架的使用者知晓,而自动化框架的开发者无需知晓。 这里的页面有多篇文章,每一篇文章都有一个阅读全文按钮,按钮用一个 div 实现,div 上有一个名为 readmore 的 class,其下有一个 a 标签,点击 a 标签会跳到对应文章。 文章详情页面的评论框是一个 textarea,有一个名为 gt-header-textarea 的 class。 我们可以这样设计我们的 API。 1234567891011const automator = require(\"my-automator\");const browser = automator.launch(options);const page = browser.open({ url: \"https://lucifer.ren/blog\",});const element = page.select(\".readmore a\"); // 类似于 document.querySelector 功能element.click();await browser.waitForNavigation(); // 等待页面跳转完成const textarea = browser.currentPage.select(\".gt-header-textarea\");textarea.input(\"赞\"); 也就是说,如果我们实现了这几个 API,就可以自动化测试上面的这一套用户操作序列了。 那么理论上我们只需要不断完成 API,就可以造一个完整的自动化框架了。文章后面会讲如果实现这些 API 的整体思路。 当然如何设计 API 不是本文重点, 大家完全可以采用自己认可的方式进行。大家只需要理解,我们是按照这种面向对象的方式,根据常见的用户操作进行细粒度拆分 API 即可。 返回特定的信息除了模拟用户操作,我们还需要能够返回特定的信息。 比如当前页面是否在 loading, 当前 button 上显示的文字是什么。 这个有两个用途: 方便根据不同情况写代码。还是以前面的跳转到最新文章下留言为例,我想看下当前第一篇文章是人为置顶的文章,还是按照时间排它是最新的。(假设我们会给置顶文章加一个特殊 class),那么就需要有一个获取元素 class 的 API。 方便集成测试框架。还是以前面的跳转到最新文章下留言为例,我想知道这个过程有没有成功。那么可以根据 selectAll 方法找到所有留言,然后根据留言内容判断是否刚才成功留言了。那么就需要获取元素内容。这也是一种信息。 有了这两个功能,大部分常用的自动化框架的内容都可以实现。 如果实现一个自动化框架首先不管是 web 的自动化, 还是小程序的自动化,甚至是手机的自动化,我们都需要明确一点。那就是:不管是 web 应用也好,小程序应用也好,手机应用也好。他们都是通过某种语言编写的,并且这些语言都可以满足我们前面提到的第二条”返回特定的信息“。 但是它不是根据自动化脚本自动执行,而是需要用户驱动。 不过这个很好办。我们只需设计一个通信机制。 测试脚本通过这套通信机制通知应用需要执行何种操作。 然后应用根据脚本发送的信息,执行操作,然后返回结果信息。 也就是说我们需要: 设计一套测试脚本和应用的通信手段。 实现一套应用代码,这段代码监听发送的消息,并将消息转化为页面的操作,然后返回结果信息。 对于第一个问题为了足够通用,通信我们选择 websocket,主流自动化框架都支持,尤其是 web 自动化框架。第二个问题就更简单了,只要你掌握应用编写的技巧,那么这套植入到应用中的代码并不难写。 整体架构 如图,测试脚本通过 websocket 消息和应用通信,应用收到消息做对应动作,然后返回对应信息给测试脚本。 其中 handle 是对结果进行处理。 比如把不支持序列化的属性去掉或者转化为可序列化的结构。 功能实现消息的结果,我们可以参考 CDP。CDP 使用 method 来标识不同类型的参数。其中 method 的命名采用固定格式: 1[domain_name].[method_name] 比如你想调用 document.querySelector,就可以发送一个 websocket 消息给 chrome,其中 method 字段固定为 “DOM.querySelector”,参数为 nodeId 和 selector,返回值是选中结果 DOM 的 nodeId。 基于 CDP,你可以自己定制一个远程 devtool 工具,而不必使用 chrome 内置的 devtool。其实 chrome 内置的 devtool 也是基于 CDP 做的。 我们也可以采用这种格式来实现,下面代码是以 web 自动化框架为例,其他框架同理。 测试脚本端我们需要分别实现 Browser,Page 和 Element 类。 测试脚本端主要就是发送消息, 然后等待应用端返回对应的结果消息,最后回传给用户即可。 Browser: 123456class Browser { constructor() {} open({ url }) { // open page in browser }} Page: 12345678910111213141516171819202122232425262728class Page { constructor() {} select({ selector }) { return new Promise((resolve, reject) => { const id = uuid() ws.send({ id method: 'PAGE.select', // PAGE 是 domain, select 是 method query: { selector } }) // 等待应用端消息 ws.addEventListener(\"message\", (event) => { const data = event.data if (data.id === id) { resolve(data.result) // 这里其实可以移除监听了 } // ... }); }) } selectAll({ selector }) { // ... } // ...} Element 1234567891011121314151617181920212223242526272829class Element { constructor(id) { this.id = id } click() { return new Promise((resolve, reject) => { const id = uuid() ws.send({ id method: 'ELEMENT.click', // ELEMENT 是 domain, click 是 method query: { id: this.id } }) // 等待应用端消息 ws.addEventListener(\"message\", (event) => { const data = event.data if (data.id === id) { resolve(data.result) // 这里其实可以移除监听了 } // ... }); }) } attribute(name) { // ... }} 可以看出测试脚本端代码都是类似的。 应用端我们需要植入一段代码到应用端。这套代码如果你了解应用代码如何编写, 也不会难。 总之,应用端就是监听消息,做动作,然后通过 websocket 将结果回传即可。 123456789101112131415161718192021222324252627282930const domMap = new Map();function getElementById(id) { return domMap.get(id);}function setElementById(id, ele) { return domMap.set(id, ele);}ws.addEventListener(\"message\", (event) => { const data = event.data; if (data.method === \"select\") { const ele = document.querySelector(data.query.selector); const id = uuid(); setElementById(id, ele); ws.send({ id: data.id, result: { id, ...handle(ele), }, }); } if (data.method === \"click\") { const element = getElementById(data.query.id); ws.send({ id: data.id, result: handle(element.click()), }); } // ...}); 总结不管是 web 端自动化框架,还是小程序端自动化框架,甚至是手机端,电脑端自动化框架,我们都可以使用这个思路来完成。 即: 注入一段代码到应用端。 定义一套脚本语言,脚本发送消息到应用端。 应用响应消息后做动作,最后返回结果信息给脚本端。 使用面向对象编程方法,结合 CDP 的定义格式可以帮助我们写出更清晰易懂的代码。 同时我们的自动化框架也可以轻松集成任意的测试框架,实现自动化测试的目的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"自动化","slug":"自动化","permalink":"https://lucifer.ren/blog/categories/自动化/"},{"name":"devtool","slug":"devtool","permalink":"https://lucifer.ren/blog/categories/devtool/"}],"tags":[{"name":"前端,自动化,automator","slug":"前端,自动化,automator","permalink":"https://lucifer.ren/blog/tags/前端,自动化,automator/"},{"name":"chrome","slug":"chrome","permalink":"https://lucifer.ren/blog/tags/chrome/"}]},{"title":"如何移除项目中未使用的 CSS","slug":"remove-useless-css","date":"2023-08-07T16:00:00.000Z","updated":"2023-08-08T09:51:56.146Z","comments":true,"path":"2023/08/08/remove-useless-css/","link":"","permalink":"https://lucifer.ren/blog/2023/08/08/remove-useless-css/","excerpt":"使用 chrome 的 devtool 可以查看项目中未被使用的 JS 和 CSS。具体可以参考下 chrome 官方的博客: Coverage: Find unused JavaScript and CSS 有没有方法可以自己检测呢?甚至是做成工具集成到 CI/CD 甚至 code review 中呢? 看完本文,你能学到如何自己手撸一个这样的工具。同时也会推荐社区里经过验证的好用的同类型库。","text":"使用 chrome 的 devtool 可以查看项目中未被使用的 JS 和 CSS。具体可以参考下 chrome 官方的博客: Coverage: Find unused JavaScript and CSS 有没有方法可以自己检测呢?甚至是做成工具集成到 CI/CD 甚至 code review 中呢? 看完本文,你能学到如何自己手撸一个这样的工具。同时也会推荐社区里经过验证的好用的同类型库。 前置知识 postcss jsdom 正文推荐两个库,其中第一个是我使用过的。另外一个研究了一下,和第一个大同小异,下面我会具体分析,大家看完后可以根据自己的情况进行选择。 uncss首先第一个工具:uncss 一个最简洁的用法(全部使用默认配置): 1234567var uncss = require('uncss');var files = ['my', 'array', 'of', 'HTML', 'files', 'or', 'http://urls.com'],uncss(files, function (error, output) { console.log(output);}); 它会在内存中使用 jsdom 打开你的 HTML 文件, 然后像上面提到的 chrome devtools 的 coverage 功能一样分析你的哪些 CSS 选择器是没有被使用到的。 其配置项也非常丰富,具体可以参考官方文档 它的原理非常简单,第一个核心部分是 process 函数。 省略非核心代码 12345678async function process(opts) { // getHTML 会调用 jsdom.fromSource ,生成 page 对象。由于可以输入多个文件,因此会返回 pages 数组 const pages = await getHTML(opts.html, opts); // ... 省略非核心代码 // getStylesheets 会调用 jsdom.getStylesheets 得到样式文件 return getStylesheets(opts.files, opts, pages);} 经过上面的预处理后会走到第二个关键部分是 uncss 函数。 123456789101112131415161718192021222324/** * Main exposed function * @param {Array} pages List of jsdom pages * @param {Object} css The postcss.Root node * @param {Array} ignore List of selectors to be ignored * @return {Promise} */module.exports = async function uncss(pages, css, ignore) { const nestedUsedSelectors = await Promise.all( pages.map((page) => getUsedSelectors(page, css)) ); const usedSelectors = _.flatten(nestedUsedSelectors); const filteredCss = filterUnusedRules(css, ignore, usedSelectors); const allSelectors = getAllSelectors(css); return [ filteredCss, { /* Get the selectors for the report */ all: allSelectors, unused: _.difference(allSelectors, usedSelectors), used: usedSelectors, }, ];}; 核心就是 getUsedSelectors(pages, css) ,根据你提供的 HTML 和 CSS,找到被使用的选择器, 全部的选择器减去被使用的选择器自然就是没有被使用到的选择器。 1234567891011121314/** * Find which selectors are used in {pages} * @param {Array} page List of jsdom pages * @param {Object} css The postcss.Root node * @return {Promise} */function getUsedSelectors(page, css) { let usedSelectors = []; css.walkRules((rule) => { usedSelectors = _.concat(usedSelectors, rule.selectors.map(dePseudify)); }); return jsdom.findAll(page.window, usedSelectors);} getUsedSelectors 则是使用 postcss, 对 CSS 进行 AST 转化后,调用 dePseudify 进行处理。 dePseudify 基本上就是直接调用了 postcss-selector-parser。不熟悉 postcss-selector-parser 的话也没关系,看下它官方提供的 demo 就懂了。 123456789const parser = require(\"postcss-selector-parser\");const transform = (selectors) => { selectors.walk((selector) => { // do something with the selector console.log(String(selector)); });};const transformed = parser(transform).processSync(\"h1, h2, h3\"); 可以看出, 就是给定一个 css 文本,它会逐个输出 css 中的选择器。 简单总结一下它的原理,使用 jsdom 读取 html 和 css。jsdom 可以得到已经被使用的选择器, postcss 以及插件会得到 css 中的全部选择器。两者相减,就是没有被使用的 css。 purifycss相比上一个库,这个就非常小巧了。提供的配置也比较少,官方文档 12345678import purify from \"purify-css\";let content = \"\";let css = \"\";let options = { output: \"filepath/output.css\",};purify(content, css, options); 核心代码就是 purify 函数。 123456789101112131415const purify = (searchThrough, css, options, callback) => { // ... 省略非核心代码 let cssString = FileUtil.filesToSource(css, \"css\"), content = FileUtil.filesToSource(searchThrough, \"content\"); let wordsInContent = getAllWordsInContent(content), selectorFilter = new SelectorFilter(wordsInContent, options.whitelist), tree = new CssTreeWalker(cssString, [selectorFilter]); tree.beginReading(); let source = tree.toString(); fs.writeFile(options.output, source, (err) => { if (err) return err; });}; getAllWordsInContent() 是获取全部的 css 选择器。 tree.beginReading() 是为了获取已经被引用的 css 选择器。 两者相减就是未被使用的选择器。 其中 tree.beginReading 则是调用了另外一个库 rework 实现的。rework(this.startingSource).use(this.readPlugin.bind(this)); 本质上也是借助于 rework 提供的插件系统,自己实现了一个插件 selectorFilter 来找到被使用的选择器。而 selectorFilter 的核心就是 filterSelectors 这样一个函数: 12345678910111213141516171819function filterSelectors(selectors) { let contentWords = this.contentWords, rejectedSelectors = this.rejectedSelectors, usedSelectors = [] selectors.forEach(selector => { let words = getAllWordsInSelector(selector), usedWords = words.filter(word => contentWords[word]) if (usedWords.length === words.length) { usedSelectors.push(selector) } else { rejectedSelectors.push(selector) } }) return usedSelectors} 如果你没听过 rework 也没关系, 你可以将 rework 看成是 postcss 去理解,问题不大。 总结相信读完本文,你已经明白如何自己实现一个这样的工具,只不过还有需要考虑就是了。 如果你是想在开发阶段大概看一下未被使用的代码,推荐 chrome 的 devtool 工具 coverage。 如果你想使用现成工具,个人更推荐使用 uncss,因为其实基于 jsdom 的, 实现上更接近 chrome 的 coverage 功能,支持的功能也更多。你甚至可以基于它实现 e2e 测试后,出具一份未被使用的选择器名单。 而 purifycss 则简单许多,但是原理是相似的,都是将 css 进行 ast 化,然后使用插件分析规则作为被使用的选择器,然后 html 中引用的选择器作为全部的选择器,两者相减得出没有被使用的选择器。","categories":[],"tags":[{"name":"Chrome","slug":"Chrome","permalink":"https://lucifer.ren/blog/tags/Chrome/"},{"name":"CSS","slug":"CSS","permalink":"https://lucifer.ren/blog/tags/CSS/"},{"name":"AST","slug":"AST","permalink":"https://lucifer.ren/blog/tags/AST/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第 11 期)","slug":"91algo-11","date":"2023-06-03T16:00:00.000Z","updated":"2023-11-02T05:17:52.233Z","comments":true,"path":"2023/06/04/91algo-11/","link":"","permalink":"https://lucifer.ren/blog/2023/06/04/91algo-11/","excerpt":"第十一期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"第十一期,感谢大家一路的陪伴,我们会不辜负大家的信任,努力做的更好! 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第 11 期的正式开启的时间为 2023-06-10。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第十一期和往期的不同。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 其实之前也是有作业的, 只不过并不会对每一个小节都留作业,因此后续我们会继续完善作业。 上一期开始,我们正式在活动结束后发放 HTML 版本的电子书, 效果很好。这一期也会同样发放 html 版本, 并解决一些 bug。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 我们的题解大部分都是提供了多种语言实现的。而目前 chatgpt 发展势头很猛,我们大家可以使用 chatgpt 来帮助自己翻译代码。比如我们的题解只提供了 python, 而你是 Go 语言从业者, 就可以使用 chatgpt 将其翻译为 Go。根据我的使用经验, 翻译就直接丢给它就行,准确率很高(偶尔出错,不过是小错误, 跑一下就发现了)。关于如何使用 chatgpt,可以参与活动后查看活动主页的 FAQ。 另外也可以使用 chatgpt 给自己讲解代码。 比如有几行代码,不明白在做什么,就可以直接让 chatgpt 可以解释。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2023-06-10 至 2023-09-08 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第十一期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 如果进群后发现自己是 50 名之后 100 名之前需要在 2023/02/02前补交10元,否则会被清退,只能原价参与了。 大家可以通过进群后点击右上角的三个点,查看自己头像所在位置判断自己是第几名进群的,这是因为头像顺序就是入群顺序。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q: 题目每一期都是一样的么? A: 大部分是一样的,少部分会更新。比如第九期我们就更换了 5 道打卡题目。而 5 道占比全部打卡题目(91道)还是不算多的。除了打卡题目, 我们也有作业题目,第九期同样我们增加了几道作业题目。后续我们也会按照类似的节奏进行。 Q:第十一期和上一期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。具体不同可以参考文章前半部分的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。另外大家可以使用前面介绍的技巧,使用 chatgpt 辅助,这样即使只有一门编程语言基础也完全可以应对。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"记一次从 webpack 4 升级 webpack 5 + swc 的经历","slug":"migrate-to-webpack5_swc","date":"2023-03-26T16:00:00.000Z","updated":"2023-03-28T02:57:55.525Z","comments":true,"path":"2023/03/27/migrate-to-webpack5_swc/","link":"","permalink":"https://lucifer.ren/blog/2023/03/27/migrate-to-webpack5_swc/","excerpt":"最近将项目的 webpack 4 升级到了 webpack 5,用了一两天的时间,遇到了一些网上找不到资料的问题, 于是决定将过程记录一下。","text":"最近将项目的 webpack 4 升级到了 webpack 5,用了一两天的时间,遇到了一些网上找不到资料的问题, 于是决定将过程记录一下。 直接找到官方提供的迁移教程 To v5 from v4 进行操作。 升级 webpack将项目依赖的版本直接改为最新版本,注意将变更同步到 package.json 和 lock 文件。 逐个检查 loader 和 plugin 的 兼容性我用到了一个叫 virtual-module-webpack-plugin 的插件,根据 readme 的介绍,直接改为 webpack-virtual-modules 即可。 写法有一点小小的变动。 before: 123456plugins: [ new VirtualModulePlugin({ moduleName: \"src/mysettings.json\", contents: JSON.stringify({ greeting: \"Hello!\" }), }),]; after: 12345plugins: [ new VirtualModulesPlugin({ \"src/mysettings.json\": JSON.stringify({ greeting: \"Hello!\" }), }),]; 检查项目中是否使用到了 breakchange 功能使用官方提供的命令在项目下跑一下即可。 1node --trace-deprecation node_modules/webpack/bin/webpack.js 核心就是在项目下跑一下 webpack,webpack 会打印一些 deprecation 警告,通过上面的命令可以将其筛出来。 内置模块不再 polyfill 由于 webpack 5 内置不在 polyfill path,process 等,因此如果你的项目用到了,需要自己处理。处理方式也很简单。 对于 path: 1config.resolve.fallback.path = require.resolve(\"path-browserify\"); 更多请参考:how-to-polyfill-node-core-modules-in-webpack-5 个别 sourcemap 选项变更 比如 cheap-module-eval-sourcemap 改为了 eval-cheap-module-source-map 这个问题虽然网上我没找到解决方案,但是我问了 chatgpt 它告诉了怎么做(感谢chatgpt)。 如果你开发了一些插件和 loader, 那么要注意一些 api 变了。 比如 assets.compilation,再或者 compiler.hooks.invalid.call 这些变化虽然没有在官方文档体现,但是你的项目中如果使用 ts,那么其会直接报错,应该也不算太坑。 循环依赖webpack4 默认帮助我们处理循环依赖问题。 而webpack5则不会。 webpack5 会报错:webpack 5 cannot access '__webpack_default_export__' before initialization 因此如果你的项目中使用了循环依赖, 可以尝试改变写法。如果你不知道哪里有循环依赖,也可以使用 webpack 官方推荐的检测插件。 Circular Dependency Plugin - Detect modules with circular dependencies when bundling — Maintainer: Aaron Ackerman types 丢失webpack 4 的版本,如果直接使用 api 模式, 那么可以使用很多它导出的类型,webpack 5 中不再导出, 可以使用 patch-package 的方式修改包,也可以自己 declare webpack module 去扩展。 比如不再导出的类型有: MultiWatching LoaderContext Plugin … 升级到 swc-loader之前用的是 babel-loader,这次顺便升级一下 swc,速度据说比 babel-loader 快很多。实际使用我的项目要快 20%-40%。由于每个项目不一样,仅供参考。 升级 swc-loader 很简单。 before: 123456789101112module: { rules: [ { test: /\\.m?js$/, exclude: /(node_modules)/, use: { // `.babelrc` can be used to configure swc loader: \"babel-loader\", }, }, ];} after: 123456789101112module: { rules: [ { test: /\\.m?js$/, exclude: /(node_modules)/, use: { // `.swcrc` can be used to configure swc loader: \"swc-loader\", }, }, ];} 由于我项目中使用了 sourcemap,因此直接修改会报错。 1swc-loader 0: failed to read input source map from user-provided sourcemap 解决方式很简单,直接 swc-loader 传递 sourceMap: false 即可,这样swc-loader 就不处理 sourcemap 了。如果 为 true 的话,swc-loader 会将 sourcemap JSON.stringify 一下,从而出问题。 swc-loader 处理这部分的源码大概是: 1swc.transform(source, options).then(output => callback(null, output.code, parseMap ? JSON.parse(output.map) : output.map), err => callback(err)) 也就是说设置不解析 sourcemap 后就不会序列化了。 其他遇到的问题 node.setImmediate 不支持了,其实 node 上的东西都不支持了。比如 buffer, path 等等。 optimization.noEmitOnErrors 变为了 optimization.emitOnErrors,这个就是 true 和 false 换一下的事,不理解 webpack5 为啥要改这个。 compiler.outputFileSyste.write 签名好像变了 compilation.assets 的类型变了。之前 Source 类的 map 函数返回值可以是 null, 现在只能是 Object 类型 这几个解决方式网上都有,也很简单,不再赘述。 总结这次迁移使得项目性能提升 20% - 30%,总共花了一两天的时间,效果还不错。 另外也会之后升级 rspack 做了准备,因此 rspack 给的迁移例子都是 webpack 5 的, 也就是说如果 webpack 5 出问题更容易找到资料。 我自己试了一下, 目前强行迁移到 rspack 坑很多,并且网上几乎没资料可以参考,所以决定先等等等到社区相对成熟了再入坑。","categories":[],"tags":[{"name":"webpack","slug":"webpack","permalink":"https://lucifer.ren/blog/tags/webpack/"},{"name":"swc","slug":"swc","permalink":"https://lucifer.ren/blog/tags/swc/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第X期)","slug":"91algo-10","date":"2023-01-31T16:00:00.000Z","updated":"2023-06-03T11:19:57.208Z","comments":true,"path":"2023/02/01/91algo-10/","link":"","permalink":"https://lucifer.ren/blog/2023/02/01/91algo-10/","excerpt":"X 是罗马数字中的 X, 也就是数字 10,这里是第十期,一个对我很有意义的节点。 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"X 是罗马数字中的 X, 也就是数字 10,这里是第十期,一个对我很有意义的节点。 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第十期的正式开启的时间为 2023-02-14。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第十期和往期的不同。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 其实之前也是有作业的, 只不过并不会对每一个小节都留作业,因此后续我们会继续完善作业。 电子书可能会以 html 的形式给出,这样大家阅读体验会更好。不会出现什么代码无法滚动,导航不管用等等问题。html 版本虽然搜索功能很强,但是目前还有若干我不太满意的地方(我依赖的工具很久不更新了,有些地方要想实现可能需要多费功夫才行),会在第十期逐步优化,争取令大家获得更好的阅读体验。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2023-02-14 至 2023-05-15 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第十期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 如果进群后发现自己是 50 名之后 100 名之前需要在 2023/02/02前补交10元,否则会被清退,只能原价参与了。 大家可以通过进群后点击右上角的三个点,查看自己头像所在位置判断自己是第几名进群的,这是因为头像顺序就是入群顺序。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q: 题目每一期都是一样的么? A: 大部分是一样的,少部分会更新。比如第九期我们就更换了 5 道打卡题目。而 5 道占比全部打卡题目(91道)还是不算多的。除了打卡题目, 我们也有作业题目,第九期同样我们增加了几道作业题目。后续我们也会按照类似的节奏进行。 Q:第十期和上一期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。具体不同可以参考文章前半部分的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"一款显示题目对应周赛难度分的浏览器插件| TamperMonkey | Chrome | FireFox","slug":"leetcode-rating","date":"2023-01-18T16:00:00.000Z","updated":"2023-01-19T07:27:03.353Z","comments":true,"path":"2023/01/19/leetcode-rating/","link":"","permalink":"https://lucifer.ren/blog/2023/01/19/leetcode-rating/","excerpt":"","text":"leetcode rating website经常有同学反映力扣难度设置的不合理。 比如:“为什么我觉得这个中等题目比某个困难还要难啊?”。 这其实很正常,尤其是在力扣上。因此我也不建议大家完全按照难度去刷题, 而是结合通过率等其他指标去刷。 而今天介绍的这个网站能够将力扣中的题目通过各个维度算出一个更接近题目实际难度的隐藏分。这个分数和你的竞赛分数是一致的。大家可以根据你的竞赛分去刷题。 比如你的竞赛分是 1800, 那么你就可以直接搜索 1800 - 1900 的题目进行练习,效率高很多。 网站地址:https://zerotrac.github.io/leetcode_problem_rating/#/ leetcode rating extension有一位朋友基于上面网站作为数据源自己写了一个扩展,直接在力扣官网里显示对应题目分数(如果有分数的话)。 这是一款显示题目对应周赛难度分的浏览器插件| TamperMonkey | Chrome | FireFox 它是基于油猴的脚本, 大家需要先安装油猴扩展, 然后通过油猴去启动即可。 使用效果: 仓库地址:https://github.com/zhang-wangz/LeetCodeRating 总结如果你需要根据第几场周赛或者分数进行筛选,建议你直接使用网站即可。如果你平时都是在力扣刷题,比如每天做一下力扣的每日一题活动, 那么建议你安装浏览器扩展。另外扩展目前在不断完善中,后续可能增加更多功能,大家有可以去仓库提 issue 或者 pr。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"我的 2022 年总结","slug":"2022","date":"2023-01-17T16:00:00.000Z","updated":"2023-01-19T08:41:23.343Z","comments":true,"path":"2023/01/18/2022/","link":"","permalink":"https://lucifer.ren/blog/2023/01/18/2022/","excerpt":"从工作,自媒体,生活,技术,游戏,家人朋友等角度记录一下我的 2022。","text":"从工作,自媒体,生活,技术,游戏,家人朋友等角度记录一下我的 2022。 工作2022 投入工作的时间无疑是最多的。倒不是坐在电脑前敲代码时间久,即使我出去吃饭可能也会带着一些工作思考,这也算是一种隐性时间。 不过 2022 大多数公司都不好过,包括我们公司。因此激励也会比往年弱。这个时候更应该努力,思考如何打破现状,寻找突破点。 “更关注用户一点” 是我的工作中的年度词语。这一年更多地倾听用户的声音,帮助他们解决问题。这同时为了收获了一些好评和认可。虽然吐槽更多,但是这些认可就足以支撑我继续优化产品了。 希望 2023 能让大多数的用户都对我的产品满意,并有所期待! 自媒体不得不承认,2022 花在自媒体的时间比往年要少很多。Github,博客以及算法活动更新频率都较往前有所降低。 91 天学算法活动自媒体中投入精力最多的当然是我们的算法活动了。为了每一期都更有质量,我精心准备了很多作业,不断更新和打磨讲义,往讲义里塞入了很多常见套路总结,使得讲义硬度大大增加,这是我 2022 年投入在自媒体上最引以为豪的东西了。 Github 综合获星超过 10 kleetcode 项目贡献 80% 以上。 目前获星 ⭐️ 50.2 k 由于我几乎每天都会做题,其实也很容易,直接跟着力扣官方的每日一题做就行了,每天花不了几分钟时间。遇到好题目的话我也会更新题解,如果有必要也会更新到 Github 的 leetcode 仓库中。并定期将题解更新到我的刷题插件 leetcode cheatsheet。这不就在前没多久刚刚提交了一版本,更新了题库以及增加了差分数组模板。 博客博客更新不到 20 篇,其中还有一些是我的算法活动宣传。 有点愧疚,希望 2023 年可以多多更新干货。 视频视频已经全面停更了,包括 bilibili 在内的视频网站全面停止更新。 原因也很真实,文字我都不想更新,视频怎么会去更新呢? 😄 生活2022 年上半年还比较稳定,而到了今年年底疫情突然放开,我周围的人几乎都中招了,包括我。 老人和孩子可遭殃了,尤其是有基础病的老人,一点消息都没有的突然放开大家都没有准备,这是对那些弱势群体的极大不尊重。 理财年底冲了一波业绩,很多票都红了,目前账面盈利并不多。目前市场还是接近底部的位置,因此对这个结果还是满意的。 美元人民币汇率之前一度涨到 7.2+,现在已经回落到 6.7 了,这意味着相比于 7.2 时期有利于我们买买买海淘了。 另外现在不在公众号投放广告了,而是选择了对大家影响更小的 Github 上投放,再加上一些推广和我的理财收入,基本可以覆盖基本生活开支了。 技术这一年对技术的深入研究大多是源自于工作上的需求。通过订阅日报和周报来使自己不会太脱离大部队太多。通过每日一题练习自己对其他语言的掌握情况,比如 golang,python。这真的是一种不错的学习新语言的方法啊(这个方法我之前给大家推荐过) 😄 游戏巫师 3 重置版,魂系游戏(艾尔登,黑魂等),瘟疫传说安魂曲等等大作充实了我的业务生活。 一些小众游戏我也很喜欢,比如类恶魔城的游戏 《蒂德莉特的奇境冒险》。 目前在玩的游戏是 AI 梦境档案第二部,然后还预定了火焰纹章 engage,过年应该刚好能玩上。 另外非常期待今年的塞尔达王国之泪还有卧龙。 家人&朋友由于疫情影响,家人的身体状况有所影响,这该死的玩意。 今年和朋友们接触的也很少,并不仅仅是因为疫情,还有一个原因是居家办公时间久了出去的时间也少了(其实疫情某种程度加深了这个影响)。 希望 2023 家人身体健康,朋友开开心心,不那么忙碌,没事能多聚聚。 爱情没有太强的欲望想找另外一半。可能只是某些瞬间想有个人陪吧,但这其实和找对象不是一回事。 不强求的态度可能会越来越不想找对象,因此我还是要努力先寻找机会,完成一个终身大事。","categories":[{"name":"年终总结","slug":"年终总结","permalink":"https://lucifer.ren/blog/categories/年终总结/"}],"tags":[{"name":"年终总结","slug":"年终总结","permalink":"https://lucifer.ren/blog/tags/年终总结/"},{"name":"2022","slug":"2022","permalink":"https://lucifer.ren/blog/tags/2022/"}]},{"title":"力扣刷题的正确姿势是什么?","slug":"how-leetcode","date":"2023-01-01T16:00:00.000Z","updated":"2023-01-19T07:16:07.810Z","comments":true,"path":"2023/01/02/how-leetcode/","link":"","permalink":"https://lucifer.ren/blog/2023/01/02/how-leetcode/","excerpt":"本文原本是打算加到我的新书《算法通关之路》的附录部分。不过由于力扣官方不过审,因此只好作罢。将这部分内容发到这里给大家参考。 《算法通关之路》介绍以及购买可访问:https://leetcode-solution.cn/book-intro","text":"本文原本是打算加到我的新书《算法通关之路》的附录部分。不过由于力扣官方不过审,因此只好作罢。将这部分内容发到这里给大家参考。 《算法通关之路》介绍以及购买可访问:https://leetcode-solution.cn/book-intro 力扣(LeetCode )网站使用方法力扣(LeetCode )官网收录了许多互联网公司的算法题目,一度被称为刷题神器。这里我们就来介绍下如何使用 力扣(LeetCode )网站。 由于力扣(LeetCode)本身也处于不断迭代之后,因此本文部分内容有可能在将来会变得不再适用。 以力扣国际站为例,其官网给出了四个分类:Algorithms、Database、Shell 和 Concurrency,分别表示算法题、数据库题、Shell 和并发题,第一个就是我们所需要刷的算法题。 并发是 2019 年才添加的新的模块。 点开 Algorithms 后,我们可以看到一个题目的列表,每个题目都有一个唯一的序号。力扣(LeetCode )目前有 1000 多道题目,并且一直持续更新,其中有一些是带锁的,需要会员才能查看。 后面的接受率(Acceptance)表示提交的正确率,Difficulty 表示难易程度。难易程度有三个级别,分别是 Easy、Medium 和 Hard。 Easy 通常不需要太多思考和也不会有复杂的细节,比较特别适合新手或者拿来热身。 Medium 级别就会有些难度,一般都会涉及到经典的算法,需要一定的思考。 Hard 级别是最难的,有些时候是算法本身的难度,有些时候特别需要你考虑到各种细节。 这里分享一个小技巧给大家。衡量一道题目难不难除了看难度之外,还可以看下接受率,接受率越低代表题目越难,这个指标有时候比难度更靠谱。 你可以对题目进行筛选和排序。 如果我们只想要找某一类型的题或者某个公司的题库,可以通过 Tags 或 Company 来筛选。 另外我们在做某一题时,觉得还想再做一个类似的,可以点击题目描述下方 Show Similar Problems 或 Tags 来找到相似的问题。 每个题目都有各自的 Discuss 区域。在这里,许多人都把自己的思路和代码放到了上面,你可以发贴提问,也可以回复别人,里面大神很多,题解质量都很高,如果实在没有思路或者想看下有没有更好的思路可以来逛一下。通常来说我建议你优先看留言最多或者投票最多的。 点开某一个题目,会跳转到具体题目详情页面,你可以在右侧的代码区切换选择自己需要的编程语言。 代码编写完了之后,不要急着提交,先测试运行一下(Run Code 按钮)。你可以多写几个测试用力跑一下,没有问题再提交,要知道比赛的时候错误提交要加时间的。 提交通过之后,可以点开 More Details 查看详细运行结果信息。 每道题旁边的 My Submissions 可以找到自己的对于该题的提交情况,这里可以看到自己过去所有的提交,点 Accepted 或 Wrong Answer 就可以查看自己过去提交的代码情况,包括代码是什么,运行时间以及全部用户提交的时间分布图等。 以上就是 力扣(LeetCode )的主要功能,希望通过这一节内容能让你对 力扣(LeetCode )网站有所了解,从而更快地进行刷题。 刷题工具所谓“工欲善其事必先利其器”,接下来给大家带来一些提高刷题效率的小工具。 Leetcode Problem Rating经常有同学反映力扣难度设置的不合理。 比如:“为什么我觉得这个中等题目比某个困难还要难啊?”。 这其实很正常,尤其是在力扣上。因此我也不建议大家完全按照难度去刷题, 而是结合通过率等其他指标去刷。 而今天介绍的这个网站能够将力扣中的题目通过各个维度算出一个更接近题目实际难度的隐藏分。这个分数和你的竞赛分数是一致的。大家可以根据你的竞赛分去刷题。 比如你的竞赛分是 1800, 那么你就可以直接搜索 1800 - 1900 的题目进行练习,效率高很多。 网站地址:https://zerotrac.github.io/leetcode_problem_rating/#/ 力扣代码调试器 力扣代码调试器是一款简单易用的代码调试工具,它能够随时检查代码的运行情况以及选择性地运行代码,以便您排错、调试。 力扣代码调试器支持以下特色功能: 支持无限制添加断点,单步运行调试 一键监听表达式,动态追踪变量,及时定位错误 「力扣代码调试器」处于内测阶段,具体功能可能会不定期调整,请以最终 release 版本为准。代码调试器目前支持 C++、Java、Python、Python3、C 等 5 种语言,持续更新中。 值得注意的是,这是一个会员才可以使用的功能。 力扣(LeetCode )For VSCODE 这个是一个可以让你在 VSCODE 编辑器中选题,写代码,测试,提交的力扣题目的插件。主要特点是可以利用自己编辑器的一切优势,包括但不限于编辑器主题,代码智能提示, 代码片段,调试工具。也就是说,上面提到的力扣代码调试器的大多数功能,你都可以通过 VSCODE 以及其扩展插件实现。 缺点是部分功能使用体验不好, 比如错误提示不明显,经常误提交等,不过最让我感到不舒服的是自定义测试用例,明显没有力扣官方做的好。 大家可以在 vscode 插件商店搜索 leetcode 进行下载安装。 LeetCode-Cheat 题解模板为了方便大家写出格式良好的题解,插件现在内置题解模板功能,目前模板只有一套,这套模板是我经常使用的题解模板。 安装好我们的插件(版本需要 v0.8.0 及以上)后,打开力扣中文版,会发现如下的按钮。 点击之后会自动引导你到一个新的页面, 该页面的题解语言,题目地址和题目名称信息会自动填充。 你可以快速完成时间复杂度,空间复杂度的插入,复杂度已经按照性能好坏的顺序给大家排好了,点击即可插入。 此外我们提供了若干常用的公式供你快速复制使用。除了公式,其他内容都可以在右侧的预览区域查看。 写完只会可以点击复制,将其复制到其他地方以便持久化存储。由于我们没有做持久化存储,因此页面刷新内容就会消失哦。 最后祝大家写出漂亮的题解! 数据结构可视化你可以使用 canvas 自由绘制各种数据结构,不管是写题解还是帮助理解题目都很有用。 我们提供了很多内置的模板供你快速上手。 如果你对内置的模板不满意,也可以将自己的模板保存以便下次使用。 学习路线算法怎么学?推荐按专题来。具体到某一个专题怎么学?这里提供了一个学习路线帮助你。本功能旨在将一个专题中的题目进行分类。专题本质就是对题目的一种划分,学习路线基于专题又进行了一次划分。 复杂度分析你的代码能会超时么?复杂度分析帮助你。 一键复制所有内置测试用例省去了一个个手动复制的过程,效率翻倍! 代码模板提供了大量的经过反复验证的模板。模板的作用是在你理解了问题的基础上,快速书写,并减少出错概率,即使出错,也容易 debug。 禅定模式 点击之后会变成这样: 底部控制台会消失,当你鼠标重新移过来或者退出禅定模式就出现了。 查看题解当你在任意非题目详情页或者我还没有收录的题目详情页的时候, 我都会列出当前我总结的所有题解。 其实我给比较经典的题目做了题解,因此这个题目数目不是很多,目前是 173 道题。另外有时候我直接写了专题,没有单独给每道题写题解,因此数量上要比 173 多很多。 当你进到一个我写了题解的题目详情页的时候, 你就可以正式使用我的插件了。 它可以: 给出这道题目的前置知识。换句话说就是我需要先掌握什么才能做出这道题。 这个题目的关键点。 哪些公司出了这道题。 我实在不会了,给我看看题解吧。好,满足你。 题解我就不看了,直接 show me code 吧。好,满足你。 根据公司,查找题目。面试突击必备 后期规划: 更多公司信息。 持续完善题目的公司信息,这个过程需要大家的帮助,大家可以把自己面试遇到的问题发给我(附带公司和岗位信息),我可以免费提供咨询服务。 岗位信息。 可视化调试。 可视化展示你的代码允许情况。 自动制定复习计划。 A I 智能提示。即新的提示也可以根据题目信息推测可能的解法。 等等 需要的话,可以去我的公众号《力扣加加》回复插件获取。 LeetBoard 这是一个帮助你写写画画的一个浏览器扩展工具。很多时候,我们需要在纸上演算一下,验证自己的思路,这个时候就可以用这个工具。 这个工具提供了多种常见数据结构的可视化,方便大家快速书写。目前支持的数据格式有:数组,链表,二叉树。 debug-visualizer 这是一个支持可视化调试的 VSCODE 插件。相比于平时我们 debug ,它能帮助我们可视化内存中的数据结构。比如数组,树,图等都可以胜任。由于其不是为力扣量身制作的,实际上它还有很多其他的用处, 比如可视化图表,比如折线图。其功能非常强大, 你甚至可以基于它定制一套自己专属的数据结构。 大家可以在 vscode 插件商店搜索 leetcode 进行下载安装。 总结这五种工具,除了 debug-visualizer,其他都是为力扣量身定做的。 各个工具功能侧重各不相同, 大家也可以结合起来使用。总的来说: 如果你想在编辑器中写力扣,那么 力扣(LeetCode )编辑器插件是一个不错的选择。配合 debug-visualizer 可以实现丝滑般柔顺的感觉。 如果你想在力扣解题过程中写写画画,屡屡思路,建议使用 LeetBoard。 如果你想看看题目的公司信息, 题解等,建议使用我写的 LeetCode-Cheat。 如果你不想使用本地编辑器, 并按照一堆插件,只想用浏览器去完成,那么力扣代码调试器绝对是不二选择。 另外还有一些网站可以可视化算法的执行过程,这对算法初学者来说尤其重要,algorithm-visualizer 就是这样的一个网站,它收录了几乎全部常见的算法, 相信可以满足你。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"入职 Google 一年多的感触(你们的大狗头来啦~)","slug":"goutou-2","date":"2022-12-31T16:00:00.000Z","updated":"2023-01-19T07:15:53.935Z","comments":true,"path":"2023/01/01/goutou-2/","link":"","permalink":"https://lucifer.ren/blog/2023/01/01/goutou-2/","excerpt":"这篇文章是 91 天学算法最具人气奖获奖选手狗头的投稿,原文可以在这里访问到。 以下为原文内容。","text":"这篇文章是 91 天学算法最具人气奖获奖选手狗头的投稿,原文可以在这里访问到。 以下为原文内容。 正文大家好,大狗头来啦~ 狗头已经入职狗家一年多啦。随着在职时间的增加,越发的觉得沟通在软件工程师职业中起着非常重要的作用。 大家因为觉得「工程师嘛是技术岗位」而常常忽略掉沟通这个软技巧(soft skill)的重要性。或者有时候,大家会觉得,「我只是个刚开始工作的大头兵,会写代码就行啦,沟通没那么重要。」 其实沟通一直是非常重要的。对于刚开始工作的工程师来说也是! 沟通技巧为什么呢?因为在大佬眼里,刚开始工作的工程师有啥技术呀!好沟通,说啥就懂,团队气氛融洽,大家都开心干活才是重点啊! 换句话说,在一个「代码写的还行的刺头」和一个「代码写的稍差的善于沟通脾气好大家都喜欢跟他说话的人」之间,大佬会选谁呢?当然是后者啦~因为代码写的差可以教,但是刺头很难改变呀~ 当然,这可能是个有点理想化的例子。换成更贴近真实情况的例子就是「大佬们希望招一个又会写代码、又好沟通、又有上进心的人」。刚毕业的工程师看起来代码水平都差不多的情况下,自然就选择看起来好沟通的那个啦~ 但沟通是一个非常宏大的命题,这里提供两个具体的沟通技巧。 一、多用「好奇心」来代替「质问和指责」当你觉得 xxx 不应该这样,无论是薪水还是技术,你的提问方式可以是「我想知道我的薪水是由什么决定的呢?」而不是「为啥还不给我涨工资呢,你看不到我加班?」 「我想知道我距离升职还有哪些需要努力的地方呢?」而不是「我想升职,咋还不给我升职?我都在这两年啦!」 这样的好处是:首先这个提问方式会让对方舒服很多。然后当对方想了一遍,对方自我得出来了「哦这个事儿确实不合理」的结果的时候,你的目的也达到了。如果对方跟你说「因为 xxx」,那你也在没让对方不舒服的情况下拿到了需要知道的信息。一举两得。 二、在寻求反馈的时候给对方垫话当你在真诚的寻求对方给反馈的时候,可以给对方垫一些话,让对方觉得不会冒犯到你。因为反馈有的时候可能包含你做的不够好的内容,而并不是每个人都能真的接受这样的「批评」或者「带有建设性的意见」(actionable feedback)。作为被询问意见的对象,在跟你不是很熟悉的情况下,往往会倾向于选择比较安全的「夸赞反馈」。被夸当然很好也很有必要啦,但是有时候我们也真心的需要被指出「做的不好的地方」。这个时候,如果能先在对话当中说出自己的反思,比如「我觉得这个项目我可能 xxx 做的不太好,xxx 改进就好了,下次可以…」,然后再询问对方能不能给你点意见,对方就会比较好接话,你也更容易获得深层的意见。 最后希望大家都能在职场上步步高升,加油加油~ Lucifer 补充正如狗头所说“沟通是一个非常宏大的命题“, 我来给大家补充几点内容。 先说明背景遇到很多人问我问题都是直接”我在做 xxxx,但是不 work, 能给我看看为什么不 work 么?“ 如果你对 xx 没那么熟悉的话,这个时候很多人都会一头雾水。 更好的做法是,不要假设对方对你的业务很熟悉(除非他确信真的非常熟悉你的业务细节。实际上真的有这样的人,但是大多数情况下并不是)。 比如你可以说:我正在做 xxx 业务线的一个 yyy 功能,这个功能需要用到 zzz,做这个 zzz 的时候我遇到了一个问题,目前的现状是 。。。, 但是我的预期是 。。。,方便帮我看下为什么么? 请求帮助前,自己做好充分的准备寻求对方帮助的话自己要做好充足的准备,尽量不要出现对方付出的劳动比你自己还多情况,切忌让对方直接给你写代码。比如你请教张三某件事情怎么做的时候,不要直接问”听说你做过 xxx ,我遇到了 xxx 问题,你给我讲讲呗?” 甚至 “你告诉我应该具体咋写呗“, ”你帮我写写吧“。 更合适的做法是,描述你做了什么努力。然后说”可以帮我看下我的实现哪里有问题么?“ 总之就是把对方当成一个审查者的角色, 而不是一个执行者的角色。","categories":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/categories/谷歌/"},{"name":"软实力","slug":"谷歌/软实力","permalink":"https://lucifer.ren/blog/categories/谷歌/软实力/"}],"tags":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/tags/谷歌/"},{"name":"沟通","slug":"沟通","permalink":"https://lucifer.ren/blog/tags/沟通/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第九期)","slug":"91algo-9","date":"2022-10-14T16:00:00.000Z","updated":"2023-02-07T04:15:30.347Z","comments":true,"path":"2022/10/15/91algo-9/","link":"","permalink":"https://lucifer.ren/blog/2022/10/15/91algo-9/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言第九期的正式开启的时间为 2022-11-01。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第九期和往期的不同。 很多参加活动的同学其实是因为有算法笔试的需求的。因为我一直在考虑有没有什么办法更加地帮助到这些同学。 后来我突然注意到力扣官方和一些公司有一些合作,有一些公开的笔试,大家可以自由参与。 比如这场笔试:https://leetcode.cn/open-exam/perfectworld-2023/ 我的想法是尽量每个月让大家都参与一次公开笔试,然后将里面的题目以及题解更新到我们的 91 天学算法中。 但是这个频率不是我们能控制的, 需要看这个月没有有公司有这种公开笔试。具体形式还没想好,后面定了后会在 91 天学算法群里通知大家。 除此之外还有一些常规更新: 讲义和打卡增加最近更新时间,方便大家追踪讲义以及周期性打卡的更新。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 电子书可能会以 html 的形式给出,这样大家阅读体验会更好。不会出现什么代码无法滚动,导航不管用等等问题。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2022-11-01 至 2023-01-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第九期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第九期和上一期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。具体不同可以参考文章前半部分的介绍。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第八期)","slug":"91algo-8","date":"2022-07-08T16:00:00.000Z","updated":"2023-02-07T04:15:30.348Z","comments":true,"path":"2022/07/09/91algo-8/","link":"","permalink":"https://lucifer.ren/blog/2022/07/09/91algo-8/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言好多同学问下一期什么时候开始,其实最近比较忙,所以怕到时候大家有问题没有办法及时回复,因此推迟几天到月中给大家开始。 第八期的正式开启的时间为 2022-07-15。今天开始正式报名,活动开始前大家可以先预习。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第八期和往期的不同。 增加作业。毕竟重要的章节我们会给出作业,作业是每日一题的补充,同时也是给大家一个交流的机会,大家可以可以在一定时间内完成(不需要当前就完成了)。 电子书可能会以 html 的形式给出,这样大家阅读体验会更好。不会出现什么代码无法滚动,导航不管用等等问题。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 更多内容持续更新。。。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-07-15 至 2022-10-13 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 详细内容参考官网 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第八期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 往期很多小伙伴都得到了奖品,基本上满勤的都得到了安慰奖(红包一个)。有的人甚至得了很多次奖哦。所以大家努力下基本就相当于免费了,甚至可能赚钱。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第八期和前七内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。第七期增加了加练内容。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"恭喜又一个小姐姐进了谷歌","slug":"google-interview-tao","date":"2022-05-20T16:00:00.000Z","updated":"2023-01-07T13:53:04.630Z","comments":true,"path":"2022/05/21/google-interview-tao/","link":"","permalink":"https://lucifer.ren/blog/2022/05/21/google-interview-tao/","excerpt":"这是 JinTao(aka TJ) 的一篇分享自己如何面试准备进谷歌的经历分享,经历还是蛮曲折的,想去大公司的话非常具体参考价值。 TJ 是我的一个老朋友了,《91 天学算法》也跟了好几期,如果你也参加过《91 天学算法》,那么很有可能知道她。我俩私底下交流也蛮多的,得知她最近刚刚进了谷歌,就邀请她来写一下自己的面试准备经历。 本文中的 lucifer: 是我自己添加的内容,不是作者写的,大多是补充一些资料以便让大家进一步学习。","text":"这是 JinTao(aka TJ) 的一篇分享自己如何面试准备进谷歌的经历分享,经历还是蛮曲折的,想去大公司的话非常具体参考价值。 TJ 是我的一个老朋友了,《91 天学算法》也跟了好几期,如果你也参加过《91 天学算法》,那么很有可能知道她。我俩私底下交流也蛮多的,得知她最近刚刚进了谷歌,就邀请她来写一下自己的面试准备经历。 本文中的 lucifer: 是我自己添加的内容,不是作者写的,大多是补充一些资料以便让大家进一步学习。 关于刷题西法的讲义和他的方法足够足够了。另外,狗头刷题经验已经说很好了,很全面了。做题,注重理解,做西法推荐的经典题,和复习是关键。时间长了会忘记,会缓慢。 lucifer: 关于狗头的刷题经验,大家可以去《力扣加加》历史文章中搜索”狗头“关键字 谷歌很难说刷题范围,把西法讲义好好做,基础题做好,记得面试的时候交流积极主动且清楚,只要运气正常没遇到超级超级难的题,就可以了。谷歌比较注重 followup,比如一开始一个简单题,不要急着秒,要想各种 case,和面试官讨论,其实那些 case 有可能会在之后 followup 出。 这里的讲义指的是 《91 天学算法》的资料,活动介绍与参与方式见我的博客置顶帖。地址:https://lucifer.ren/blog/ 谷歌的面经不一定有意义,这里是指,”别人面了,靠记忆从英文翻译成了中文写出来,且非常模糊的那类面经“,时间不够的话,可以不看。leetcode tag 高频还是有参考意义的,但也不要指望遇到原题,几乎不可能,注重西法的讲义学习和练习就好。 面试过程一般的过程(可能不适用 meta,但狗家是追求这样的)是: 第一步,面试官说完题目,至少得花时间好好理解题意,套自己想的好的 test case,问清 corner case 处理情况。千万不要马上开始写。 第二步,说暴力法,说复杂度,想怎么优化,说方法,得到面试官的认可,才开始写代码。 第三步,写代码(一般西法有归纳模版,经典题一般有基本套路,得提前练熟) 第四步,过自己的 testing case,然后分析复杂度。 第五步,应对可能的 followup。一般都会有。 lucifer: 我在网上找到一份 《Interview Cheat Sheet》,这个 PDF 列举了面试的模板步骤。,详细指示了如何一步步完成面试。地址:https://github.com/azl397985856/leetcode/blob/master/assets/cheatsheet.pdf 整个过程尽可能积极,创造快乐积极的氛围,把面试官当同事。 lucifer: 这句话我熟啊,上次听似乎还是在狗头那儿 心态面试途中除了刷题挫败,还有就是对自己怀疑&对现状不满之类的负面情绪需要处理。如果需要心理咨询,尽快做,比如 betterhelp,也许可以减少弯路;尽可能每天瑜伽垫上跟视频运动下,同时多输入积极的东西,看看别人在最艰难的时候是怎么做的,给自己打打鸡血。每天至少 10 分钟,闭眼憧憬下未来,越疯狂和理想越好,想不到看不到,就很难有行动力,尤其是当外界没有一点正反馈的时候。最后就是保持感激,最艰难的时候,一定会有支持和帮助你的人,如果没有的话,就换圈子和积极寻找。再差再难过也得有点信念吧,这点是最难也是最重要的。 lucifer: 好的状态可以事半功倍 学习资料北美的同学,可以考虑一下 CodePath,是个很好的公益组织,有免费的刷题课程帮助提升 tech diversity,讲师都是名企的,一般都是 CodePath 之前的校友,现在反哺。如果已经上班了,也可以考虑志愿做 CodePath mentor,可以有效提高沟通能力。和西法讲义结合,强强联手,另外 AlgoExpert 也不错的。如果时间有限,可以只刷 AlgoExpert。题不在多,在于思路覆盖面。 最后找工作可以很漫长,也可以很快,很多变数,而且很看缘分,很多时候更是自己和自己的斗争吧,在最低谷的时候,也请相信自己。","categories":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/categories/谷歌/"},{"name":"面试","slug":"谷歌/面试","permalink":"https://lucifer.ren/blog/categories/谷歌/面试/"}],"tags":[{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/tags/谷歌/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"}]},{"title":"快来 **伯克利大学** 学计算机","slug":"daily-featured-2022-04","date":"2022-05-14T16:00:00.000Z","updated":"2023-01-07T14:01:24.672Z","comments":true,"path":"2022/05/15/daily-featured-2022-04/","link":"","permalink":"https://lucifer.ren/blog/2022/05/15/daily-featured-2022-04/","excerpt":"","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2022-042022-04-28[工具]import-local 是一个 NodeJS 端的工具,用于检测本地是否安装了某个包。如果你在开发一个 node 的 cli 应用,并且想要提高性能使用用户本地安装好的包,它就很适合你。 via: https://github.com/sindresorhus/import-local 2022-04-28[好文]rest api 中的 POST 和 PUT 有什么区别?一个用于新建资源,一个用于更新资源?不是的! via: https://stackoverflow.com/questions/630453/what-is-the-difference-between-post-and-put-in-http 2022-04-25[网站]一个低代码平台,通过它可以拖拽生成自己的网站。 值得一提的是,一些框架已经开始集成它了。你可以通过 builder.io 导出代码,然后通过一些工具生成各个框架的中间代码(比如 react,vue),也可以直接生成原生 JS 代码。 via: https://www.builder.io/ 2022-04-24[工具] 今天是中国五一假期的调休。调休真的是一个反人类的设计。 cypress 是一个 e2e 测试工具,可以很容易地集成到各个测试框架中去,比如 jest。 via: https://github.com/cypress-io/cypress 2022-04-23[好文]Navidrome 是一个音乐管理系统,你可以将其部署到本地,然后通过网页播放器播放本地音乐。Navidrome 体验 via: https://demo.navidrome.org/app/ 很多其他的工具使用的都是网上现成的资源,比如 1listen 就是使用的虾米,QQ 和网易云的音乐源。 via: https://www.navidrome.org/ 2022-04-22[好文]之前我写过 TypeScript 系列教程,其中有一篇是 上帝视角看 TypeScript 这个文章和我的那篇很像,都是从宏观上带大家理解 TypeScript 究竟做了什么。这篇文章比我的更细致一点,推荐大家结合起来阅读。 via: https://www.huy.rocks/everyday/04-01-2022-typescript-how-the-compiler-compiles 2022-04-21[网站]yandex 提供了简洁的在线翻译功能。 你可以直接输入文字进行翻译,也可以上传文件整体翻译。 它还提供了 api 供开发者使用,我的 leetcode 项目的部分内容就是用它进行翻译的。不得不承认,专有名词的翻译还是不行,比如动态规划会翻译为 dynamic planning。 via: https://translate.yandex.com/ 2022-04-20[好文]只要 5 美元就可以破解指纹解锁?Your Fingerprint Can Be Hacked For $5. Here’s How. via: https://blog.kraken.com/post/11905/your-fingerprint-can-be-hacked-for-5-heres-how/ 2022-04-19[好文]如果检测有没有全局变量引起的内存泄漏?这篇文章告诉你,作者提供了 js 代码,大家可以直接拿来主义。 文中有一点没有提到,其实很多全局变量是需要一定条件才会触发的。因此要想真正将其集成到项目的 CI 中,还需要一些额外的条件,那就是在程序中手动多次调用检测方法,而不是调用一次就完事了。 via: https://mmazzarolo.com/blog/2022-02-14-find-what-javascript-variables-are-leaking-into-the-global-scope/ 2022-04-18[工具]上海疫情使得很多人买不到菜。热爱折腾的网友开源了抢菜软件。 注:如非必要,不要使用这种极端方法,这会给其他没有菜吃的人带来很多麻烦。 via: ddshop dingdong-helper 2022-04-15[技巧]Github 的 issue 有很多不好用的地方,比如不支持 comment 自定义排序,以至于有一些插件“钻了空子”,提供了按照 reactions 进行排序的功能。 Disscussion 弥补了这块空白。 Disscussion 内置两种排序规则,分别是时间顺序和投票数。 你可以结合使用 issue 和 Disscussion 获得更好的体验。 近期 Github 还给 Disscussion 提供了问答社区才有的功能 - 选为答案。 只需要在新建 Disscussion 的时候类别选择 Q&A 就可以体验这个功能了。 via: https://github.com/azl397985856/leetcode/discussions 2022-04-14[好文]JS 的继承和传统的 class 继承(比如 Java 的)有什么区别?(How does JavaScript’s prototypal inheritance differ from classical inheritance?) via: https://dev.to/chalarangelo/how-does-javascripts-prototypal-inheritance-differ-from-classical-inheritance-oii 2022-04-13[网站]和昨天的推荐类似,这个网站也是移除不想要的部分的神奇网站。 只不过它不是移除图片中不想要的部分,而是分离音频中的人声和非人声。这样就可以达到移除人声或者移除噪音的效果。 via: https://vocalremover.org/ 2022-04-12[网站]一个无需注册的在线网站,你可以用它来移除图片中的部分内容。 via: https://www.magiceraser.io/ 2022-04-11[网站]一个俄罗斯的网站,据说是全世界最大的名画博物馆。 并且提供免费的高清下载,比如蒙娜丽莎这里可以直接下载,分辨率是 3931 * 5178,4 M 左右的大小。 via: https://gallerix.asia/ 2022-04-08[网站]Games104 网站提供了从零学习游戏引擎的教程,有成型的完整代码托管在开源的 Github 仓库。 有做游戏的,或者想了解游戏引擎的可以看一下。 via: https://games104.boomingtech.com/sc/course-list/ 2022-04-07[好文]chrome 103 目前支持了 fs api。 用户可以通过 fs api 来读取文件,写入文件,删除文件,创建文件等。 比如读取文件的代码: 12345678910let fileHandle;document.querySelector(\".pick-file\").onclick = async () => { [fileHandle] = await window.showOpenFilePicker(); const file = await fileHandle.getFile(); const content = await file.text(); return content;}; 除了 chrome 103 ,其他部分浏览器的新版本也提供了支持,具体支持情况如下图。 via: https://css-tricks.com/getting-started-with-the-file-system-access-api/ 2022-04-06[杂谈]想去贵州看樱花~ via: https://fashion.sina.cn/l/ds/2022-03-07/detail-imcwipih5777616.d.html 2022-04-05[工具]bitbucket 是一个开源的代码仓库,可以用来存放开源项目的代码。 和 Github,Gitlab 不同,bitbucket 内置了 jira 用于管理需求 ,snyk 用于管理 包安全。个人感觉 Github 和 Gitlab 在这几方面体验还没那么好。 via: https://bitbucket.org 2022-04-03[技巧]vscode 中会自动为 typescript 项目选择 workspace 的 node_modules 的 typescript,但是我们可以手动选择 workspace。 方法很简单, 你只需要打开一个 workespace 下的 TypeScript 文件,然后点击右下角的 TypeScript 旁边的版本号。 然后会让你选择版本。 如果有多个 TypeScript ,错误使用其他版本的 TypeScript 会导致编译失败。项目中可以通过配置 vscode 的方式解决这问题。 具体地,大家可以在项目根目录的 .vscode 文件夹下新建一个 setting.json 然后进行如下配置。 123{ \"typescript.tsdk\": \"node_modules/typescript/lib/typescript.js\"} 更多用法参考官方文档:https://code.visualstudio.com/docs/typescript/typescript-compiling 2022-04-02[好文]Github 面试还会布置家庭作业? 家庭作业也通过 Github 进行。大概是给你一个仓库,然后你 fork 过去后进行编辑,完成后 pr 到原仓库进行 review。 via: https://github.blog/2022-03-31-how-github-does-take-home-technical-interviews/ 2022-04-01[网站]CS61A(Structure and Interpretation of Computer Programs)是伯克利所有计算机系学生必须要上的第一门编程课,前半部分以 Python 为主,后半部分以 Schema 为主。网站资源很丰富,作为一个普通游客最主要的就是课件,其提供了 html 和 pdf 两种格式。课件图文丰富,这和其他同级别课程差异很大,对新手比较友好。 via: https://cs61a.org/ 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2022-04","slug":"每日一荐/2022-04","permalink":"https://lucifer.ren/blog/categories/每日一荐/2022-04/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"西法的 2022 书单推荐","slug":"books-2022","date":"2022-04-25T16:00:00.000Z","updated":"2023-01-05T12:24:49.733Z","comments":true,"path":"2022/04/26/books-2022/","link":"","permalink":"https://lucifer.ren/blog/2022/04/26/books-2022/","excerpt":"去年推荐了基本技术书单,推荐的主要图书有: 你不知道的 JavaScript 淘宝地址 京东地址 算法图解 淘宝地址 京东地址 算法第四版 淘宝地址 京东地址 读者反响还不错,这次推荐了一些更专业的书单。","text":"去年推荐了基本技术书单,推荐的主要图书有: 你不知道的 JavaScript 淘宝地址 京东地址 算法图解 淘宝地址 京东地址 算法第四版 淘宝地址 京东地址 读者反响还不错,这次推荐了一些更专业的书单。 从一到无穷大第一本是大名鼎鼎的乔治.伽莫夫 的《从一到无穷大》,李永乐也非常推荐这本书,还针对这本书录制了视频,视频在 B 站上付费订阅的。 这本书里面的内容非常有趣,直接就点燃了我的学习欲望。比如文中提到所有的偶数和所有的整数是一样多的,还给出了证明方法。你可能会想这怎么可能呢? 然后就忍不住读下去。文中还穿插一些小故事,读起来没那么类,属于科普向的图书,没有很硬核的感觉。但是当你读完这本书的时候,会发现自己知识增加了。😄 淘宝地址 京东地址 时间简史这本书我还没有看完,就迫不及待和大家分享了。和《从一到无穷大》主题上有一点重叠,不过更加深入,会比较难懂一些。 如果你肯耐心看下去,可能就会发现其中的乐趣所在。等我看完了再给大家谈谈读后感。 淘宝地址 京东地址 黑客与画家这本书没有教你怎么写代码,似乎整本书又都在教你怎么写代码。 给你一种讲了,似乎又没讲的感觉。文中很多的观点倒现在都没有过时,要知道这本书出版过了几十年了(中译本都十几年了)。 摘一段黑客与画家的一个小节给大家感受一下。 《黑客与画家》摘录 - 14. 梦寐以求的编程语言让我们试着描述黑客心中梦寐以求的语言来为以上内容做个小结。 这种语言干净简练,具有最高层次的抽象和互动性,而且很容易装备,可以只用很少的代码就解决常见的问题。不管是什么程序,你真正要写的代码几乎都与你自己的特定设置有关,其他具有普遍性的问题都有现成的函数库可以调用。 这种语言的句法短到令人生疑。你输入的命令中,没有任何一个字是多余的,甚至用到 shift 键的机会也很少。 这种语言的抽象程度很高,甚至你可以快速写出一个程序的原型。然后,等到你开始优化的时候,它还提供一个真正出色的性能分析器,告诉你应该重点关注什么地方。你能让多重循环快得难以置信,并且在需要的地方还能直接嵌入字节码。 这种语言有大量优秀的范例可供学习,并且非常符合直觉,你只需要花几分钟阅读范例就能领会应该如何使用此种语言。你偶尔才需要查阅操作手册, 它很薄,里面关于限定条件和例外情况的警告寥寥无几。 这种语言内核很小,但很强大。各个函数库高度独立,并且和内核一样经过精心设计,它们都能很好地协同工作。语言的每个部分就像精密照相机的各种零件一样完美契合,不需要为了兼容性问题放弃或者保留某些功能。所有的函数库的源码都能很容易得到。这种语言能很轻松地与操作系统和其他语言开发的应用程序对话。 这种语言以层的方式构建。较高的抽象层透明地构建在较低的抽象层上。如果有需要的话,你可以直接使用较低的抽象层。 除了一些绝对必要隐藏的东西。这种语言的所有细节对使用者都是透明的。它提供的抽象能力只是为了方便你开发,而不是强迫你按照它的方式行事。事实上,它鼓励你参与它的设计,给你提供与语言创作者平等的权利。你能够对它的任何部分加以改变, 甚至包括它的语法。它尽可能让你自己定义的部分与它本身定义的部分处于同等地位,这种梦幻般的编程语言不仅开放源码,更开放自身的设计。 淘宝地址 京东地址 哲学家们都干了些什么?这本书和技术无关了,但个人认为也属于是科普读物。 哲学家这种在我们看来十分高大上的物种,在这本书中被扒拉地明明白白。这让我想起来呼兰讲过的一个段子: A: 你是干什么的? B(意味深长地说): 我是诗人。 A(弱弱的问): 那你是一毕业就当的诗人么? 哦,原来诗人就是无业游民啊。 这本书也是一样,让你在轻松愉快中笑一笑还把知识还顺便给学了,这上哪讲理去? 淘宝地址 京东地址 总结以上就是我强烈推荐给大家阅读的四本书。如果你有什么好书推荐的话,可以私信我哦~ 我最近缺好书看了,另外说不定下一期的书单就有它了。","categories":[{"name":"书单","slug":"书单","permalink":"https://lucifer.ren/blog/categories/书单/"}],"tags":[{"name":"书单","slug":"书单","permalink":"https://lucifer.ren/blog/tags/书单/"}]},{"title":"Git 中的算法第二弹-最近公共祖先","slug":"git-merge-base","date":"2022-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.757Z","comments":true,"path":"2022/04/06/git-merge-base/","link":"","permalink":"https://lucifer.ren/blog/2022/04/06/git-merge-base/","excerpt":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第二篇 - 《Git 中的最近公共祖先》,第一篇在 这里","text":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第二篇 - 《Git 中的最近公共祖先》,第一篇在 这里 git merge-basegit merge-base A B 可以查找 A 提交和 B 提交的最近公共祖先提交。而由于 分支和标签在 Git 中仅仅是提交的别名,因此 A 和 B 也可以是分支或者标签。 12345 o---o---o---B /---o---1---o---o---o---A 如上图的 Git 提交情况,那么 git merge-base A B 会直接返回提交 1 的哈希值。 更多关于 merge-base的用法细节可以参考 官方文档 如何查找公共祖先呢?我们知道 git 每次提交,实际上都是新建了一个提交对象,里面记录一些元信息。比如: 提交人 提交时间 哈希 上一次提交的引用 。 而上一次提交的引用导致了 git 提交是一个链表结构。而 git 支持创建分支,并基于分支进行开发,因此 git 提交本质上是有向无环图结构。 如上图,我们基于提交 2 创建了新分支 dev,dev 上开发后我们可以将其合并到主分支 master。 而当我们执行合并操作的时候,git 会先使用 merge-base 算法计算最近公共祖先。 如果最近公共祖先是被 merge 的 commit, 则可执行 fast-forward。如下图,我们将 dev 合并到 master 就可以 fast-forward,就好像没有创建过 dev 分支一样。 最后举一个更复杂的例子。如下图,我们在提交 3 上执行 git merge HEAD 提交 6。会发生什么? 答案是会找到提交 2。那怎么找到 2 呢? 如果从提交 6 出发不断找父节点,找到 1,并将其放到哈希表中。接下来再从提交 3 出发同样不断找父节点,如果父节点在哈希表中存在,那么我们就找到了公共祖先,由于是找到的第一个公共祖先,因此其是最近公共祖先,直接返回即可。 力扣中刚好有一个题目,我们来看下。 力扣真题题目地址 (236. 二叉树的最近公共祖先)https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ 题目描述1234567891011121314151617181920212223242526272829303132给定一个二叉树,找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 示例 1:输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1输出:3解释:节点 5 和节点 1 的最近公共祖先是节点 3 。示例 2:输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4输出:5解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。示例 3:输入:root = [1,2], p = 1, q = 2输出:1 提示:树中节点数目在范围 [2, 105] 内。-109 <= Node.val <= 109所有 Node.val 互不相同 。p != qp 和 q 均存在于给定的二叉树中。 前置知识 二叉树 树的遍历 哈希表 思路这道题是给你一个二叉树,让你从二叉树的根出发。 这和 Git 是不一样的,Git 中我们需要从两个提交节点出发往父节点找。 那是不是意味着上面方法不可以套用了? 也不是。我们可以在遍历二叉树的时候维护父子关系,然后问题就转化为了前面的问题。 代码 语言支持:Java Java Code: 123456789101112131415161718192021222324252627282930313233class Solution { HashMap<Integer, TreeNode> map = new HashMap<>(); // 关系为:key 的父亲是 value HashSet<TreeNode> set = new HashSet<>(); public void dfs(TreeNode root) { if (root.left != null) { map.put(root.left.val, root); dfs(root.left); } if (root.right != null) { map.put(root.right.val, root); dfs(root.right); } } public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { dfs(root); // 从 p 出发,找到 p 的所有祖先节点,将其放入 HashSet while (p != null) { set.add(p); p = map.get(p.val); } // 从 q 出发找到第一个能在 HashSet 中找到的节点,即为最近公共祖先 while (q != null) { if (set.contains(q)) { return q; } q = map.get(q.val); } return null; }} 复杂度分析 令 n 为链表长度。 时间复杂度:$O(n)$ 空间复杂度:$O(n)$ 优化实际上该算法效率并不高。如果我们仓库提交很多,也就是 N 非常大,也是会慢的。 有没有优化的可能? 当然可以。而且优化的角度有很多。 这不,这位同学就想到了预处理。 链接在这里。即第一次维护好了节点信息,将其存到文件里,那么以后执行 merge-base,就不需要对已经处理过的节点进行遍历了。理论上,不管 merge-base 多少次,我们都仅遍历一次节点。 真的这么理想么? 很可惜不是的。比如我执行了 rebase ,reset 等操作改变了节点怎么处理?这里的细节很多,我就不在这里展开了。感兴趣的可以加入我的力扣群讨论。 总结git merge-base 本质上就是一个寻找最近公共祖先的算法。 而这个算法最朴素的就是先从一个节点使用哈希表预处理,然后从另外一个节点开始遍历,找到的第一个在哈希表中出现的节点就是最近公共祖先。 这个算法也有优化空间,而优化后又需要考虑各种边界条件,即缓存失效问题。 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[],"tags":[{"name":"算法,最近公共祖先","slug":"算法,最近公共祖先","permalink":"https://lucifer.ren/blog/tags/算法,最近公共祖先/"}]},{"title":"程序员如何准备面试中的算法","slug":"interviewer","date":"2022-03-20T16:00:00.000Z","updated":"2023-01-05T12:24:50.091Z","comments":true,"path":"2022/03/21/interviewer/","link":"","permalink":"https://lucifer.ren/blog/2022/03/21/interviewer/","excerpt":"春招季来临,大家陆续已经开始准备面试斩获心仪 offer。 这次 lucifer 就从面试官角度给大家分享一些面试技巧,让大家面试时少走弯路。这次分享侧重算法面试。 我负责公司的面试已经有 5 年以上了,基本都是初面和二面,因此技术面试的层面比较深,更多的是了解候选人的技术能力是否达标。在这几年时间,我前前后后也面试了很多的候选人。这些人中有的技术能力不行,但也有些人很可惜,技术能力是可以的,但是却没能通过我的面试,为什么呢?","text":"春招季来临,大家陆续已经开始准备面试斩获心仪 offer。 这次 lucifer 就从面试官角度给大家分享一些面试技巧,让大家面试时少走弯路。这次分享侧重算法面试。 我负责公司的面试已经有 5 年以上了,基本都是初面和二面,因此技术面试的层面比较深,更多的是了解候选人的技术能力是否达标。在这几年时间,我前前后后也面试了很多的候选人。这些人中有的技术能力不行,但也有些人很可惜,技术能力是可以的,但是却没能通过我的面试,为什么呢? 面试考察什么?首先,通常判断候选人是否可以通过面试有如下几个标准: 技能能否胜任 沟通能力如何 工作热情如何 。。。 那么我面试的时候肯定也是围绕上面展开的,只不过侧重考察不同罢了。 算法题和编程题实际上能够很好地检验以上信息,而不仅仅是检验能力是否胜任。前提是面试官不能太死板,比如直接扔一个网上原题,有的甚至自己都不太会就拿出来考,这肯定是不行的。 有同学反馈算法题目做出来了,但是却挂了面试。这又是为什么呢? 除了想当然的那种做的很好,实际上 corner case 没考虑到或者不是最优解。还是有可能会被挂,为什么呢? 其实你题目做的很好,仅仅可以证明能力可以胜任,这不意味着其他也满足要求,比如上面提到的沟通能力,工作热情等等。 那么如何更好地展示自己,给面试官留下更好的印象从而通过面试呢?除了提高自己的技术实力之外,方法也很重要。这里我就总结了几个技巧给大家。 算法面试基本步骤 我在网上找到一份《Interview Cheat Sheet》,这个 PDF 列举了面试的模板步骤,详细指示了如何一步步完成面试。 这个 pdf 开头就提到了好的代码三个标准: 可读性 时间复杂度 空间复杂度 这写的太好了。 紧接着,列举了 15 算法面试的步骤。比如步骤一:当面试官提问完后,你需要先下来关键点(之后再下面写注释和代码) 看完我的感受就是,面试只要按照这个来做,成功率蹭蹭提升。 pdf 地址 多问几次,确保题目理解正确。 比如输入输出,corner case 等等。试想一个同事拿到需求不分青红皂白就去做,结果发现做出来的不对,多尴尬?你会想和这样的同事一起共事么? 比如你可以问: 需要考虑负数么? 结果的顺序重要么? 可以使用额外空间么? 。。。 先说思路,再写代码。 虽然题目理解没问题,但是可能思路根本不对,或者面试官不想要这个解法。 试想你是面试官, 对面写了半天代码。思路根本就不对或者不是你想要的解法,是不是有点失望? 所以尽量先说思路,面试官觉得没问题再写,不要浪费彼此的时间。 比如你可以说: 朴素的暴力的思路是:xxxx。而这么做的话时间复杂度是 xxxx。 朴素的暴力算法的瓶颈在于 xxx,我们可以使用 xxxx 算法进行优化,这样复杂度可以优化到 xxxx。 上一步骤给面试官讲思路的时候,代入几个例子。 corner case 和 normal case 都至少举一个来说明。这样不仅让面试官感觉你沟通能力强,而且可以帮助你进一步理解题目以及理清思路。 有点时候大家面试比较紧张,经过代入例子讲解紧张感会慢慢减少。就好像我做技术分享,往往紧张的就是前面几分钟,后面就不会有紧张的感觉了。 比如你可以说: 当输入为 [1,2,3,4] 时, 我们的先 xxxx, 这样就 xxxx,接下来计算出 xxxx ,最后xxxx 。 当输入为负数时,我们可以直接返回 xxx。 写代码要快,不要来来回回改,不然就会被扣上 coding 不行的帽子。 其实有前面的铺垫,写快不难。因为前面其实讲思路,通过例子讲解法你已经对算法很了解了才对。 但是思路没问题不代表可以完整写出来。同样可以完整写出来不代表不需要涂涂改改。这需要大家做题目前先勾勒出代码的大体框架。 一个简单的技巧就是:分模块写代码,一个功能一个函数。这样可以减少不断地涂涂抹抹,修修补补的可能性。 一个例子: 12345678910def solve(nums): def check(mid): # do something def another_func(): pass # ... l, r = 0, len(nums) - 1 while l <= r: mid = (l + r) // 2 check(mid) 其中 solve 为主体函数,而 check 和 another_func 则为拆分的函数。 我给大家的练习建议是:要先刷题,并且要重复地刷,一道题刷 3 遍胜过刷 3 道不同的题。 一般来说,一道题(包括纯换皮)刷个 3,5 遍是正常的。不知道刷什么题,怎么刷的可以在我公众号回复 91 参加 91 天打卡活动。 写完代码自己先写个测试。 这不仅体现了你代码习惯好,而且能帮你发现代码写的有没问题。 小技巧:你可以把前面你和面试官举的例子以及面试官给的例子代进去看对不对,由于有前面铺垫了,这个应该也很快。 一个例子: 1234567891011121314def solve(nums): def check(mid): # do something def another_func(): pass # ... l, r = 0, len(nums) - 1 while l <= r: mid = (l + r) // 2 check(mid)assert solve([1,2,3,4]) == Trueassert solve([]) == False# ... 这里我们使用 assert 进行了断言。类似于我们日常开发后对代码进行测试。 总结最后给大家整理一个流程图,方便大家记忆,大家可以把图存起来备用。 最后希望大家可以在春招季斩获自己信息的 offer。也欢迎大家进我的春招群。加我微信,回复春招即可进群。","categories":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"}],"tags":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第七期)","slug":"91algo-7","date":"2022-03-11T16:00:00.000Z","updated":"2023-02-07T04:15:30.327Z","comments":true,"path":"2022/03/12/91algo-7/","link":"","permalink":"https://lucifer.ren/blog/2022/03/12/91algo-7/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 前言前一期正式于 2022-03-12 结束,下一期正式开启的时间为 2022-04-01。今天开始正式报名,活动开始前大家可以先预习。 为什么要04-01 开始,这么久?原因在于最近力扣举办了2022 春招季活动。这个活动主要是三月份,有很多同学想参与。为了和这个活动分流,大家也刷地不那么累。决定 4 月再开启,希望大家可以理解。 力扣 2022 春招季活动我加入的是二进制战队。我还组建了一个春招群,大家想进的可以话可以加我个人微信私信我加群(微信号 DevelopeEngineer)。 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第七期和往期的不同。 有的同学反应 91 题太少了,而讲义给的题目太多了。为此第七期我删除了部分讲义题目,然后增加推荐加练小节,里面对于一些强烈推荐的会给一个标识。这样大家可以根据自己的情况练习。学有余力的多刷刷,有一点时间的把我强烈推荐的做做即可。真没时间的就做做打卡题好了。 如果做打卡题的时间也没有的话就不推荐参与了。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-04-01 至 2022-06-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 注意上面的时间是上一期的。这期的具体时间会在活动正式开始前(2022-04-01 前)在官网中更新,敬请期待! 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第七期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 活动规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 奖励 对于坚持打卡满勤的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡(虚拟道具,自动使用)一张哦,使用补签卡后可以满勤也算满勤。 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要原价参与了,找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第七期和前六内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。第六期增加了排序章节和线段树章节。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"91 第七期和战队赛一起来了?","slug":"zhandui","date":"2022-03-03T16:00:00.000Z","updated":"2023-01-07T12:34:55.853Z","comments":true,"path":"2022/03/04/zhandui/","link":"","permalink":"https://lucifer.ren/blog/2022/03/04/zhandui/","excerpt":"有两个事情和大家同步。","text":"有两个事情和大家同步。 第七期《91 天学算法》第一个事情是最近问的比较多的问题:下一期(第七期)什么时候开始。 力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 大概时间就是三月份, 具体时间需要等公众号正式通知,以防止大家提前蹲点抢票。老规矩,前 50 免费。 力扣春招季第二个事情是最近和力扣官方合作的一个福利活动。 力扣春招季上线了一个刷题活动,届时官网会有专门的活动入口。我准备进个战队,大家看要不要一起? 时间也是三月份, 大概三月中旬的样子。具体时间等公众号通知,大家点个关注和星标后耐心等待即可。 此外,我还邀请力扣的工作人员进群,来围观大家的刷题,并且在活动期间给大家送一些 buff。 buff 有很多。比如: 免费的春招直播课:算法笔试技巧和算法技术分享,技术有提升; 官方内容每日精选:春招季每日 1 道押题、站内用户总结的优质面经以及其他有趣的话题内容; 持续刷题彩蛋:算法资讯,面试资料、经典好书,努力有激励; 社群刷题氛围:志同道合的同学与助教老师,学习有交流。 。。。 ​","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"远程办公有多爽?","slug":"wfh2","date":"2022-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.579Z","comments":true,"path":"2022/02/20/wfh2/","link":"","permalink":"https://lucifer.ren/blog/2022/02/20/wfh2/","excerpt":"大家好,我是 lucifer! 上一次和大家聊我的工作,算起来有超过半年的时间了。很多同学对远程办公比较感兴趣,我们就继续聊聊这个话题。 如果用一句话来概括文章内容的话,那就是:工作有趣,待遇满意,时间充足。 以下是详细内容:","text":"大家好,我是 lucifer! 上一次和大家聊我的工作,算起来有超过半年的时间了。很多同学对远程办公比较感兴趣,我们就继续聊聊这个话题。 如果用一句话来概括文章内容的话,那就是:工作有趣,待遇满意,时间充足。 以下是详细内容: 工作有趣具体内容涉及公司机密,不方便透露。但是可以说的是,目前负责的内容是一些前端基础设施的建设,具体内容涉及到工程化,编译,跨端等。并且负责的内容和之前工作有一定差异,因此也学到了不少东西。 ”拥有高绩效远程团队的公司会投资于员工完成工作所需的技术“,往往远程办公的技术团队的技术能力更强! 节奏上,也是常见的 sprint 形式来组织,并且 task 不会压得特别紧,特别赶。 很多时候工作上有很大的自我发挥空间。即不是明确到位的一个方法让你填空,更像是问答题,让你自己发挥的成分会多一点。 待遇满意不方便透露具体数字。但是可以向大家说:我现在的工资福利待遇是原来的不止两倍。 和国内大公司(比如头条,阿里等)对比的话,普遍也比他们要高一点。 时间充足不仅仅是待遇丰厚,时间也很充裕。 由于没有通勤时间,因此时间上会有一些天然优势。有些人原本单程通勤就需花费 30 分钟,而在家办公每周可节省五个小时的时间,此外还能节省交通成本。 我们虽然没通勤成本,但是交通补助啥的还是有的 因此我时不时可以和朋友出去 high 一下,逛街打游戏的时间也比以前多。 这已经感觉自己相当自由了,而且我的年假还有 10 多天。如果把这年假用到旅游的话,去趟国外都没问题。一年一次长途旅行根本不是梦。 回答问题最后回答一些网友常见的问题。 工作会摸鱼么? 答案是不会刻意摸鱼。 对我而言,摸鱼是为了对抗明明工作都完成了,也到点了,可就是不能(或不好意思或为了蹭补助)走的局面。和坐班不同,不要求你一定在工位上,因此远程办公就没有这个问题。 会不会 007 待命? 我没有 007 待命。个人认为 007 待命和是否远程办公无关,而是和公司有关。如果一个公司的文化就是这样,那么无论是否远程,都需要随时 oncall,不需要我举例子了吧? 工作和生活没有界限? 其实还好。大多数情况干够 8 个小时,我就不会继续工作了,而是全心投入生活。除非某一个任务比较紧急或者由于自身问题没有完成,实际这些情况都很少。 那你英语肯定很好吧? 我英语不好,尤其是口语。 实际工作中,面对英语的情况大多数是文字以及例会。而例会虽然是口语英语,但是需要你说话的情况比较少。你想啊,一大堆人开会,一个人发言的时间会有多少? 因此口语在我们这里问题不大。 不过你的英语阅读能力和听力还是有点要求的,不然还是有点问题的。 面试肯定很难吧?我行么? 面试难度和国内大公司难度差不多,侧重点不同。我们更侧重思维,架构设计,算法等底层能力,对框架,语言等要求不高。 我举几个面试题的例子给大家感受一下。 如果我们的网站 QPS 突然变得特别大,以至于当前的单机服务器的模式顶不住了,你会怎么做? 给你一堆股票的价格,让你不断地求最近一小时的股票平均值。比如每秒计算一次最近一个小时的股票平均值。 。。。 系统设计的话可以参考这个 system-design-primer 仓库.这个仓库从大的方向讲述了系统设计中的常见场景,比如如何设计高可用系统。这个仓库 star 非常多,从侧面反应项目质量还行,掘金官方也参与了仓库的翻译工作,大概看了下,翻译质量不错。 算法的话可以参考下这个 leetcode,里面有几十个算法专题以及几百道经典算法面试题的解析,通俗易懂。掌握了里面所有内容后面试绝大多数公司至少算法这块是没问题的。 内推你如果也想要这样的远程办公(wfh),工作和生活平衡(work life balance) 的工作,可以找我内推,大家加我的微信就好(可以提供免费面试咨询哦) 目前的岗位仅支持 base 到国外,相应待遇会直接对标国外的薪资标准,不打算出国的同学就不用考虑了。不过这里还有一些别的远程办公公司可以给大家内推,比如微软, Gitlab 加好友请注明:内推","categories":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/categories/成长经历/"}],"tags":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/tags/成长经历/"}]},{"title":"Git 中的二分","slug":"git-bisect-bug","date":"2022-02-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.982Z","comments":true,"path":"2022/02/11/git-bisect-bug/","link":"","permalink":"https://lucifer.ren/blog/2022/02/11/git-bisect-bug/","excerpt":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第一篇 - 《Git 中的二分》","text":"大家好,我是 lucifer。今天给大家分享 Git 中的算法。 这是本系列的第一篇 - 《Git 中的二分》 二分代码当我发现一个难以理解和发现的 bug 的时候,我的终极办法就是二分代码。 比如先删去文件 a 的一半代码(大约),然后测试问题是否还在。 如果问题不在了,那么我们就找到了出问题的代码。 如果问题还在,那么我们继续使用同样的办法,继续删去文件 a 的一半代码(大约),然后测试问题是否还在。 重复上述步骤,直到找到出问题的代码 这个方法在我一时没有思绪或者帮助别人定位问题的时候非常有用。由于这种做法时间复杂度大致是 logn,因此只需要短短几次我们就可以大致定位到问题代码。类似地,我们也可以在多个文件中同时进行二分。 二分提交更多的时候,我们是在两次发布之间发现了一个 bug。而这两个发布之间是有若干 commit的,并且 commit 还不少(几十个甚至上百个)。 我们也可以使用类似的方法。 先切换到两次发布之间的中间提交 x(即使得提交 x 相对于两次发布之间的距离差最小)。 实际上这个最小距离差要么是 0, 要么是 1 验证问题是否存在。如果不存在,我们不能确定就是这个提交的问题,不妨先标记当前提交 c 为 good。如果存在,不妨标记当前提交 c 为 bad。 经过上面的标记,我们就可以找到最早呈现 bad 的那次提交,并且最关键的是复杂度为logn,其中 n 为我们需要验证的提交的总次数。显然,这个工作量比逐个检查 commit要快很多。 不理解其中原理?稍后我们会讲。 Git 中的二分查找git 的开发者也想到了这一点,因此提供了 bisect 命令来帮助我们做上面这个事情。 使用 bisect 进行问题查找主要依赖于下面的三个命令: git bisect start 启动一个查找,start 后面可以加上 good 和 bad 的提交点: git bisect start [rev bad] [rev good] 如果不加 good 和 bad 的提交点,那么 git 将会等到我们使用 good 和 bad 命令指定对应的提交点后开始进行查找 git bisect good 用来标记一个提交点是正确的,后面可以加上 rev 来指定某个特定的提交点,如果不加,则默认标记当前的提交点。 git bisect bad 用来标记一个提交点是包含问题的,如果 bad 后可以加上 rev 来指定某个特定的提交点,如果不加,则默认标记当前的提交点。 背后原理我们来补前面的坑。为什么经过这样的标记,我们就可以找到第一个有问题(标记 bad)的提交?并且时间复杂度为 $O(logn)$。 正好力扣有一道原理,我们直接用它来讲吧。 题目地址(278. 第一个错误的版本)https://leetcode-cn.com/problems/first-bad-version/ 题目描述123456789101112131415161718192021222324252627282930你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 示例 1:输入:n = 5, bad = 4输出:4解释:调用 isBadVersion(3) -> false调用 isBadVersion(5) -> true调用 isBadVersion(4) -> true所以,4 是第一个错误的版本。示例 2:输入:n = 1, bad = 1输出:1 提示:1 <= bad <= n <= 231 - 1 思路可以看出,这个过程和我们上面的描述是一样的。并且我们的目标都是找到第一个出错的提交。 需要明确的是: 如果一个版本 x 是 good 的,那么 [1, x] 之间的所有提交肯定都是 good 的,因此待检测版本变为 [x+1,n] 如果一个版本 x 是 bad 的,那么 [x, n] 之间所有的提交肯定都是 bad 的。由于我们要找到的是第一个有问题的版本,因此待检测版本变为 [1,x-1] 因此无论我们检测的版本是 good 还是 bad,我们都可以将待检测的版本数量变为一半,也就是说我们可以在 $logn$ 次内找到第一个有问题的版本。 如果你看过我的二分专题,应该知道这就是我总结的最左二分。 二分专题(上) 二分专题(下) 代码 语言支持:Python3 Python3 Code: 123456789101112class Solution: def firstBadVersion(self, n): l, r = 1, n while l <= r: mid = (l + r) // 2 if isBadVersion(mid): # 收缩 r = mid - 1 else: l = mid + 1 return l 复杂度分析 时间复杂度:$O(logn)$ 空间复杂度:$O(1)$ 总结二分大法在日常工作中应用还是蛮多的,二分找 bug 是其中一个很实用的技巧。最简单的二分找 bug 可以通过删除文件内容的方式进行。而如果文件很多,就很不方便了,这个时候我们可以使用二分提交来完成。 其中的原理其实也很简单,就是一个朴素的最左二分。如果大家对此不熟悉,强烈建议看下文章中提到的二分专题,两万字总结绝对让你有所收获。","categories":[],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/tags/二分/"}]},{"title":"跨域了? 装个插件就够了!","slug":"cors-extension","date":"2022-02-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.566Z","comments":true,"path":"2022/02/06/cors-extension/","link":"","permalink":"https://lucifer.ren/blog/2022/02/06/cors-extension/","excerpt":"浏览器为了安全引入了同源策略,这直接导致默认情况下前后端域名如果不同,那么则功能会受限。 随着前后端分离的发展,前端和后端职责的分离,前端会有专门的本地开发服务器(local dev server)用于本地开发。这个时候和后端接口联调时就很可能会遇到跨域安全问题。 这本身是浏览器的一种安全策略,但是却对前端开发造成了影响。如何解决呢?","text":"浏览器为了安全引入了同源策略,这直接导致默认情况下前后端域名如果不同,那么则功能会受限。 随着前后端分离的发展,前端和后端职责的分离,前端会有专门的本地开发服务器(local dev server)用于本地开发。这个时候和后端接口联调时就很可能会遇到跨域安全问题。 这本身是浏览器的一种安全策略,但是却对前端开发造成了影响。如何解决呢? 之前我的解决方式是通过本地代理(node 或 nginx 等服务)解决。基本思路就是给请求响应头增加大概如下内容: 1234Access-Control-Allow-Origin: https://foo.exampleAccess-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: X-PINGOTHER, Content-TypeAccess-Control-Max-Age: 86400 后来我使用了更方便的工具: 浏览器扩展。之后跨域问题便可以一去不复返。 刚开始的时候用的是一个专门给请求加跨域头的插件 Allow CORS: Access-Control-Allow-Origin ,地址:https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf/related?hl=en-US。 这个插件使用起来非常简单,只需要点击切换 on 和 off 的状态即可。 on 的时候会自动给请求头增加跨域功能,off 则相当于禁用了插件。 后来我发现这个插件有些头不会加上,比如 access-control-expose-headers 。 因此一个通用的给请求增加头信息的插件就有必要了。于是我选择了 requestly 美中不足是每个规则只能免费改一个头。不过好消息是你可以新建多个规则,每个规则改一个头就可以白嫖了。 地址:https://app.requestly.io requestly 不仅仅可以增加跨域请求头,理论上可以对请求和响应做任意的修改。因此用来做 mock,统一加参数等等都是可以的。 基于此,使用浏览器扩展就可以彻底解决前端在本地开发时候遇到的跨域问题了。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"听说逆向思维能够降低时间复杂度?","slug":"backward","date":"2022-01-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.606Z","comments":true,"path":"2022/01/27/backward/","link":"","permalink":"https://lucifer.ren/blog/2022/01/27/backward/","excerpt":"以终为始在日常生活中指的是先确定目标,再做好计划。之前读管理学的书的时候,学到了这个概念。 而在算法中,以终为始指的是从结果反向推,直到问题的初始状态。 那么什么时候适合反向思考呢?简单的原则就是: 正向思考的情况比较多 代码比较难写或者算法复杂度过高 这个时候我们可以考虑反向操作。 我的习惯是如果直接求解很难,我会优先考虑使用能力检测二分,如果不行我则会考虑反向思考。 关于能力检测二分,可以在我的公众号中找到,大家可以在《力扣加加》回复二分获取。 今天西法通过三道题来给大家聊聊到底怎么在写算法题的时候运用以终为始思想。","text":"以终为始在日常生活中指的是先确定目标,再做好计划。之前读管理学的书的时候,学到了这个概念。 而在算法中,以终为始指的是从结果反向推,直到问题的初始状态。 那么什么时候适合反向思考呢?简单的原则就是: 正向思考的情况比较多 代码比较难写或者算法复杂度过高 这个时候我们可以考虑反向操作。 我的习惯是如果直接求解很难,我会优先考虑使用能力检测二分,如果不行我则会考虑反向思考。 关于能力检测二分,可以在我的公众号中找到,大家可以在《力扣加加》回复二分获取。 今天西法通过三道题来给大家聊聊到底怎么在写算法题的时候运用以终为始思想。 机器人跳跃问题这道题来自于牛客网。地址:nowcoder.com/question/next?pid=16516564&qid=362295&tid=36905981 题目描述1234567891011121314151617181920212223242526272829303132333435363738时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 32M,其他语言64M机器人正在玩一个古老的基于DOS的游戏。游戏中有N+1座建筑——从0到N编号,从左到右排列。编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位。起初, 机器人在编号为0的建筑处。每一步,它跳到下一个(右边)建筑。假设机器人在第k个建筑,且它现在的能量值是E, 下一步它将跳到第个k+1建筑。它将会得到或者失去正比于与H(k+1)与E之差的能量。如果 H(k+1) > E 那么机器人就失去 H(k+1) - E 的能量值,否则它将得到 E - H(k+1) 的能量值。游戏目标是到达第个N建筑,在这个过程中,能量值不能为负数个单位。现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?输入描述:第一行输入,表示一共有 N 组数据.第二个是 N 个空格分隔的整数,H1, H2, H3, ..., Hn 代表建筑物的高度输出描述:输出一个单独的数表示完成游戏所需的最少单位的初始能量输入例子1:53 4 3 2 4输出例子1:4输入例子2:34 4 4输出例子2:4输入例子3:31 6 4输出例子3:3 思路题目要求初始情况下至少需要多少能量。正向求解会比较困难,因此我的想法是: 能力检测二分。比如能量 x 是否可以,如果 x 可以,那么大于 x 的能量都可以。依此我们不难写出代码。 反向思考。 虽然我们不知道最开始的能量是多少,但是我们知道最后的能量是 0 才最优,基于此我们也可以写出代码。 这里我们使用第二种方法。 具体来说:我们从后往前思考。到达最后一级的能量最少是 0 。而由于: 1next = pre + (pre - H[i]) 因此: 1pre = (next + H[i]) / 2 由于除以 2 可能会出现小数的情况,因此需要 ceil。 你可以: 1pre = math.ceil((next + H[i]) / 2) 也可以: 1pre = (next + H[i] + 1) // 2 // 是地板除,即向下取整 代码(Python)123456n = input()H = input().split(\" \")ans = 0for i in range(len(H) - 1, -1, -1): ans = (ans + int(H[i]) + 1) // 2print(ans) 复杂度分析 时间复杂度:$O(n)$ 空间复杂度:$O(1)$ 这个题目的关键一句话总结就是:我们需要确定最少需要多少初始能量,因此我们是不确定最初的能量的,我们可以确定的是达到最后一个建筑能量是 0 才最优。 2139. 得到目标值的最少行动次数第二道题是 2022.01 月份的一场周赛的第二题,题目还是比较新的。 题目地址https://leetcode-cn.com/problems/minimum-moves-to-reach-target-score/ 题目描述1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950你正在玩一个整数游戏。从整数 1 开始,期望得到整数 target 。在一次行动中,你可以做下述两种操作之一:递增,将当前整数的值加 1(即, x = x + 1)。加倍,使当前整数的值翻倍(即,x = 2 * x)。在整个游戏过程中,你可以使用 递增 操作 任意 次数。但是只能使用 加倍 操作 至多 maxDoubles 次。给你两个整数 target 和 maxDoubles ,返回从 1 开始得到 target 需要的最少行动次数。 示例 1:输入:target = 5, maxDoubles = 0输出:4解释:一直递增 1 直到得到 target 。示例 2:输入:target = 19, maxDoubles = 2输出:7解释:最初,x = 1 。递增 3 次,x = 4 。加倍 1 次,x = 8 。递增 1 次,x = 9 。加倍 1 次,x = 18 。递增 1 次,x = 19 。示例 3:输入:target = 10, maxDoubles = 4输出:4解释:最初,x = 1 。递增 1 次,x = 2 。加倍 1 次,x = 4 。递增 1 次,x = 5 。加倍 1 次,x = 10 。 提示:1 <= target <= 1090 <= maxDoubles <= 100 思路由于刚开始数字为 1,最终状态为 target。因此正向思考和反向思考都是 ok 的。 而如果正向模拟的话,虽然很容易实现,但是时间复杂度太高。 这是因为从 1 开始我们有两个选择(如果仍然可以加倍),接下来仍然有两个选择(如果仍然可以加倍)。。。 因此时间复杂度大致为 $O(target * maxDoubles)$。代入题目给的数据范围显然是无法通过的。 而正向思考比较困难,我们不妨从反向进行思考。 从 target 开始,如果 target 是奇数,显然我们只能通过 + 1 而来,即使我们仍然可以加倍。这样时间复杂度就降低了。不过这还不够,进而我们发现如果 target 为偶数我们应该选择加倍到 target(如果仍然可以加倍),而不是 + 1 到 target。这是因为 我们是反向思考的,如果你现在不选择加倍而是后面再选择加倍那么加倍带来的收益会更低 加倍的收益一定大于 + 1,换句话说加倍可以更快达到 target。 基于此,不难写出如下代码。 代码 语言支持:Python3 Python3 Code: 12345678910111213class Solution: def minMoves(self, target: int, maxDoubles: int) -> int: ans = 0 while maxDoubles and target != 1: ans += 1 if target % 2 == 1: target -= 1 else: maxDoubles -= 1 target //= 2 ans += (target - 1) return ans 复杂度分析 如果 maxDoubles 无限大,那么时间大概是 $log target$。而如果 target 无限大,那么时间大概是 maxDoubles。因此时间复杂度为 $O(min(maxDouble, log target))$ 时间复杂度:$O(min(maxDouble, log target))$ 空间复杂度:$O(1)$ LCP 20. 快速公交最后一道题是力扣杯的一道题,难度为 hard,我们来看下。 题目地址(20. 快速公交)https://leetcode-cn.com/problems/meChtZ/ 题目描述12345678910111213141516171819202122232425262728293031323334353637383940414243444546小扣打算去秋日市集,由于游客较多,小扣的移动速度受到了人流影响:小扣从 x 号站点移动至 x + 1 号站点需要花费的时间为 inc;小扣从 x 号站点移动至 x - 1 号站点需要花费的时间为 dec。现有 m 辆公交车,编号为 0 到 m-1。小扣也可以通过搭乘编号为 i 的公交车,从 x 号站点移动至 jump[i]*x 号站点,耗时仅为 cost[i]。小扣可以搭乘任意编号的公交车且搭乘公交次数不限。假定小扣起始站点记作 0,秋日市集站点记作 target,请返回小扣抵达秋日市集最少需要花费多少时间。由于数字较大,最终答案需要对 1000000007 (1e9 + 7) 取模。注意:小扣可在移动过程中到达编号大于 target 的站点。示例 1:输入:target = 31, inc = 5, dec = 3, jump = [6], cost = [10]输出:33解释:小扣步行至 1 号站点,花费时间为 5;小扣从 1 号站台搭乘 0 号公交至 6 * 1 = 6 站台,花费时间为 10;小扣从 6 号站台步行至 5 号站台,花费时间为 3;小扣从 5 号站台搭乘 0 号公交至 6 * 5 = 30 站台,花费时间为 10;小扣从 30 号站台步行至 31 号站台,花费时间为 5;最终小扣花费总时间为 33。示例 2:输入:target = 612, inc = 4, dec = 5, jump = [3,6,8,11,5,10,4], cost = [4,7,6,3,7,6,4]输出:26解释:小扣步行至 1 号站点,花费时间为 4;小扣从 1 号站台搭乘 0 号公交至 3 * 1 = 3 站台,花费时间为 4;小扣从 3 号站台搭乘 3 号公交至 11 * 3 = 33 站台,花费时间为 3;小扣从 33 号站台步行至 34 站台,花费时间为 4;小扣从 34 号站台搭乘 0 号公交至 3 * 34 = 102 站台,花费时间为 4;小扣从 102 号站台搭乘 1 号公交至 6 * 102 = 612 站台,花费时间为 7;最终小扣花费总时间为 26。提示:1 <= target <= 10^91 <= jump.length, cost.length <= 102 <= jump[i] <= 10^61 <= inc, dec, cost[i] <= 10^6 思路由于起点是 0,终点是 target。和上面一样,正向思考和反向思考难度差不多。 那么我们可以正向思考么? 和上面一样正向思考情况太多,复杂度过高。 那么如何反向思考呢?反向思考如何优化复杂度的呢? 由于题目可以在移动过程中到达编号大于 target 的站点,因此正向思考过程中坐标大于 target 的很多点我们都需要考虑。 而如果反向思考,我们是不能在移动过程中到达编号大于 0 的站点的,因此情况就大大减少了。而达编号大于 target 的站点只需要思考向右移动后再乘坐公交返回 target 的情况即可(也就是说我们是做了公交然后往回走的情况) 对于每一个位置 pos,我们都思考: 全部走路 直接乘公交 走几步再乘公交 在这三种情况取最小值即可。 问题的关键是情况 3,我们如何计算是走几步再乘公交呢?如果反向思考,我们可以很简单地通过 pos % jump[i] 算出来,而开始乘公交的点则是 pos // jump。 代码 语言支持:Python3 Python3 Code: 123456789101112131415class Solution: def busRapidTransit(self, target: int, inc: int, dec: int, jumps: List[int], cost: List[int]) -> int: @lru_cache(None) def dfs(pos): if pos == 0: return 0 if pos == 1: return inc # 最坏的情况是全部走路,不乘公交,这种情况时间为 pos * inc ans = pos * inc for i, jump in enumerate(jumps): pre_pos, left = pos // jump, pos % jump if left == 0: ans = min(ans, cost[i] + dfs(pre_pos)) else: ans = min(ans, cost[i] + dfs(pre_pos) + inc * left, cost[i] + dfs(pre_pos + 1) + dec * (jump - left)) return ans return dfs(target) % 1000000007 复杂度分析 令 T 为 jump 数组的长度。 时间复杂度:$O(target * T)$ 空间复杂度:$O(target)$ 总结反向思考往往可以达到降维打击的效果。有时候可以使得求解思路更简单,代码更好写。有时候可以使得情况更少,复杂度降低。 回顾一下什么时候使用反向思考呢?一个很简单的原则就是: 正向思考的情况比较多 代码比较难写或者算法复杂度过高 我给大家举了三个例子来说明如何运用反向思考技巧。其中 第一题正向思考只能使用逐一枚举的方式,当然我们可以使用二分降低复杂度,但是复杂度仍然不及反向思考。 第二题反向思考情况大大减少,复杂度指数级降低,真的是降维打击了。 第三题利用无法超过 0 的位置这点,反向思考降低复杂度。 这些题还是冰山一角,实际做题过程中你会发现反向思考很常见,只是主流的算法划分没有对应的专题罢了 。我甚至还有想法将其加入91 天学算法中,就像后期加枚举章节一样,我认为反向思考也是一个基础的算法思考,请诸君务必掌握!","categories":[],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"逆向思维","slug":"逆向思维","permalink":"https://lucifer.ren/blog/tags/逆向思维/"}]},{"title":"技术面试原来不止考技术?","slug":"non-tech-skill","date":"2022-01-03T16:00:00.000Z","updated":"2023-01-07T12:34:34.366Z","comments":true,"path":"2022/01/04/non-tech-skill/","link":"","permalink":"https://lucifer.ren/blog/2022/01/04/non-tech-skill/","excerpt":"大家好呀,狗头又出现啦~ 我在 21 年的时候无相关背景转码上岸狗家,并且接到了一些中小厂的 offer。在我准备面试和面试的过程中,我总结了一些非技术相关的技巧和心得。这些技巧和心得在技术面试中往往能起到锦上添花的作用。这篇文章讨论的所有技巧都跟代码能力和工程能力无关~","text":"大家好呀,狗头又出现啦~ 我在 21 年的时候无相关背景转码上岸狗家,并且接到了一些中小厂的 offer。在我准备面试和面试的过程中,我总结了一些非技术相关的技巧和心得。这些技巧和心得在技术面试中往往能起到锦上添花的作用。这篇文章讨论的所有技巧都跟代码能力和工程能力无关~ 当然啦,在技术面试中,技术是第一位的。只是在面试同一个岗位的时候,如果几个面试者技术都足够好,更好的沟通能力和表达能力能让自己更加突出。我有一个转码培训班 (coding bootcamp) 的同学在毕业两个月之内就找到了工作,她就经常说自己『写代码很差,但是能让跟我一起工作的人很开心。』诚然,她写代码『很差』是自谦,但跟她的交流确实很开心。 那为什么『交流开心』在技术面试中也很重要呢?因为面试官的目的不是寻找一个做题机器,而是寻找一个好的同事和合作伙伴,好的技术、善于学习的能力和良好的品质都很重要。但在技术面试里如何用有限的时间给面试官留下一个好的印象呢? 我其实并不是很喜欢『面试官』这个称呼,因为『官』暗示了等级的差距;其实大家都应该是平等且合作的。 我总结了以下 6 点建议给大家。 自己的心态和状态要是积极和开心的,因为情绪是会传染的。 和我们在很难过的朋友身边很难开心,在听相声和脱口秀的厂子里很难流泪一样,面试中面试者的传达出的情绪也会影响面试官对面试者的评价。 第一,面试者如果在面试的过程中传达出正向的情绪,那么说明面试者大概率也喜欢这份工作。喜欢一份工作,就会有动力(be motivated),那么工作做的应该也不差。 第二,在面试的时候,如果面试者的心情是愉悦和开心的,那么面试官在回想面试表现的时候,大概率也会回想起开心的情绪。我个人感觉,有的时候面试官可能记不住面试者所有技术上犯的错误,但是却一定能记得面试官对面试者的『感觉』。(题外话:面试者往往能深刻的记住自己犯的每一个错误,啊哈哈哈哈…)一份正面 (positive) 的感觉往往能让面试官对面试者评价更高。那该如何准备呢?首先一定要多跟自己不熟悉的人模拟面试来消除面试的紧张感。其次,可以找一些让自己能瞬间开心起来的心理暗示,比如一些开心的视频或者音乐,或者对着镜子给自己加油打气。我很喜欢在面试之前听安全着陆的《个人简介》,并且告诉自己我就是酷炫吊炸天因为我真的好他妈酷 ୧(๑•̀◡•́๑)૭!所以我面试的时候一般在问好和交流思想环节都是微笑的,因为我真的很帅嘛! 在跟对方交流的时候,留意对方的反馈,并且在阐述思路的时候时不时询问对方自己是否表达清楚。 首先明确,作为面试者,面试中的沟通的服务对象是面试官。换句话说,在面试的时候,沟通的评判标准主观也简单 ── 面试官听懂了就是好的沟通,听不懂就是不好的沟通。在跟对方沟通的时候,如果对方表现出疑惑的表情,最好能及时询问对方『自己是否有哪里解释的不太清楚?』注意,这里的提问并不是『你哪里没听懂?』。为什么这两者在沟通上有差别呢?因为『自己是否解释的不清楚』暗示了如果不通畅是自己的问题,并且自己希望可以得到反馈并且改进;而『你哪里没听懂?』则更像是在暗示『听懂』是对方应该达到的期待。如果对方没有听懂,那么对方似乎就没有达到你的期待。让人失望总是一件不那么愉悦的事情。如果自己说了很长的一段话,也可以来问一下对方是否还有什么要问的。总体来说就是避免『讲课 (lecturing)』而更多是一来一回的『沟通 (communication)』。 如果遇到不太喜欢给反馈的面试官,这一条好像没什么用… 但是我没遇到过扑克脸的面试官。 在交流的时候,尽量把每段话的结尾变成积极、正向、令人感到愉悦的事。 『End on a high note.』这是我 bootcamp 职业指导 (behavior coach) 告诉我的一条面试交谈法则。就像主持人常常讲究『烘托气氛』和『暖场』一样,面试的交流也需要被『暖』。 在面试中什么气氛是『暖』的呢?其实就是在每段交谈(也就是说你来我往,而不是一个人一直说)中,双方慢慢的感觉到对彼此的认可和愉悦。 举个例子:面试官问到我不会的技能的时候,我会回答『我不会』 (负面的),『但是很乐意学习』 (正面的),如果有了解的话还可以补充说『听说{这个工具}可以让{这个工具的优点},所以我一直很感兴趣』(正面且具体的)。 当被质疑/指出错误的时候,要以合作且感谢的心态进行对话。为什么是合作呢?因为技术面试其实更像是同事和同事之间的『试工作』。在真正的工作当中,工程师会需要解释自己的代码/实现方式;作为新人,犯错也不可避免。如何好的接受反馈和建议是非常重要的,也很大程度上决定了一个人是否能成长。给一些真实的面试官问题作为例子: 非合作心态 面试官:你这个写法我看不太懂啊?它怎么就能解这道题呢? 面试者:???这不很明显吗 / (沉默) / 这你不懂吗? 合作心态 下面是在面试官说的不对的时候。是的,面试官说的也不全对哈哈~ 面试官:你这个循环没有检查第二个数组在这个引索情况下是否为空,会报错的 我:确实一般来说没有判空是会报错(首先肯定对方),但是对于 JS 来说,如果这个数组里不存在这个引索,那么其实是’undefined’,不会有(a === b)的情况,所以不会报错~(大概是有两个数组 A 和 B,A.length > B.length, 我以 A 的 index 来循环 A,并且检查 A[i] === B[i]) 如果面试官说的对的话,可以肯定对方,并且给与感谢,因为毕竟这是一次让自己成长的机会。 面试官如果提出一个问题,有可能是在试图引导面试者自己发现自己的错误,就是 giving hints;也有可能是面试官对面试者使用的技术栈或者语言不太熟悉。无论是哪种情况,面试者如何应对『提出的问题』都非常重要。即便提出的问题是错的,面试者应该以合作和积极的心态去应对,而不是有防御性的或者是消极的。如果对于某个细节无法达成一致,可以按照面试官的假设来解决问题。这个方法除了能在情绪上避免争执,遵循某个预设来解决问题也是有现实意义的。比如,有的公司的代码规范里不允许一些写算法题的常规操作。 在遇到自己卡壳的地方的时候,能准确的说出卡的点。 我在面狗家之前,问过狗家面试官『When the candidate is stuck, what can the candidate do to still give you a positive impression?』狗家面试官给的答案是『This candidate can clearly identity the block.』换句话说,当自己在面试中卡壳了,不要慌,毕竟真实工作中哪有人不需要查文档/不需要同事帮助呢?这时候比较推荐的做法是:『我这道题的思路是 abcd,但是我不知道怎么实现 c 这一步。』(注意,此处每一步应该尽可能的小、可实现。) 如果可以的话,尽可能给出多个解决问题的方案和他们的利弊 在我入职之后,我听很多经理都谈论过『能够给出多套方案并且权衡利弊』是他们衡量候选人重要的一个标志之一。在日常工作中,也经常有因为各种规定,而需要给出多套方案讨论的时候。例子:详细阐述思路的时候可以提到:『我现在有 2 个方案,方案一是 abcd,好处是…,坏处是…;方案二是 cdef,好处是…坏处是…』。 利用好提问环节,这里可以加塞给对方的称赞和肯定:D 一般技术面试的最后,面试官都会给一些可以提问的时间。如果不知道要问什么,推荐问跟对方公司、职业相关(但是尽量足够宽泛,不涉及到保密信息的),且答案非常有可能让对方联想起积极情绪的问题。什么是联想起积极情绪?比如『你最骄傲的项目是什么?』反面的例子比如『你在这份工作里最大的失误是什么?』,『你在这份工作中最绝望的时刻是什么时候?』,『你被老板骂哭过吗?为啥呢?』。 在这个时间里,其实面试者可以『夹带私货』。比如:『请问你们工作强度是怎么样的?』和『刚刚在面试的时候,我觉得你给我指出的几处错误特别厉害。我特别希望能跟你这样优秀的程序员在一起工作,所以我想问一下你们平时的工作强度是怎么样的?』 当然,这一部分如果夹带太多私货就会变得非常油腻,所以希望大家在给称赞的时候一定是真心的。 我最喜欢问的问题是『What can I do, for this position, to not only meet your expectation but also exceed it?』因为这个问题不仅暗示了我很想要争取这个职位,而且也试探了对方对应聘者的期待。我经常通过这个问题能收获到很多大佬们的指点,但是有一次差点『翻车』。在我问了这个问题之后,面试官跟我说『这个问题我看见过,是网上最推荐求职者问的问题之一吧。』(面试官在暗示我他识破了我的套路。)我回答『是的,但是这个问题是我从一长串问题中选出来的。我选它是因为这个问题能帮我进一步了解同事对一个新入职的程序员的期待,毕竟这是我的第一份程序员工作嘛。』(我没有否认/抵抗/狡辩,我承认了,并且真诚的给与了原因。)面试官满意的点了点头。 其实说了这么多,都是一些并非原创的、没有什么新意的技巧。在这些『技巧』的背后,最重要的是真诚。当一个面试者发自内心的喜欢这个岗位、喜欢这个公司,这份激动 (excitement) 和动机 (motivation) 往往是难以掩饰的,而这份热情肯定也会打动面试官的。 快速 Q & A Q: 怎么样寻找『积极情绪的』感觉? A: 多观察服务业的人员。在北美的朋友们可以看看地道的北美餐厅服务员、护士、房地产中介/manager。在国内的朋友们…要不点个特别会聊天的陪玩试试?(手动狗头保命,我是开玩笑的) Q: 什么是『正向情绪』? A:开心、高兴等让人感到愉悦和美好的情绪。 Q:什么是『负面情绪』? A:有防御性的、生气、不合作等。 Q:写代码的时候没办法看到面试官的表情怎么办? A:(答案可能只适用于北美的技术面试)我敲代码的时候也不看面试官的表情,因为确实很难分神。但是在技术面试的开头,就是开场和沟通的部分会尽量观察面试官的反馈。一般来说在讨论好大致思路之后真正写码的速度应该是很快的,所以真正写码的时间其实并不会特别多。 最后本人在这方面没有任何学术背景,如果说的不对,还请指正。本人不适用于所有人,仅仅是个人总结,希望能作为大家的参考。每个人实际情况不同,请选择适合自己的方式和方法。","categories":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"}],"tags":[{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"}],"author":{"name":"易潇","avatar":"https://tva1.sinaimg.cn/large/008i3skNly1gxzfkhprmpj30cs0csaar.jpg","url":"https://github.com/lilyzhaoyilu"}},{"title":"刷题插件可以隐藏测试用例啦","slug":"leetcode-cheat-hide-cases","date":"2021-12-21T16:00:00.000Z","updated":"2023-01-07T12:34:34.314Z","comments":true,"path":"2021/12/22/leetcode-cheat-hide-cases/","link":"","permalink":"https://lucifer.ren/blog/2021/12/22/leetcode-cheat-hide-cases/","excerpt":"一切都来源于一个 issue今日有粉丝提意想增加隐藏测试用例的功能。issue 地址:https://github.com/azl397985856/blog/issues/91 当天我就完成了这个功能的开发,并上架商店进行审核了。","text":"一切都来源于一个 issue今日有粉丝提意想增加隐藏测试用例的功能。issue 地址:https://github.com/azl397985856/blog/issues/91 当天我就完成了这个功能的开发,并上架商店进行审核了。 功能介绍简单介绍一个这个功能,有需要的同学可以更新一下最新的版本。 当我们提交力扣代码,并且无法通过时,力扣会直接显示出错误的测试用例以及期望的输出。 有些时候,我们不想立马看到错误的具体细节,而只想知道有错误就够了。这样可以起到很好的练习作用。试想你正在参加面试,面试官告诉你代码有问题,而不告诉你具体哪个 case 有问题,这是很正常的吧?因此锻炼自己的这种自己分析问题的能力也很有必要。 比如题目直接显示错误的用例输入是空字符,那你直接就知道自己忘记处理边界了。但是如果这个空字符串是自己想出来的,那么效果肯定比力扣告诉你要好。 于是我开发了这个提交错误后默认隐藏出错细节的功能。 只有你点击显示的时候才可以看到详细信息。避免你自己不想看,却被弹到脸上的尴尬。 值得注意的是这个功能目前默认开启,无法关闭。 如何使用大家首先要更新到 v0.10.0 及以上版本。其次找到力扣中的任意一道题,点击提交即可。如果提交出错即可看到西法给你的贴心提示啦! 插件的安装和使用方法请到我的公众号力扣加加回复插件获取 后话为了测试这个功能,我可以故意做错了很多题,满屏的红色好扎心。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"},{"name":"刷题","slug":"刷题","permalink":"https://lucifer.ren/blog/tags/刷题/"}]},{"title":"区间算法题用线段树可以秒解?","slug":"segment-tree","date":"2021-12-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.787Z","comments":true,"path":"2021/12/16/segment-tree/","link":"","permalink":"https://lucifer.ren/blog/2021/12/16/segment-tree/","excerpt":"背景给一个两个数组,其中一个数组是 A [1,2,3,4],另外一个数组是 B [5,6,7,8]。让你求两个数组合并后的大数组的: 最大值 最小值 总和 这题是不是很简单?我们直接可以很轻松地在 $O(m+n)$ 的时间解决,其中 m 和 n 分别为数组 A 和 B 的大小。 那如果我可以修改 A 和 B 的某些值,并且我要求很多次最大值,最小值和总和呢? 朴素的思路是原地修改数组,然后 $O(m+n)$ 的时间重新计算。显然这并没有利用之前计算好的结果,效率是不高的。 那有没有效率更高的做法? 有!线段树就可以解决。 ​","text":"背景给一个两个数组,其中一个数组是 A [1,2,3,4],另外一个数组是 B [5,6,7,8]。让你求两个数组合并后的大数组的: 最大值 最小值 总和 这题是不是很简单?我们直接可以很轻松地在 $O(m+n)$ 的时间解决,其中 m 和 n 分别为数组 A 和 B 的大小。 那如果我可以修改 A 和 B 的某些值,并且我要求很多次最大值,最小值和总和呢? 朴素的思路是原地修改数组,然后 $O(m+n)$ 的时间重新计算。显然这并没有利用之前计算好的结果,效率是不高的。 那有没有效率更高的做法? 有!线段树就可以解决。 ​ 线段树是什么线段树本质上就是一棵树。更准确地说,它是一颗二叉树,而且它是一颗平衡二叉树。关于为什么是平衡二叉树,我们后面会讲,这里大家先有这样一个认识。 虽然是一棵二叉树,但是线段树我们通常使用数组来模拟树结构,而不是传统的定义 TreeNode 。 一方面是因为实现起来容易,另外一方面是因为线段树其实是一颗完全二叉树,因此使用数组直接模拟会很高效。这里的原因我已经在之前写的堆专题中的二叉堆实现的时候中讲过了,大家可以在我的公众号《力扣加加》回复堆获取。 线段树解决什么问题正如它的名字,线段树和线段(区间)有关。线段树的每一个树节点其实都存储了一个区间(段)的信息。然后这些区间的信息如果满足一定的性质就可以用线段树来提高性能。 那: 究竟是什么样的性质? 如何提高的性能呢? 究竟是什么样的性质?比如前面我们提到的最大值,最小值以及求和就满足这个一定性质。即我可以根据若干个(这里是两个)子集推导出子集的并集的某一指标。 以上面的例子来说,我们可以将数组 A 和 数组 B 看成两个集合。那么:集合 A 的最大值和集合 B 的最大值已知,我们可以直接通过 max(Amax, Bmax) 求得集合 A 与集合 B 的并集的最大值。其中 Amax 和 Bmax 分别为集合 A 和集合 B 的最大值。最小值和总和也是一样的,不再赘述。因此如果统计信息满足这种性质,我们就可以可以使用线段树。但是要不要使用,还是要看用了线段树后,是否能提高性能。 如何提高的性能呢?关于提高性能,我先卖一个关子,等后面讲完实现的时候,我们再聊。 线段树实现以文章开头的求和为例。 我们可以将区间 A 和 区间 B 分别作为一个树的左右节点,并将 A 的区间和与 B 的区间和分别存储到左右子节点中。 接下来,将 A 的区间分为左右两部分,同理 B 也分为左右两部分。不断执行此过程直到无法继续分。 总结一下就是将区间不断一分为二,并将区间信息分别存储到左右节点。如果是求和,那么区间信息就是区间的和。这个时候的线段树大概是这样的: 蓝色字体表示的区间和。 注意,这棵树的所有叶子节点一共有 n 个(n 为原数组长度),并且每一个都对应到原数组某一个值。 体现到代码上也很容易。 直接使用后续遍历即可解决。这是因为,我们需要知道左右节点的统计信息,才能计算出当前节点的统计信息。 不熟悉后序遍历的可以看下我之前的树专题,公众号力扣加加回复树即可获取 和二叉堆的表示方式一样,我们可以用数组表示树,用 2 * i + 1 和 2 * i + 2 来表示左右节点的索引,其中 i 为当前节点对应在 tree 上的索引。 tree 是用来构建线段树的数组,和二叉堆类似。只不过 tree[i] 目前存的是区间信息罢了。 上面我描述建树的时候有明显的递归性,因此我们可以递归的建树。具体来说,可以定义一个 build(tree_index, l, r) 方法 来建树。其中 l 和 r 就是对应区间的左右端点,这样 l 和 r 就可以唯一确定一个区间。 tree_index 其实是用来标记当前的区间信息应该被更新到 tree 数组的哪个位置。 我们在 tree 上存储区间信息,那么最终就可以用 tree[tree_index] = …. 来更新区间信息啦。 核心代码: 12345678910111213141516def build(self, tree_index:int, l:int, r:int): ''' 递归创建线段树 tree_index : 线段树节点在数组中位置 l, r : 该节点表示的区间的左,右边界 ''' if l == r: self.tree[tree_index] = self.data[l] return mid = (l+r) // 2 # 区间中点,对应左孩子区间结束,右孩子区间开头 left, right = 2 * tree_index + 1, 2 * tree_index + 2 # tree_index 的左右子树索引 self.build(left, l, mid) self.build(right, mid+1, r) # 典型的后序遍历 # 区间和使用加法即可,如果不是区间和要改下面这行代码 self.tree[tree_index] = self.tree[left] + self.tree[right] 上面代码的数组 self.tree[i] 其实就是用来存类似上图中蓝色字体的区间和。每一个区间都在 tree 上存有它一个位置,存它的区间和。 复杂度分析 时间复杂度:由递推关系式 T(n) = 2*T(n/2) + 1,因此时间复杂度为 $O(n)$ 不知道怎么得出的 $O(n)$? 可以看下我的《算法通关之路》的第一章内容。 https://leetcode-solution.cn/book-intro 空间复杂度:tree 的大小和 n 同阶,因此空间复杂度为 $O(n)$ 终于把树建好了,但是知道现在一点都没有高效起来。我们要做的是高效处理频繁更新情况下的区间查询。 那基于这种线段树的方法,如果更新和查询区间信息如何做呢? 区间查询先回答简单的问题区间查询原理是什么。 如果查询一个区间的信息。这里也是使用后序遍历就 ok 了。比如我要找一个区间 [l,r] 的区间和。 那么如果当前左节点: 完整地落在 [l,r] 内。比如 [2,3] 完整地落在 [1,4] 内。 我们直接将 tree 中左节点对于的区间和取出来备用,不妨极为 lsum。 部分落在 [l,r] 内。比如 [1,3] 部分落在 [2,4]。这个时候我们继续递归,直到完整地落在区间内(上面的那种情况),这个时候我们直接将 tree 中左节点对于的区间和取出来备用 将前面所有取出来备用的值加起来就是答案 右节点的处理也是一样的,不再赘述。 复杂度分析 时间复杂度:查询不需要在每个时刻都处理两个叶子节点,实际上处理的次数大致和树的高度一致。而树是平衡的,因此复杂度为 $O(logn)$ 或者由递推关系式 T(n) = T(n/2) + 1,因此时间复杂度为 $O(logn)$ 不知道怎么得出的 $O(logn)$? 可以看下我的《算法通关之路》的第一章内容。 https://leetcode-solution.cn/book-intro 大家可以结合后面的代码理解这个复杂度。 区间修改那么如果我修改了 A[1] 为 1 呢? 如果不修改 tree,那么显然查询的区间只要包含了 A[1] 就一定是错的,比如查询区间 [1,3] 的和 就会得到错误的答案。因此我们要在修改了 A[1] 的时候同时去修改 tree。 问题在于我们要修改哪些 tree 的值,修改为多少呢? 首先回答第一个问题,修改哪些 tree 的值呢? 我们知道,线段树的叶子节点都是原数组上的值,也是说,线段树的 n 个叶子节点对应的就是原数组。因此我们首先要找到我们修改的位置对应的那个叶子节点,将其值修改掉。 这就完了么? 没有完。实际上,我们修改的叶子节点的所有父节点以及祖父节点(如果有的话)都需要改。也就是说我们需要从这个叶子节点不断冒泡到根节点,并修改沿途的区间信息 这个过程和浏览器的事件模型是类似的 接下来回答最后一个问题,具体修改为多少? 对于求和,我们需要首先将叶子节点改为修改后的值,另外所有叶子节点到根节点路径上的点的区间和都加上 delta,其中 delta 就是改变前后的差值。 求最大最小值如何更新?大家自己思考一下。 修改哪些节点,修改为多少的问题都解决了,那么代码实现就容易了。 复杂度分析 时间复杂度:修改不需要在每个时刻都处理两个叶子节点,实际上处理的次数大致和树的高度一致。而树是平衡的,因此复杂度为 $O(logn)$ 或者由递推关系式 T(n) = T(n/2) + 1,因此时间复杂度为 $O(logn)$ 不知道怎么得出的 $O(logn)$? 可以看下我的《算法通关之路》的第一章内容。 https://leetcode-solution.cn/book-intro 大家可以结合后面的代码理解这个复杂度。 线段树模板线段树代码已经放在刷题插件上了,公众号《力扣加加》回复插件即可获取。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485class SegmentTree: def __init__(self, data:List[int]): ''' data: 传入的数组 ''' self.data = data self.n = len(data) # 申请 4 倍 data 长度的空间来存线段树节点 self.tree = [None] * (4 * self.n) # 索引 i 的左孩子索引为 2i+1,右孩子为 2i+2 if self.n: self.build(0, 0, self.n-1) # 本质就是一个自底向上的更新过程 # 因此可以使用后序遍历,即在函数返回的时候更新父节点。 # index 是原数组索引 # tree_index 是对应我们构造的二叉树数组的索引 def update(self, tree_index, l, r, index): ''' tree_index: 某个根节点索引 l, r : 此根节点代表区间的左右边界 index : 更新的值的索引 ''' if l == r==index : self.tree[tree_index] = self.data[index] return mid = (l+r)//2 left, right = 2 * tree_index + 1, 2 * tree_index + 2 if index > mid: # 要更新的区间在右子树 self.update(right, mid+1, r, index) else: # 要更新的区间在左子树 index<=mid self.update(left, l, mid, index) # 查询区间一部分在左子树一部分在右子树 # 区间和使用加法即可,如果不是区间和要改下面这行代码 self.tree[tree_index] = self.tree[left] + self.tree[right] def updateSum(self,index:int,value:int): self.data[index] = value self.update(0, 0, self.n-1, index) def query(self, tree_index:int, l:int, r:int, ql:int, qr:int) -> int: ''' 递归查询区间 [ql,..,qr] 的值 tree_index : 某个根节点的索引 l, r : 该节点表示的区间的左右边界 ql, qr: 待查询区间的左右边界 ''' if l == ql and r == qr: return self.tree[tree_index] # 区间中点,对应左孩子区间结束,右孩子区间开头 mid = (l+r) // 2 left, right = tree_index * 2 + 1, tree_index * 2 + 2 if qr <= mid: # 查询区间全在左子树 return self.query(left, l, mid, ql, qr) elif ql > mid: # 查询区间全在右子树 return self.query(right, mid+1, r, ql, qr) # 查询区间一部分在左子树一部分在右子树 # 区间和使用加法即可,如果不是区间和要改下面这行代码 return self.query(left, l, mid, ql, mid) + self.query(right, mid+1, r, mid+1, qr) def querySum(self, ql:int, qr:int) -> int: ''' 返回区间 [ql,..,qr] 的和 ''' return self.query(0, 0, self.n-1, ql, qr) def build(self, tree_index:int, l:int, r:int): ''' 递归创建线段树 tree_index : 线段树节点在数组中位置 l, r : 该节点表示的区间的左,右边界 ''' if l == r: self.tree[tree_index] = self.data[l] return mid = (l+r) // 2 # 区间中点,对应左孩子区间结束,右孩子区间开头 left, right = 2 * tree_index + 1, 2 * tree_index + 2 # tree_index 的左右子树索引 self.build(left, l, mid) self.build(right, mid+1, r) # 区间和使用加法即可,如果不是区间和要改下面这行代码 self.tree[tree_index] = self.tree[left] + self.tree[right] 使用的方式很简单: 初始化 SegmentTree 并直接传入一个你想计算指标的数组即可。 如何更新原数组的某一项? 只要调用 updateSum(index, value) 即可,其中 index 和 value 为你想修改的原数组的索引和值。 如何查询原数组的区间和?只要调用 querySum(ql, qr) 即可,其中 ql 和 qr 为你想查询的区间的左右端点。 相关专题 堆 大家可以看下我之前写的堆的专题的二叉堆实现。然后对比学习,顺便还学了堆,岂不美哉? 树状数组 树状数组和线段树类似,难度比线段树稍微高一点点。有机会给大家写一篇树状数组的文章。 immutablejs 前端的小伙伴应该知道 immutable 吧? 而 immutablejs 就是非常有名的实现 immutable 的工具库。西法之前写过一篇 immutable 原理解析文章,感兴趣的可以看下 https://lucifer.ren/blog/2020/06/13/immutable-js/ 回答前面的问题为啥是平衡二叉树?前面的时间复杂度其实也是基于树是平衡二叉树这一前提。那么线段树一定是平衡二叉树么?是的。这是因为线段树是完全二叉树,而完全二叉树是平衡的。 当然还有另外一个前提,那就是树的总的节点数和原数组长度同阶,也就是 n 的量级。关于为啥是同阶的,也容易计算,只需要根据递归公式即可得出。 为啥线段树能提高性能?只要你理解了我实现部分的时间复杂度,那么就不难明白这个问题。因为修改和查询的时间复杂度都是 $logn$,而不使用线段树的暴力做法查询的复杂度高达 $O(n)$。相应的代价就是建树的 $O(n)$ 的空间,因此线段树也是一种典型的空间换时间算法。 最后点一下题。区间算法题是否可以用线段树秒解?这其实文章中已经回答过了,其取决于是否满足两点: 是否可以根据若干个(这里是两个)子集推导出子集的并集的某一指标。 是否能提高性能(相比于朴素的暴力解法)。通常面临频繁查询或者修改的场景都可以考虑使用线段树优化修改后的查询时间消耗。","categories":[{"name":"线段树","slug":"线段树","permalink":"https://lucifer.ren/blog/categories/线段树/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"线段树","slug":"线段树","permalink":"https://lucifer.ren/blog/tags/线段树/"}]},{"title":"腾讯校招还考抛物线?","slug":"tencent-2021","date":"2021-12-12T16:00:00.000Z","updated":"2023-01-07T12:34:34.411Z","comments":true,"path":"2021/12/13/tencent-2021/","link":"","permalink":"https://lucifer.ren/blog/2021/12/13/tencent-2021/","excerpt":"昨天翻了一下牛客网的腾讯 2021 校园招聘技术类编程题汇总,一共有五道题目。难度大不大?我们来看下! 具体代码我就不贴了,大家如果看了我的思路还是写不出来可以来我的校招群来问,大家可以在公众号后台回复校招进群。 题目地址:https://www.nowcoder.com/test/30545524/summary","text":"昨天翻了一下牛客网的腾讯 2021 校园招聘技术类编程题汇总,一共有五道题目。难度大不大?我们来看下! 具体代码我就不贴了,大家如果看了我的思路还是写不出来可以来我的校招群来问,大家可以在公众号后台回复校招进群。 题目地址:https://www.nowcoder.com/test/30545524/summary 第一题题目描述现在有 $10^5$ 个用户,编号为 1-$10^5$,现在已知有 m 对关系,每一对关系给你两个数 x 和 y ,代表编号为 x 的用户和编号为 y 的用户是在一个圈子中,例如: A 和 B 在一个圈子中, B 和 C 在一个圈子中,那么 A , B , C 就在一个圈子中。现在想知道最多的一个圈子内有多少个用户。 思路我们可以将用户抽象为点,用户的关系抽象为边。那么问题转化为最大联通子图。我们可以使用并查集来解决,直接套用下我的并查集模板即可解决。模板可以在我的刷题插件中找到,插件说明:https://github.com/azl397985856/leetcode-cheat 第二题题目描述输入一个字符串 s,s 由小写英文字母组成,保证 s 长度小于等于 5000 并且大于等于 1。在 s 的所有不同的子串中,输出字典序第 k 小的字符串。字符串中任意个连续的字符组成的子序列称为该字符串的子串。字母序表示英文单词在字典中的先后顺序,即先比较第一个字母,若第一个字母相同,则比较第二个字母的字典序,依次类推,则可比较出该字符串的字典序大小。 思路我们可以枚举所有的子串,这需要 $n^2$的时间复杂度。 接下来,我们排序取 第 k 个,或者使用堆来取第 k 个都是可以的。 有一点需要注意: 由于子串可能重复,因此我们需要去重。以题目中的 aabb 来说, a 这个子串其实有两个,但是算答案的时候仅仅算一个。 第三题题目描述 思路。。。 非常郁闷。为什么会考这种题? 这道题可以用微积分的知识来解。具体来说,我们可以根据 A,B,C 的值求出交点(两个交点),然后对 y 做积分,求出定积分的值即为所求的面积。 91 天学算法群里的数学大佬 @空识 给了一个计算方法,大家可以参考一下。 第四题题目描述数据结构基础之一——队列队列有五种基本操作,插入队尾、取出队首、删除队首、队列大小、清空队列。 现在让你模拟一个队列的操作,具体格式参考输入。 注意本题有多组输入。 思路由于时间复杂度需要 $O(1)$,因此无法使用数组来模拟,我们需要使用链表来模拟。代码可以参考一下双端队列的源码。 如果大家实现有困难,可以先尝试使用数组来模拟,然后改为链表。由于链表和数组其实都是线性数据结构,只是具体 api 不一样,因此改起来只要掌握方法很容易。 第五题题目描述界面中存在 id=jsContainer 的节点 A,系统会随机生成 id 为 jsLayout 的 m 行 x n 列 表格(m >= 3, n >= 3),并随机选中一个 td 节点,请按照如下需求实现 bind 函数1、bind 函数为每个 td 节点绑定 click 事件,当某个 td 节点被点击时 class 变为 current,同时以该 td 为中心的同一行和同一列 td 节点 class 变为 wrap,具体效果参考以下图片2、每次 click 后,请清空所有不需要变动的 td 节点的 class3、请不要手动调用 bind 函数4、当前界面为系统生成 $9 * 9$ 表格,执行 bind 函数,并点击其中 td 后的效果5、请不要手动修改 html 和 css6、不要使用第三方插件 题目预设代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105<div id=\"jsContainer\"> <table class=\"game\"> <tbody> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"current\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> <td class=\"wrap\"></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td class=\"wrap\"></td> <td></td> <td></td> <td></td> <td></td> </tr> </tbody> </table></div> 123456789101112131415161718table.game { font-size: 14px; border-collapse: collapse; width: 100%; table-layout: fixed;}table.game td { border: 1px solid #e1e1e1; padding: 0; height: 30px; text-align: center;}table.game td.current { background: #1890ff;}table.game td.wrap { background: #f5f5f5;} 1function bind() {} 思路这是一个前端题目。 由于只可以修改 js,因此我们可以: 使用事件代理在最外层绑定 click 事件 click 处理函数判断 event target 是第几行,第几列,从而给格子添加不同 class。比如 event target 是第 x 行,第 y 列,那么只需要给 x 行 y 列设置 current,给 x 行 其他 以及 y 列 其他 设置 wrap,最后清空其他单元格 class 即可。 总结五道题难度除了积分求面试这道题有点不太常规之外,其他都是很常见的题目,难度也不大。 第一题是常规的并查集的题目,基本属于最简单的并查集了。 第二题纯暴力其实也可以解决。 第三题,emmmm 第四题,实现一个队列。很常规的一个题目了,唯一需要注意的是使用链表来模拟。 第五题,是一个前端题目。考察事件代理,以及基本的 dom 操作。","categories":[{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"}],"tags":[{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第六期)","slug":"91algo-6","date":"2021-12-02T16:00:00.000Z","updated":"2023-02-07T04:15:30.326Z","comments":true,"path":"2021/12/03/91algo-6/","link":"","permalink":"https://lucifer.ren/blog/2021/12/03/91algo-6/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第六期和往期的不同。 基础篇增加排序专题,该专题为自习专题。进阶篇增加线段树专题,该专题目前暂定为自习,后面根据当前面试中考察频率决定是否改为非自习。 自习指的是不给每日一题的做题时间,需要自己找时间学习和练习。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 丰富多语言,给大家更流畅的阅读体验。大部分题解都提供了多种语言,包括 Python,Java,CPP 和 JS。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-12-12 至 2022-03-12 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 先导篇(自习) 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 排序(自习) 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 线段树(自习) 高频面试题(自习) 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 第六期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 本期有共五位讲师,每个专题由一位具体的讲师负责,大家有不会的问题可以进行提问。如果讲师来不及回答,大家可以在仓库中提 issue。 关于每一个专题的负责讲义,我们会在 91 官网中的“讲师”模块给出,如上图所示。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡一张哦 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 购买过《算法通关之路》且之前没有参与《算法通关之路》免费参与活动(具体活动介绍见后面的购书免费参与) 已经付费 则可进群填写一个表单,接下来只需要等待即可,一般一天以内就可以访问我们的网站了。 购书免费参与本期不再提供分享返现的活动,转而提供购书免费参与的活动。 如果你购买了《算法通关之路》,可以凭借好评截图找我(我的微信号 DevelopeEngineer)免费报名参与一期哦。注意一本书只能参与一次哦~ 实体版购书链接 电子版购书链接 在线试读 FAQ Q:活动结束后可以回看讲义资料么? A:活动结束后会提供讲义的电子书版本,大家可以通过电子书回看所有的讲义以及官方题解。你自己和其他人的题解可以在公开仓库找到。 Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第六期和前五内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。第五期增加了模拟和枚举的题目,删除了高频面试题的题目。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"今年秋招大家都在讨论的话题,薪资只排第3","slug":"qiuzhao2021","date":"2021-11-27T16:00:00.000Z","updated":"2023-01-07T12:34:34.407Z","comments":true,"path":"2021/11/28/qiuzhao2021/","link":"","permalink":"https://lucifer.ren/blog/2021/11/28/qiuzhao2021/","excerpt":"有人发布了知乎统计数据,时间范围是2021年6月到10月,话题是秋招。下面我们来看下这些数据。","text":"有人发布了知乎统计数据,时间范围是2021年6月到10月,话题是秋招。下面我们来看下这些数据。 秋招概况今天看了一眼知乎的关键词统计报告,被提及最多的关键词中薪资排第三。那么前两位是什么? 其实第二个和薪资也是紧密相关的。第二就是前段时间传的大家热议的腾讯校招 40w 白菜年薪事件。这个我当时也蛮震惊的,心想不知道又有多少老员工被倒挂了。别说老员工,就算是前段时间刚刚接了 offer 的朋友都给我反馈说感觉自己被倒挂。 其实不仅仅是腾讯这样的大型互联网公司,很多公司(比如一些传统银行,物流行业公司)年薪普遍变得很高,反正比我当时高多了。只不过互联网公司显得尤为严重。 另外腾讯还公布了 养老制度,平均月薪8w+ 等新闻,可谓风靡一时。另外腾讯在所有公司中被提及次数最多,排名前三的分别是腾讯,字节和阿里。 据我说知,快手给的薪资也是异常的高,高到无法让人拒绝😄。另外还有一些没有那么 算法面试如何?很奇怪关键字竟然没有如何准备面试? 后来看到浏览量 top10 问题终于看到了,原来在这里。 排名第五的问题就是互联网公司常见算法面试题,我顺便搜了一下这个知乎的问题。发现是一个很老的问题了,每年春招秋招对会被拉出来“鞭尸”。排名靠前的还是那几个熟悉的面孔。 说实话,西法一点都不建议大家看这些答案。你想啊,靠前的大多数都是老答案,而这些题目有些都已经过时了,大家都不太考了。而如果你按照时间排序,有参考价值的回答有几个?所以我不推荐大家去刷这些知乎回答。 记得之前我搞的《91天学算法》活动里面也有高频面试题板块,后来在第五期被我取消了。我觉得这个题目的意义不大,有着时间倒不如刷刷你心仪公司的面经中的题目。当然很多公司有保密制度,不允许公开这些题目。因此去一些相对不那么公开的场合就显得很重要了。比如私聊,交流群或者一些知识星球之类的地方。顺便一题,我的群里就是不是有人拿一些网上的面经问答案,大家交流交流效果甚好。当然我强烈建议大家先讲讲自己的思路,以及做过什么样的尝试。不要做伸手党! 关于面经,我更推荐大家去牛客,一亩三分地这样的社区看,然后和其他同学交流题 解,这样的效果往往更好。很多拿了 sp 甚至 ssp 的同学都是这样的套路。 最后如果大家在准备下一界秋招,也可以加入我的交流群或者直接报名参与《91天学算法》。 交流群在我的公众号力扣加加回复 leetcode 加群,《91天学算法》回复 91 即可。 《91天学算法》十二月中旬结束,届时可能会开启第六期,感兴趣的可以蹲一波,说不定还会有买《算法通关之路》免费参与的活动哦~,图书试读以及介绍:https://leetcode-solution.cn/book-intro","categories":[],"tags":[{"name":"秋招","slug":"秋招","permalink":"https://lucifer.ren/blog/tags/秋招/"}]},{"title":"或许是一本可以彻底改变你刷 LeetCode 效率的题解书","slug":"leetcode-book.intro","date":"2021-11-19T16:00:00.000Z","updated":"2023-02-07T04:16:09.194Z","comments":true,"path":"2021/11/20/leetcode-book.intro/","link":"","permalink":"https://lucifer.ren/blog/2021/11/20/leetcode-book.intro/","excerpt":"经过了半年时间打磨,投入诸多人力,这本 LeetCode 题解书终于要和大家见面了。💐💐💐💐💐。 实体版购书链接:https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAN4JK1olXwUFU1xcAUoRA18IGFMXXgQDUG4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYBXFxeCkoTHDZNRwYlQ1J3BB0EWEl0QhkIH1xMBXBlDyQ1TkcbM244G1oUXQ4HU1tbDXsnA2g4STXN67Da8e9B3OGY1uefK1olXQEEUFhYCkgSAWwOHmsSXQ8yDwszD0sSUDtbGAlCDVJVAW5tOEgnBG8BD11nHFQWUixtOEsnAF9KdV5AWQcDB1cPDktEAWpfSwhFXwUDUllVDkMVATxbHVwWbQQDVVpUOHs 电子版购书链接:https://union-click.jd.com/jdc?e=&p=JF8BAL0JK1olXDYAVVhfD04UAl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFkkWBW0PHlgUQl9HCANtcS0SdTFvWVt1X3BkVV4Kc0JxYRtPe1cZbQcyVF9cCEMSBGoOHmslXQEyHzBcOEonA2gKE1oVWwEKXV5cAXsQA2Y4QA57WgYHBwoOCxlAUztfTmslbQUyZG5dOEgnQQFaSQ5FWQYFB1cODhgSVDpaS1hFDwQLUlwJAU5DAWcJHWsXXAcGXW4 在线试读","text":"经过了半年时间打磨,投入诸多人力,这本 LeetCode 题解书终于要和大家见面了。💐💐💐💐💐。 实体版购书链接:https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAN4JK1olXwUFU1xcAUoRA18IGFMXXgQDUG4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYBXFxeCkoTHDZNRwYlQ1J3BB0EWEl0QhkIH1xMBXBlDyQ1TkcbM244G1oUXQ4HU1tbDXsnA2g4STXN67Da8e9B3OGY1uefK1olXQEEUFhYCkgSAWwOHmsSXQ8yDwszD0sSUDtbGAlCDVJVAW5tOEgnBG8BD11nHFQWUixtOEsnAF9KdV5AWQcDB1cPDktEAWpfSwhFXwUDUllVDkMVATxbHVwWbQQDVVpUOHs 电子版购书链接:https://union-click.jd.com/jdc?e=&p=JF8BAL0JK1olXDYAVVhfD04UAl9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFkkWBW0PHlgUQl9HCANtcS0SdTFvWVt1X3BkVV4Kc0JxYRtPe1cZbQcyVF9cCEMSBGoOHmslXQEyHzBcOEonA2gKE1oVWwEKXV5cAXsQA2Y4QA57WgYHBwoOCxlAUztfTmslbQUyZG5dOEgnQQFaSQ5FWQYFB1cODhgSVDpaS1hFDwQLUlwJAU5DAWcJHWsXXAcGXW4 在线试读 背景自 LeetCode 题解 (现在已经接近 45k star 了)项目被大家开始关注,就有不少出版社开始联系我写书。刚开始后的时候,我并没有这个打算,觉得写这个相对于博客形式的题解要耗费时间,且并不一定效果比博客形式的效果好。后来当我向大家提及“出版社找我写书”这件事情的时候,很多人表示“想要买书,于是我就开始打算写这样一本书。但是一个完全没有写书经验的人,独立完成一本书工作量还是蛮大的,因此我打算寻求其他志同道合人士的帮助。 团队介绍团队成员大都来自 985, 211 学校计算机系,大家经常参加算法竞赛,也坚持参加 LeetCode 周赛。在这个过程中,我们积累了很多经验,希望将这些经验分享给大家,以减少大家在刷题过程中的阻碍,让大家更有效率的刷题。 本书尤其适合那些刚刚开始刷题的人,如果你刚开始刷题,或者刷了很多题面对新题还是无法很好的解决,那么这本书肯定很适合你。最后欢迎大家加入我们的读者群和作者进行交流。 作者 - xing 作者 - lucifer 作者 - BY 作者 - fanlu 作者 - lazybing 样张这里给大家开放部分章节内容给大家,让大家尝尝鲜。当然也欢迎大家提出宝贵的建议,帮助我们写出更好的内容。 我们开放了第八章第五小节给大家看,以下是具体内容: 8.5 1206. 设计跳表题目描述不使用任何库函数,设计一个跳表。 跳表是在 $O(logN)$ 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。 跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 $O(N)$。跳表的每一个操作的平均时间复杂度是 $O(logN)$,空间复杂度是 $O(N)$。 在本题中,你的设计应该要包含这些函数: bool search(int target) : 返回 target 是否存在于跳表中。 void add(int num): 插入一个元素到跳表。 bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回 false. 如果存在多个 num ,删除其中任意一个即可。 注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。 样例: 1234567891011Skiplist skiplist = new Skiplist();skiplist.add(1);skiplist.add(2);skiplist.add(3);skiplist.search(0); // 返回 falseskiplist.add(4);skiplist.search(1); // 返回 trueskiplist.erase(0); // 返回 false,0 不在跳表中skiplist.erase(1); // 返回 trueskiplist.search(1); // 返回 false,1 已被擦除 约束条件:0 <= num, target <= 20000最多调用 50000 次 search, add, 以及 erase 操作。 思路首先,使用跳表会将数据存储成有序的。在数据结构当中,我们通常有两种基本的线性结构,结合有序数据,表达如下: 有序链表,我们有三种基本操作: 查找指定的数据:时间复杂度为 $O(N)$, $N$ 为数据位于链表的位置。 插入指定的数据:时间复杂度为 $O(N)$, $N$ 为数据位于链表的位置。因为插入数据之前,需要先查找到可以插入的位置。 删除指定的数据:时间复杂度为 $O(N)$, $N$ 为数据位于链表的位置。因为删除数据之前,需要先查找到可以插入的位置。 有序数组: 查找指定的数据:如果使用二分查找,时间复杂度为 $O(logN)$, $N$ 为数据的个数。 插入指定的数据:时间复杂度为 $O(N)$, 因为数组是顺序存储,插入新的数据时,我们需要向后移动指定位置后面的数据,这里 $N$ 为数据的个数。 删除指定的数据:时间复杂度为 $O(N)$, 因为数组是顺序存储,删除数据时,我们需要向前移动指定位置后面的数据,这里 $N$ 为数据的个数。 而神奇的跳表能够在 $O(logN)$ 时间内完成增加、删除、搜索操作。下面我们分别分析增加、删除和搜索这 3 个三个基本操作。 跳表的查找现在我们通过一个简单的例子来描述跳表是如何实现的。假设我们有一个有序链表如下图:原始方法中,查找的时间复杂度为 $O(N)$。那么如何来提高链表的查询效率呢?如下图所示,我们可以从原始链表中每两个元素抽出来一个元素,加上一级索引,并且一级索引指向原始链表:如果我们想要查找 9 ,在原始链表中查找路径是 1->3->4->7->9, 而在添加了一级索引的查找路径是 1->4->9,很明显,查找效率提升了。按照这样的思路,我们在第 1 级索引上再加第 2 级索引,再加第 3 级索引,以此类推,这样在数据量非常大的时候,使得我们查找数据的时间复杂度为 $O(logN)$。这就是跳表的思想,也就是我们通常所说的“空间换时间”。 跳表的插入跳表插入数据看起来很简单,我们需要保持数据有序,因此,第一步我们需要像查找元素一样,找到新元素应该插入的位置,然后再插入。 但是这样会存在一个问题,如果我们一直往原始链表中插入数据,但是不更新索引,那么会导致两个索引结点之间的数据非常多,在极端情况下,跳表会退化成单链表,从而导致查找效率由 $O(logN)$ 退化为 $O(N)$。因此,我们需要在插入数据的同时,增加相应的索引或者重建索引。 方案 1:每次插入数据后,将跳表的索引全部删除后重建,我们知道索引的结点个数为 $N$(在空间复杂度分析时会有明确的数学推导),那么每次重建索引,重建的时间复杂度至少是 $O(N)$ 级别,很明显不可取。 方案 2:通过随机性来维护索引。假设跳表的每一层的提升概率为 $\\frac{1}{2}$ ,最理想的情况就是每两个元素提升一个元素做索引。而通常意义上,只要元素的数量足够多,且抽取足够随机的话,我们得到的索引将会是比较均匀的。尽管不是每两个抽取一个,但是对于查找效率来讲,影响并不很大。我们要知道,设计良好的数据结构往往都是用来应对大数据量的场景的。因此,我们这样维护索引:随机抽取 $\\frac{N}{2}$ 个元素作为 1 级索引,随机抽取 $\\frac{N}{4}$ 作为 2 级索引,以此类推,一直到最顶层索引。 那么具体代码该如何实现,才能够让跳表在每次插入新元素时,尽量让该元素有 $\\frac{1}{2}$ 的概率建立一级索引、$\\frac{1}{4}$ 的概率建立二级索引、$\\frac{1}{8}$ 的概率建立三级索引,以此类推。因此,我们需要一个概率算法。 在通常的跳表实现当中,我们会设计一个 randomLevel() 方法,该方法会随机生成 1~MAX_LEVEL 之间的数 (MAX_LEVEL 表示索引的最高层数) randomLevel() 方法返回 1 表示当前插入的元素不需要建立索引,只需要存储数据到原始链表即可(概率 1/2) randomLevel() 方法返回 2 表示当前插入的元素需要建立一级索引(概率 1/4) randomLevel() 方法返回 3 表示当前插入的元素需要建立二级索引(概率 1/8) randomLevel() 方法返回 4 表示当前插入的元素需要建立三级索引(概率 1/16) …… 可能有的同学会有疑问,我们需要一级索引中元素的个数时原始链表的一半,但是我们 randomLevel() 方法返回 2(建立一级索引)的概率是 $\\frac{1}{4}$, 这样是不是有问题呢?实际上,只要randomLevel()方法返回的数大于 1,我们都会建立一级索引,而返回值为 1 的概率是 $\\frac{1}{2}$。所以,建立一级索引的概率其实是$1- \\frac{1}{2} = \\frac{1}{2}$。同上,当 randomLevel() 方法返回值 >2 时,我们会建立二级或二级以上的索引,都会在二级索引中添加元素。而在二级索引中添加元素的概率是 $1- \\frac{1}{2} - \\frac{1}{4} = \\frac{1}{4}$。以此类推,我们推导出 randomLevel() 符合我们的设计要求。 下面我们通过仿照 redis zset.c 的 randomLevel 的代码: 12345678### 1. SKIPLIST_P 为提升的概率,本案例中我们设置为 1/2, 如果我们想要节省空间利用效率,可以适当的降低该值,从而减少索引元素个数。在 redis 中 SKIPLIST_P 被设定为 0.25。# 2. redis 中通过使用位运算来提升浮点数比较的效率,在本案例中被简化def randomLevel(): level = 1 while random() < SKIPLIST_P and level < MAX_LEVEL: level += 1 return level 跳表的删除跳表的删除相对来讲稍微简单一些。我们在删除数据的同时,需要删除对应的索引结点。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071from typing import Optionalimport randomclass ListNode: def __init__(self, data: Optional[int] = None): self._data = data # 链表结点的数据域,可以为空(目的是方便创建头节点) self._forwards = [] # 存储各个索引层级中该结点的后驱索引结点class Skiplist: _MAX_LEVEL = 16 # 允许的最大索引高度,该值根据实际需求设置 def __init__(self): self._level_count = 1 # 初始化当前层级为 1 self._head = ListNode() self._head._forwards = [None] * self._MAX_LEVEL def search(self, target: int) -> bool: p = self._head for i in range(self._level_count - 1, -1, -1): # 从最高索引层级不断搜索,如果当前层级没有,则下沉到低一级的层级 while p._forwards[i] and p._forwards[i]._data < target: p = p._forwards[i] if p._forwards[0] and p._forwards[0]._data == target: return True return False def add(self, num: int) -> None: level = self._random_level() # 随机生成索引层级 if self._level_count < level: # 如果当前层级小于 level, 则更新当前最高层级 self._level_count = level new_node = ListNode(num) # 生成新结点 new_node._forwards = [None] * level update = [self._head] * self._level_count # 用来保存各个索引层级插入的位置,也就是新结点的前驱结点 p = self._head for i in range(self._level_count - 1, -1, -1): # 整段代码获取新插入结点在各个索引层级的前驱节点,需要注意的是这里是使用的当前最高层级来循环。 while p._forwards[i] and p._forwards[i]._data < num: p = p._forwards[i] update[i] = p for i in range(level): # 更新需要更新的各个索引层级 new_node._forwards[i] = update[i]._forwards[i] update[i]._forwards[i] = new_node def erase(self, num: int) -> bool: update = [None] * self._level_count p = self._head for i in range(self._level_count - 1, -1, -1): while p._forwards[i] and p._forwards[i]._data < num: p = p._forwards[i] update[i] = p if p._forwards[0] and p._forwards[0]._data == num: for i in range(self._level_count - 1, -1, -1): if update[i]._forwards[i] and update[i]._forwards[i]._data == num: update[i]._forwards[i] = update[i]._forwards[i]._forwards[i] return True while self._level_count > 1 and not self._head._forwards[self._level_count]: self._level_count -= 1 return False def _random_level(self, p: float = 0.5) -> int: level = 1 while random.random() < p and level < self._MAX_LEVEL: level += 1 return level 复杂度分析空间复杂度跳表通过建立索引提高查找的效率,是典型的“空间换时间”的思想,那么空间复杂度到底是多少呢?我们假设原始链表有 $N$ 个元素,一级索引有 $\\frac{N}{2}$,二级索引有 $\\frac{N}{4}$,k 级索引有 $\\frac{N}{2^k}$ 个元素,而最高级索引一般有 $2$ 个元素。所以,索引结点的总和是 $\\frac{N}{2} + \\frac{N}{2^2} + \\frac{N}{2^3}+…+ 2 \\approx N-2$ ,因此可以得出空间复杂度是 $O(N)$, $N$ 是原始链表的长度。 上面的假设前提是每两个结点抽出一个结点到上层索引。那么如果我们每三个结点抽出一个结点到上层索引,那么索引总和就是 $\\frac{N}{3} + \\frac{N}{3^2} + \\frac{N}{3^3} + 9 + 3 + 1 \\approx \\frac{N}{2}$, 额外空间减少了一半。因此我们可以通过减少索引的数量来减少空间复杂度,但是相应的会带来查找效率一定的下降。而具体这个阈值该如何选择,则要看具体的应用场景。 另外需要注意的是,在实际的应用当中,索引结点往往不需要存储完整的对象,只需要存储对象的 key 和对应的指针即可。因此当对象比索引结点占用空间大很多时,索引结点所占的额外空间(相对原始数据来讲)又可以忽略不计了。 时间复杂度查找的时间复杂度来看看时间复杂度 $O(logN)$ 是如何推导出来的,首先我们看下图: 如上图所示,此处我们假设每两个结点会抽出一个结点来作为上一级索引的结点。也就是说,原始链表有 $N$ 个元素,一级索引有 $\\frac{N}{2}$,二级索引有 $\\frac{N}{4}$,k 级索引有 $\\frac{N}{2^k}$ 个元素,而最高级索引一般有 $2$ 个元素。 也就是说:最高级索引 $x$ 满足 $2 = N/2^x$, 由此公式可以得出 $x = \\log_2(N)-1$ , 加上原始数据这一层, 跳表的总高度为 $h = \\log_2(N)$。那么,我们在查找过程中每一层索引最多遍历几个元素呢?从图中我们可以看出来每一层最多需要遍历 3 个结点。因此,由公式 时间复杂度 = 索引高度*每层索引遍历元素个数, 可以得出跳表中查找一个元素的时间复杂度为 $O(3 \\times \\log(N))$,省略常数即为 $O(\\log(N))$。 插入的时间复杂度跳表的插入分为两部分操作: 寻找到对应的位置,时间复杂度为 $O(logN)$, $N$ 为链表长度。 插入数据。我们在前面已经推导出跳表索引的高度为 $logN$。 因此,我们将数据插入到各层索引中的最坏时间复杂度为 $O(logN)$。 综上所述,插入操作的时间复杂度为 $O(logN)$ 删除的时间复杂度跳表的删除操作和查找类似,只是需要在查找后删除对应的元素。查找操作的时间复杂度是 $logN$。那么后面删除部分代码的时间复杂度是多少呢?我们知道在跳表中,每一层索引都是一个有序的单链表,而删除单个元素的复杂度为 $O(1)$, 索引层数为 $logN$,因此删除部分代码的时间复杂度为$logN$。那么删除操作的总时间复杂度为- $O(logN) + O(logN) = 2O(logN)$。我们忽略常数部分,删除元素的时间复杂度为 $O(logN)$。 扩展在工业上,使用跳表的场景很多,下面做些简单的介绍,有兴趣的可以深入了解: redis 当中 zset 使用了跳表 HBase MemStore 当中使用了跳表 LevelDB 和 RocksDB 都是 LSM Tree 结构的数据库,内部的 MemTable 当中都使用了跳表 配套网站官网开辟了一个区域,大家可以直接访问查看本书配套的配套代码,包括 JavaScript,Java,Python 和 C++。 也欢迎大家留言给我们自己想要支持的语言,我们会郑重考虑大家的意见。 效果大概是这样的: 实体版购书链接 电子版购书链接","categories":[],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法通关之路","slug":"算法通关之路","permalink":"https://lucifer.ren/blog/tags/算法通关之路/"}]},{"title":"如何准备算法竞赛?","slug":"cses","date":"2021-11-16T16:00:00.000Z","updated":"2023-01-05T12:24:49.553Z","comments":true,"path":"2021/11/17/cses/","link":"","permalink":"https://lucifer.ren/blog/2021/11/17/cses/","excerpt":"如果你想参加算法竞赛的建议越早越好。大一或者更早就需要准备起来了。如果你已经快毕业了,那就没有必要准备了,当做兴趣参加一些力扣的比赛也是不错的。","text":"如果你想参加算法竞赛的建议越早越好。大一或者更早就需要准备起来了。如果你已经快毕业了,那就没有必要准备了,当做兴趣参加一些力扣的比赛也是不错的。 题库算法面试的考察内容相比算法面试更多,难度也更大。比如数位 dp,倍增,乘法逆元都需要掌握。而这些内容在算法面试中出现的却不多。 题库的话有很多 OJ 网站。但是题目都太多了。 这里推荐两个网站,一个适合竞赛选手,一个适合普通求职者。 CSES 这个网站比较适合竞赛选手,题目有 300 道,刷完还是比较快的(相对于其他老牌 OJ 平台),地址:https://cses.fi/problemset/ 另外一个是 BinarySearch。用户体验做的很好,题目难度和力扣差不多,难度波动感觉比力扣小一点点,地址:binarysearch.com/ 练手比赛姑且先推荐三个平台吧。 其中一个是 codeforces, 这个参与竞赛的人知道的比较多, codeforces 是全球范围内每次比赛参加人数最多的竞赛平台。普通人可能不太知道。这个网站的比较难度偏大一点,质量也更高。打过的人都知道。 第二个是 Leetcode。这个大家可能听说过。目前力扣有两种赛事。 周赛:每周日早上 10:30 双周赛:每两周一次,北京时间周六的晚上 10:30开始 力扣的难度比较入门,适合新手。不过难度波动其实也不小,难度低的时不时是手速场,难度高的话只要能做出来(即使是卡点做出来)都能进前 100。 最后推荐一个是 Google 比赛,有三个等级。 Kick Start:新手入门级,也是Google面试的敲门砖,每年举办八轮。 Code Jam :Google的王牌赛事,也是最重要的赛事,分为资格赛、A轮、B轮、C轮和决赛。决赛每年有25个名额。 Hash Code:一项团队赛,与一般编程竞赛不同,赛题一般为没有最优答案的优化问题。分为资格赛和决赛两轮。 学习网站OI wiki 是一个内容比较全,深度也覆盖比赛的网站。里面的内容大概看了下,大部分不错,少部分是从网上抄的,质量也一般。如果你有一定的鉴别能力,这个网站是非常不错的。地址:https://oi-wiki.org/ 比如图论的 OI-wiki 目录是这样的: CP-Wiki 是个人的 Competitive Programming 学习资料总结。里面不仅有各个知识点的总结。 知识总结是大纲性质的,比较简略,适合拿来查缺补漏。 而且还有各个比赛的题解,强烈建议学习竞赛的你收藏。 学习图书《算法竞赛进阶指南》推荐阅读李煜东的《算法竞赛进阶指南》。他有丰富的参与竞赛以及培训竞赛的经验,同时他也是 Google 的工程师。 李煜东曾为NOI系列竞赛、NOI导刊培训基地以及全国各地多所学校的选手授课,并在网络上组织模拟赛数十场,经验丰富、讲解透彻、广受好评。多次协助石家庄市第二中学的信息学竞赛集训工作,参与北京大学“数据结构与算法”、“算法设计与分析”的课程教学、考试命题工作。 豆瓣评分 9.1 ,群众的眼睛还是雪亮的! 这种书在算法竞赛中知名度还是很高的。你如果准备算法面试的话可能听说过。 如果没有听说过,现在不妨买来看看。 附上这种书的目录给大家: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778790x00 基本算法0x01 位运算0x02 枚举、模拟、递推0x03 递归0x04 二分0x05 排序0x06 倍增0x07 贪心0x08 总结与练习0x10 基本数据结构0x11 栈0x12 队列0x13 链表与邻接表0x14 Hash0x15 字符串0x16 Trie0x17 二叉堆0x18 总结与练习0x20 搜索0x21 树与图的遍历0x22 深度优先捜索0x23 剪枝0x24 迭代加深0x25 广度优先捜索0x26 广捜变形0x27 A*0x28 IDA*0x29 总结与练习0x30 数学知识0x31 质数0x32 约数0x33 同余0x34 矩阵乘法0x35 高斯消元与线性空间0x36 组合计数0x37 容斥原理与Möbius函数0x38 概率与数学期望0x39 0/1分数规划0x3A 博弈论之SG函数0x3B 总结与练习0x40 数据结构进阶0x41 并査集0x42 树状数组0x43 线段树0x44 分块0x45 点分治0x46 二叉査找树与平衡树初步0x47 总结与练习0x50 动态规划0x51 线性DP0x52 背包0x53 区间DP0x54 树形DP0x55 环形与后效性处理0x56 状态压缩DP0x57 倍增优化DP0x58 数据结构优化DP0x59 单调队列优化DP0x5A 斜率优化0x5B 四边形不等式0x5C 计数类DP0x5D 数位统计DP0x5E 总结与练习0x60 图论0x61 最短路0x62 最小生成树0x63 树的直径与最近公共祖先0x64 基环树0x65 负环与差分约束0x66 Tarjan算法与无向图连通性0x67 Tarjan算法与有向图连通性0x68 二分图的匹配0x69 二分图的覆盖与独立集0x6A 网络流初步0x6B 总结与练习0x70 综合技巧与实践0x71 C++ STL0x72 随机数据生成与对拍0x7F 附录 如果你不打算参与算法竞赛,我也建议你买过来看看,不过内容可以选择性看看即可。如果你也不知道应该看哪部分,可以在我的交流群中进行讨论,公众号力扣加加回复 leetcode 进群。 《Guide to Competitive Programming》《Guide to Competitive Programming》这种书我自己没有读过。不过听朋友说内容不错,大家可以试读一下,看看目录是否适合自己。 BTW,前面提到的题库网站 CSES 也推荐了这本书哦。 总结大家如何想参与算法竞赛尽量趁早,建议至少从大一就开始学习。 建议大家卖一本书系统性学习,然后找个题库跟着刷题。题库刷完之后再去参与比赛。比赛完成后可以看下大家的题解,不管自己有没有做出来,做出来也看看别人的做法是不是更好。经过这样的一条完整路线,我相信大学期间拿个名次,进个大公司还是不成问题的。当然进大公司还要其他方面也不差才行 😄","categories":[{"name":"算法比赛","slug":"算法比赛","permalink":"https://lucifer.ren/blog/categories/算法比赛/"}],"tags":[{"name":"算法比赛","slug":"算法比赛","permalink":"https://lucifer.ren/blog/tags/算法比赛/"}]},{"title":"Chrome 新功能 - 录制小视频","slug":"chrome-recorder","date":"2021-11-09T16:00:00.000Z","updated":"2023-01-05T12:24:49.961Z","comments":true,"path":"2021/11/10/chrome-recorder/","link":"","permalink":"https://lucifer.ren/blog/2021/11/10/chrome-recorder/","excerpt":"Chrome 97 推出了一个预览功能 - Recorder。它允许你录制 Web 页面的操作并支持回放,编辑,测量性能 等诸多功能。","text":"Chrome 97 推出了一个预览功能 - Recorder。它允许你录制 Web 页面的操作并支持回放,编辑,测量性能 等诸多功能。 它长什么样你可以直接在 chrome devtool 中看到一个 Recorder 面板,点击它就可以体验。 如果没有找到,可以尝试 cmd + shift + p 调出命令面板搜索 Recorder。当然如果该功能未发布是搜不到的 它有什么用?通过它,你可以实现一些有趣的功能。 比如: 测试同学录制一段“视频”, 然后发送给开发,开发根据这段视频定位问题。 测试某一个业务流程在各种不同的网络和硬件环境下的表现,甚至你可以看其在不同平台的表现(比如 PC,手机,平板等)。 自动化测试。你可以录制一段视频,然后通过修改其中部分参数的形式来自动化生成很多测试用例。 。。。 由于是预览版,因此最终是什么样可能还不确定。 大招对于我来说,我想要到一个比较有意思的功能。 我们知道 Chrome 的 devtool frontend(就是你看到的开发者工具) 是开源的,代码托管在 Github:https://github.com/ChromeDevTools/devtools-frontend 因此你可以直接集成它到你的项目中。比如你可以开发一个调试工具,这个工具 fork 一下 devtool frontend,然后修改 Recoreder 部分的源码,使得用户可以手动上报自己的录像,然后你将用户的录像数据,网络数据等其他数据发送到你的后端进行分析。 Recorder 的源码到时候应该在这个文件夹下 https://github.com/ChromeDevTools/devtools-frontend/tree/main/front_end/panels 这个功能我在之前的公司做过,不过做的并不好。而如果依托于 Chrome 团队,那些棘手的问题都不需要你解决了,比如性能问题就很棘手。 如果你的公司有做用户错误上报或者信息收集的需求,不妨考虑一下是否可以为你所用。 更多介绍:https://developer.chrome.com/docs/devtools/recorder/","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"Chrome","slug":"工具/Chrome","permalink":"https://lucifer.ren/blog/categories/工具/Chrome/"}],"tags":[{"name":"Chrome","slug":"Chrome","permalink":"https://lucifer.ren/blog/tags/Chrome/"}]},{"title":"面试中图论都考什么?这篇文章告诉你!","slug":"grapth","date":"2021-11-08T16:00:00.000Z","updated":"2023-01-05T12:24:49.691Z","comments":true,"path":"2021/11/09/grapth/","link":"","permalink":"https://lucifer.ren/blog/2021/11/09/grapth/","excerpt":"图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。 \b 如下就是一种逻辑上的图结构: 图是一种最复杂的数据结构,前面讲的数据结构都可以看成是图的特例。那为什么不都用图就好了,还要分那么多种数据结构呢? 这是因为很多时候不需要用到那么复杂的功能,图的很多特性都不具备,如果笼统地都称为图那么非常不利于沟通。你想你和别人沟通总不至于说这道题是考察一种特殊的图,这种图。。。。 这未免太啰嗦了,因此给其他图的特殊的图起了特殊的名字,这样就方便沟通了。直到遇到了非常复杂的情况,我们才会用到 ”真正“的图。 前面章节提到了数据结构就是为了算法服务的,数据结构就是存储数据用的,目的是为了更高效。 那么什么时候需要用图来存储数据,在这种情况图高效在哪里呢?答案很简单,那就是如果你用其他简单的数据结构无法很好地进行存储,就应该使用图了。 比如我们需要存储一种双向的朋友关系,并且这种朋友关系是多对多的,那就一定要用到图,因为其他数据结构无法模拟。","text":"图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。 \b 如下就是一种逻辑上的图结构: 图是一种最复杂的数据结构,前面讲的数据结构都可以看成是图的特例。那为什么不都用图就好了,还要分那么多种数据结构呢? 这是因为很多时候不需要用到那么复杂的功能,图的很多特性都不具备,如果笼统地都称为图那么非常不利于沟通。你想你和别人沟通总不至于说这道题是考察一种特殊的图,这种图。。。。 这未免太啰嗦了,因此给其他图的特殊的图起了特殊的名字,这样就方便沟通了。直到遇到了非常复杂的情况,我们才会用到 ”真正“的图。 前面章节提到了数据结构就是为了算法服务的,数据结构就是存储数据用的,目的是为了更高效。 那么什么时候需要用图来存储数据,在这种情况图高效在哪里呢?答案很简单,那就是如果你用其他简单的数据结构无法很好地进行存储,就应该使用图了。 比如我们需要存储一种双向的朋友关系,并且这种朋友关系是多对多的,那就一定要用到图,因为其他数据结构无法模拟。 基本概念无向图 & 有向图〔Undirected Graph & Deriected Graph〕前面提到了二叉树完全可以实现其他树结构,类似地,有向图也完全可以实现无向图和混合图,因此有向图的研究一直是重点考察对象。 本文讲的所有图都是有向图。 前面提到了我们用连接两点的线表示相应两个事物间具有这种关系。因此如果两个事物间的关系是有方向的,就是有向图,否则就是无向图。比如:A 认识 B,那么 B 不一定认识 A。那么关系就是单向的,我们需要用有向图来表示。因为如果用无向图表示,我们无法区分 A 和 B 的边表示的是 A 认识 B 还是 B 认识 A。 习惯上,我们画图的时候用带箭头的表示有向图,不带箭头的表示无向图。 有权图 & 无权图〔Weighted Graph & Unweighted Graph〕如果边是有权重的是有权图(或者带权图),否则是无权图(或不带权图)。那么什么是有权重呢?比如汇率就是一种有权重的逻辑图。1 货币 A 兑换 5 货币 B,那么我们 A 和 B 的边的权重就是 5。而像朋友这种关系,就可以看做一种不带权的图。 入度 & 出度〔Indegree & Outdegree〕有多少边指向节点 A,那么节点 A 的入度就是多少。同样地,有多少边从 A 发出,那么节点 A 的出度就是多少。 仍然以上面的图为例,这幅图的所有节点的入度和出度都为 1。 路径 & 环〔路径:Path〕 有环图〔Cyclic Graph〕 上面的图就是一个有环图,因为我们从图中的某一个点触发,能够重新回到起点。这和现实中的环是一样的。 无环图〔Acyclic Graph〕 我可以将上面的图稍加改造就变成了无环图,此时没有任何一个环路。 连通图 & 强连通图在无向图中,若任意两个顶点 i 与 j 都有路径相通,则称该无向图为连通图。 在有向图中,若任意两个顶点 i 与 j 都有路径相通,则称该有向图为强连通图。 生成树一个连通图的生成树是指一个连通子图,它含有图中全部 n 个顶点,但只有足以构成一棵树的 n-1 条边。一颗有 n 个顶点的生成树有且仅有 n-1 条边,如果生成树中再添加一条边,则必定成环。在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树,其中代价和指的是所有边的权重和。 图的建立一般图的题目都不会给你一个现成的图的数据结构。当你知道这是一个图的题目的时候,解题的第一步通常就是建图。 上面讲的都是图的逻辑结构,那么计算机中的图如何存储呢? 我们知道图是有点和边组成的。理论上,我们只要存储图中的所有的边关系即可,因为边中已经包含了两个点的关系。 这里我简单介绍两种常见的建图方式:邻接矩阵(常用,重要)和邻接表。 邻接矩阵(常见)〔Adjacency Matrixs〕第一种方式是使用数组或者哈希表来存储图,这里我们用二维数组来存储。 使用一个 n * n 的矩阵来描述图 graph,其就是一个二维的矩阵,其中 graph[i][j] 描述边的关系。 一般而言,对于无权图我都用 graph[i][j] = 1 来表示 顶点 i 和顶点 j 之间有一条边,并且边的指向是从 i 到 j。用 graph[i][j] = 0 来表示 顶点 i 和顶点 j 之间不存在一条边。 对于有权图来说,我们可以存储其他数字,表示的是权重。 可以看出上图是对角线对称的,这样我们只需看一半就好了,这就造成了一半的空间浪费。 这种存储方式的空间复杂度为 O(n ^ 2),其中 n 为顶点个数。如果是稀疏图(图的边的数目远小于顶点的数目),那么会很浪费空间。并且如果图是无向图,始终至少会有 50 % 的空间浪费。下面的图也直观地反应了这一点。 邻接矩阵的优点主要有: 直观,简单。 判断两个顶点是否连接,获取入度和出度以及更新度数,时间复杂度都是 O(1) 由于使用起来比较简单, 因此我的所有的需要建图的题目基本都用这种方式。 比如力扣 743. 网络延迟时间。 题目描述: 12345678910111213141516171819有 N 个网络节点,标记为 1 到 N。给定一个列表 times,表示信号经过有向边的传递时间。 times[i] = (u, v, w),其中 u 是源节点,v 是目标节点, w 是一个信号从源节点传递到目标节点的时间。现在,我们从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1。示例:输入:times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2输出:2 注意:N 的范围在 [1, 100] 之间。K 的范围在 [1, N] 之间。times 的长度在 [1, 6000] 之间。所有的边 times[i] = (u, v, w) 都有 1 <= u, v <= N 且 0 <= w <= 100。 这是一个典型的图的题目,对于这道题,我们如何用邻接矩阵建图呢? 一个典型的建图代码: 使用哈希表构建邻接矩阵: 123graph = collections.defaultdict(list)for fr, to, w in times: graph[fr - 1].append((to - 1, w)) 使用二维数组构建邻接矩阵: 1234graph = [[0]*n for _ in range(m)] # 新建一个 m * n 的二维矩阵for fr, to, w in times: graph[fr-1][to-1] = w 这就构造了一个临界矩阵,之后我们基于这个邻接矩阵遍历图即可。 邻接表〔Adjacency List〕对于每个点,存储着一个链表,用来指向所有与该点直接相连的点。对于有权图来说,链表中元素值对应着权重。 例如在无向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 可以看出在无向图中,邻接矩阵关于对角线对称,而邻接链表总有两条对称的边。 而在有向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 由于邻接表使用起来稍微麻烦一点,另外也不常用。为了减少初学者的认知负担,我就不贴代码了。 图的遍历图建立好了,接下来就是要遍历了。 不管你是什么算法,肯定都要遍历的,一般有这两种方法:深度优先搜索,广度优先搜索(其他奇葩的遍历方式实际意义不大,没有必要学习)。 不管是哪一种遍历, 如果图有环,就一定要记录节点的访问情况,防止死循环。当然你可能不需要真正地使用一个集合记录节点的访问情况,比如使用一个数据范围外的数据原地标记,这样的空间复杂度会是 $O(1)$。 这里以有向图为例, 有向图也是类似,这里不再赘述。 关于图的搜索,后面的搜索专题也会做详细的介绍,因此这里就点到为止。 深度优先遍历〔Depth First Search, DFS〕深度优先遍历图的方法是,从图中某顶点 v 出发, 不断访问邻居, 邻居的邻居直到访问完毕。 如上图, 如果我们使用 DFS,并且从 A 节点开始的话, 一个可能的的访问顺序是: A -> C -> B -> D -> F -> G -> E,当然也可能是 A -> D -> C -> B -> F -> G -> E 等,具体取决于你的代码,但他们都是深度优先的。 广度优先搜索〔Breadth First Search, BFS〕广度优先搜索,可以被形象地描述为 “浅尝辄止”,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。 如上图, 如果我们使用 BFS,并且从 A 节点开始的话, 一个可能的的访问顺序是: A -> B -> C -> F -> E -> G -> D,当然也可能是 A -> B -> F -> E -> C -> G -> D 等,具体取决于你的代码,但他们都是广度优先的。 需要注意的是 DFS 和 BFS 只是一种算法思想,不是一种具体的算法。 因此其有着很强的适应性,而不是局限于特点的数据结构的,本文讲的图可以用,前面讲的树也可以用。实际上, 只要是非线性的数据结构都可以用。 常见算法图的题目的算法比较适合套模板。 这里介绍几种常见的板子题。主要有: Dijkstra Floyd-Warshall 最小生成树(Kruskal & Prim) 目前此小节已经删除,觉得自己写的不够详细,之后补充完成会再次开放。 A 星寻路算法 二分图(染色法)〔Bipartitie〕 拓扑排序〔Topological Sort〕 下面列举常见算法的模板。 以下所有的模板都是基于邻接矩阵建图。 强烈建议大家学习完专题篇的搜索之后再来学习下面经典算法。大家可以拿几道普通的搜索题目测试下,如果能够做出来再往下学习。推荐题目:最大化一张图中的路径价值 最短距离,最短路径Dijkstra 算法DIJKSTRA 基本思想是广度优先遍历。实际上搜索的最短路算法基本思想都是广度优先,只不过具体的扩展策略不同而已。 DIJKSTRA 算法主要解决的是图中任意一点到图中另外任意一个点的最短距离,即单源最短路径。 Dijkstra 这个名字比较难记,大家可以简单记为DJ 算法,有没有好记很多? 比如给你几个城市,以及城市之间的距离。让你规划一条最短的从城市 a 到城市 b 的路线。 这个问题,我们就可以先将城市间的距离用图建立出来,然后使用 dijkstra 来做。那么 dijkstra 究竟如何计算最短路径的呢? dj 算法的基本思想是贪心。从起点 start 开始,每次都遍历所有邻居,并从中找到距离最小的,本质上是一种广度优先遍历。这里我们借助堆这种数据结构,使得可以在 $logN$ 的时间内找到 cost 最小的点。 而如果使用普通的队列的话,其实是图中所有边权值都相同的特殊情况。 比如我们要找从点 start 到点 end 的最短距离。我们期望 dj 算法是这样被使用的。 比如一个图是这样的: 1234E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F \\ /\\ \\ || -------- 2 ---------> G ------- 1 ------ 我们使用邻接矩阵来构造: 1234567891011G = { \"B\": [[\"C\", 1]], \"C\": [[\"D\", 1]], \"D\": [[\"F\", 1]], \"E\": [[\"B\", 1], [\"G\", 2]], \"F\": [], \"G\": [[\"F\", 1]],}shortDistance = dijkstra(G, \"E\", \"C\")print(shortDistance) # E -- 3 --> F -- 3 --> C == 6 具体算法: 初始化堆。堆里的数据都是 (cost, v) 的二元祖,其含义是“从 start 走到 v 的距离是 cost”。因此初始情况,堆中存放元组 (0, start) 从堆中 pop 出来一个 (cost, v),第一次 pop 出来的一定是 (0, start)。 如果 v 被访问过了,那么跳过,防止环的产生。 如果 v 是 我们要找的终点,直接返回 cost,此时的 cost 就是从 start 到 该点的最短距离 否则,将 v 的邻居入堆,即将 (neibor, cost + c) 加入堆。其中 neibor 为 v 的邻居, c 为 v 到 neibor 的距离(也就是转移的代价)。 重复执行 2 - 4 步 代码模板: Python 1234567891011121314151617181920import heapqdef dijkstra(graph, start, end): # 堆里的数据都是 (cost, i) 的二元祖,其含义是“从 start 走到 i 的距离是 cost”。 heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 JavaScript 1234567891011121314151617181920212223242526272829const dijkstra = (graph, start, end) => { const visited = new Set() const minHeap = new MinPriorityQueue(); //注:此处new MinPriorityQueue()用了LC的内置API,它的enqueue由两个部分组成: //element 和 priority。 //堆会按照priority排序,可以用element记录一些内容。 minHeap.enqueue(startPoint, 0) while(!minHeap.isEmpty()){ const {element, priority} = minHeap.dequeue(); //下面这两个变量不是必须的,只是便于理解 const curPoint = element; const curCost = priority; if(curPoint === end) return curCost; if(visited.has(curPoint)) continue; visited.add(curPoint); if(!graph[curPoint]) continue; for(const [nextPoint, nextCost] of graph[curPoint]){ if(visited.has(nextPoint)) continue; //注意heap里面的一定是从startPoint到某个点的距离; //curPoint到nextPoint的距离是nextCost;但curPoint不一定是startPoint。 const accumulatedCost = nextCost + curCost; minHeap.enqueue(nextPoint, accumulatedCost); } } return -1} 会了这个算法模板, 你就可以去 AC 743. 网络延迟时间 了。 这里提供完整代码供大家参考: Python 123456789101112131415161718192021222324252627class Solution: def dijkstra(self, graph, start, end): heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, w in times: graph[fr - 1].append((to - 1, w)) ans = -1 for to in range(N): dist = self.dijkstra(graph, K - 1, to) if dist == -1: return -1 ans = max(ans, dist) return ans JavaScript 123456789101112131415161718192021222324252627282930313233343536373839404142434445const networkDelayTime = (times, n, k) => { //咳咳这个解法并不是Dijkstra在本题的最佳解法 const graph = {}; for(const [from, to, weight] of times){ if(!graph[from]) graph[from] = []; graph[from].push([to, weight]); } let ans = -1; for(let to = 1; to <= n; to++){ let dist = dikstra(graph, k, to) if(dist === -1) return -1; ans = Math.max(ans, dist); } return ans;};const dijkstra = (graph, startPoint, endPoint) => { const visited = new Set() const minHeap = new MinPriorityQueue(); //注:此处new MinPriorityQueue()用了LC的内置API,它的enqueue由两个部分组成: //element 和 priority。 //堆会按照priority排序,可以用element记录一些内容。 minHeap.enqueue(startPoint, 0) while(!minHeap.isEmpty()){ const {element, priority} = minHeap.dequeue(); //下面这两个变量不是必须的,只是便于理解 const curPoint = element; const curCost = priority; if(visited.has(curPoint)) continue; visited.add(curPoint) if(curPoint === endPoint) return curCost; if(!graph[curPoint]) continue; for(const [nextPoint, nextCost] of graph[curPoint]){ if(visited.has(nextPoint)) continue; //注意heap里面的一定是从startPoint到某个点的距离; //curPoint到nextPoint的距离是nextCost;但curPoint不一定是startPoint。 const accumulatedCost = nextCost + curCost; minHeap.enqueue(nextPoint, accumulatedCost); } } return -1} DJ 算法的时间复杂度为 $vlogv+e$,其中 v 和 e 分别为图中的点和边的个数。 最后给大家留一个思考题:如果是计算一个点到图中所有点的距离呢?我们的算法会有什么样的调整? 提示:你可以使用一个 dist 哈希表记录开始点到每个点的最短距离来完成。想出来的话,可以用力扣 882 题去验证一下哦~ 值得注意的是, Dijkstra 无法处理边权值为负的情况。即如果出现负权值的边,那么答案可能不正确。而基于动态规划算法的最短路(下文会讲)则可以处理这种情况。 Floyd-Warshall 算法Floyd-Warshall 可以解决任意两个点距离,即多源最短路径,这点和 dj 算法不一样。 除此之外,贝尔曼-福特算法也是解决最短路径的经典动态规划算法,这点和 dj 也是不一样的,dj 是基于贪心的。 相比上面的 dijkstra 算法, 由于其计算过程会把中间运算结果保存起来防止重复计算,因此其特别适合求图中任意两点的距离,比如力扣的 1462. 课程安排 IV。除了这个优点。下文要讲的贝尔曼-福特算法相比于此算法最大的区别在于本算法是多源最短路径,而贝尔曼-福特则是单源最短路径。不管是复杂度和写法, 贝尔曼-福特算法都更简单,我们后面给大家介绍。 当然就不是说贝尔曼算法以及上面的 dijkstra 就不支持多源最短路径,你只需要加一个 for 循环枚举所有的起点罢了。 还有一个非常重要的点是 Floyd-Warshall 算法由于使用了动态规划的思想而不是贪心,因此其可以处理负权重的情况,这点需要大家尤为注意。 动态规划的详细内容请参考之后的动态规划专题和背包问题。 算法也不难理解,简单来说就是: i 到 j 的最短路径 = i 到 k 的最短路径 + k 到 j 的最短路径的最小值。如下图: u 到 v 的最短距离是 u 到 x 的最短距离 + x 到 v 的最短距离。上图 x 是 u 到 v 的必经之路,如果不是的话,我们需要多个中间节点的值,并取最小的。 算法的正确性不言而喻,因为从 i 到 j,要么直接到,要么经过图中的另外一个点 k,中间节点 k 可能有多个,经过中间点的情况取出最小的,自然就是 i 到 j 的最短距离。 思考题: 最长无环路径可以用动态规划来解么? 该算法的时间复杂度是 $O(N^3)$,空间复杂度是 $O(N^2)$,其中 N 为顶点个数。 代码模板: Python 1234567891011121314151617181920212223# graph 是邻接矩阵,n 是顶点个数# graph 形如: graph[u][v] = wdef floyd_warshall(graph, n): dist = [[float(\"inf\") for _ in range(n)] for _ in range(n)] for i in range(n): for j in range(n): dist[i][j] = graph[i][j] # check vertex k against all other vertices (i, j) for k in range(n): # looping through rows of graph array for i in range(n): # looping through columns of graph array for j in range(n): if ( dist[i][k] != float(\"inf\") and dist[k][j] != float(\"inf\") and dist[i][k] + dist[k][j] < dist[i][j] ): dist[i][j] = dist[i][k] + dist[k][j] return dist JavaScript 123456789101112131415161718192021222324const floydWarshall = (graph, v)=>{ const dist = new Array(v).fill(0).map(() => new Array(v).fill(Number.MAX_SAFE_INTEGER)) for(let i = 0; i < v; i++){ for(let j = 0; j < v; j++){ //两个点相同,距离为0 if(i === j) dist[i][j] = 0; //i 和 j 的距离已知 else if(graph[i][j]) dist[i][j] = graph[i][j]; //i 和 j 的距离未知,默认是最大值 else dist[i][j] = Number.MAX_SAFE_INTEGER; } } //检查是否有一个点 k 使得 i 和 j 之间距离更短,如果有,则更新最短距离 for(let k = 0; k < v; k++){ for(let i = 0; i < v; i++){ for(let j = 0; j < v; j++){ dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]) } } } return 看需要} 我们回过头来看下如何套模板解决 力扣的 1462. 课程安排 IV,题目描述: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152你总共需要上 n 门课,课程编号依次为 0 到 n-1 。有的课会有直接的先修课程,比如如果想上课程 0 ,你必须先上课程 1 ,那么会以 [1,0] 数对的形式给出先修课程数对。给你课程总数 n 和一个直接先修课程数对列表 prerequisite 和一个查询对列表 queries 。对于每个查询对 queries[i] ,请判断 queries[i][0] 是否是 queries[i][1] 的先修课程。请返回一个布尔值列表,列表中每个元素依次分别对应 queries 每个查询对的判断结果。注意:如果课程 a 是课程 b 的先修课程且课程 b 是课程 c 的先修课程,那么课程 a 也是课程 c 的先修课程。 示例 1:输入:n = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]输出:[false,true]解释:课程 0 不是课程 1 的先修课程,但课程 1 是课程 0 的先修课程。示例 2:输入:n = 2, prerequisites = [], queries = [[1,0],[0,1]]输出:[false,false]解释:没有先修课程对,所以每门课程之间是独立的。示例 3:输入:n = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]输出:[true,true]示例 4:输入:n = 3, prerequisites = [[1,0],[2,0]], queries = [[0,1],[2,0]]输出:[false,true]示例 5:输入:n = 5, prerequisites = [[0,1],[1,2],[2,3],[3,4]], queries = [[0,4],[4,0],[1,3],[3,0]]输出:[true,false,true,false] 提示:2 <= n <= 1000 <= prerequisite.length <= (n * (n - 1) / 2)0 <= prerequisite[i][0], prerequisite[i][1] < nprerequisite[i][0] != prerequisite[i][1]先修课程图中没有环。先修课程图中没有重复的边。1 <= queries.length <= 10^4queries[i][0] != queries[i][1] 这道题也可以使用 Floyd-Warshall 来做。 你可以这么想, 如果从 i 到 j 的距离大于 0,那不就是先修课么。而这道题数据范围 queries 大概是 10 ^ 4 , 用上面的 dijkstra 算法肯定超时,,因此 Floyd-Warshall 算法是明智的选择。 我这里直接套模板,稍微改下就过了。完整代码:Python 12345678910111213141516171819class Solution: def Floyd-Warshall(self, dist, v): for k in range(v): for i in range(v): for j in range(v): dist[i][j] = dist[i][j] or (dist[i][k] and dist[k][j]) return dist def checkIfPrerequisite(self, n: int, prerequisites: List[List[int]], queries: List[List[int]]) -> List[bool]: graph = [[False] * n for _ in range(n)] ans = [] for to, fr in prerequisites: graph[fr][to] = True dist = self.Floyd-Warshall(graph, n) for to, fr in queries: ans.append(bool(dist[fr][to])) return ans JavaScript 12345678910111213141516171819202122232425262728293031323334//咳咳这个写法不是本题最优var checkIfPrerequisite = function(numCourses, prerequisites, queries) { const graph = {} for(const [course, pre] of prerequisites){ if(!graph[pre]) graph[pre] = {} graph[pre][course] = true } const ans = [] const dist = Floyd-Warshall(graph, numCourses) for(const [course, pre] of queries){ ans.push(dist[pre][course]) } return ans};var Floyd-Warshall = function(graph, n){ dist = Array.from({length: n + 1}).map(() => Array.from({length: n + 1}).fill(false)) for(let k = 0; k < n; k++){ for(let i = 0; i < n; i++){ for(let j = 0; j < n; j++){ if(graph[i] && graph[i][j]) dist[i][j] = true if(graph[i] && graph[k]){ dist[i][j] = (dist[i][j])|| (dist[i][k] && dist[k][j]) }else if(graph[i]){ dist[i][j] = dist[i][j] } } } } return dist} 如果这道题你可以解决了,我再推荐一道题给你 1617. 统计子树中城市之间最大距离,国际版有一个题解代码挺清晰,挺好理解的,只不过没有使用状态压缩性能不是很好罢了,地址:https://leetcode.com/problems/count-subtrees-with-max-distance-between-cities/discuss/1136596/Python-Floyd-Warshall-and-check-all-subtrees 图上的动态规划算法大家还可以拿这个题目来练习一下。 787. K 站中转内最便宜的航班 贝尔曼-福特算法和上面的算法类似。这种解法主要解决单源最短路径,即图中某一点到其他点的最短距离。 其基本思想也是动态规划。 核心算法为: 初始化起点距离为 0 对图中的所有边进行若干次处理,直到稳定。处理的依据是:对于每一个有向边 (u,v),如果 dist[u] + w 小于 dist[v],那么意味着我们找到了一条到达 v 更近的路,更新之。 上面的若干次的上限是顶点 V 的个数,因此不妨直接进行 n 次处理。 最后检查一下是否存在负边引起的环。(注意) 举个例子。对于如下的一个图,存在一个 B -> C -> D -> B,这样 B 到 C 和 D 的距离理论上可以无限小。我们需要检测到这一种情况,并退出。 此算法时间复杂度:$O(V*E)$, 空间复杂度:$O(V)$。 代码示例:Python 123456789101112131415# return -1 for not exsit# else return dis map where dis[v] means for point s the least cost to point vdef bell_man(edges, s): dis = defaultdict(lambda: math.inf) dis[s] = 0 for _ in range(n): for u, v, w in edges: if dis[u] + w < dis[v]: dis[v] = dis[u] + w for u, v, w in edges: if dis[u] + w < dis[v]: return -1 return dis JavaScript 12345678910111213141516171819const BellmanFord = (edges, startPoint)=>{ const n = edges.length; const dist = new Array(n).fill(Number.MAX_SAFE_INTEGER); dist[startPoint] = 0; for(let i = 0; i < n; i++){ for(const [u, v, w] of edges){ if(dist[u] + w < dist[v]){ dist[v] = dist[u] + w; } } } for(const [u, v, w] of edges){ if(dist[u] + w < dist[v]) return -1; } return dist} 推荐阅读: bellman-ford-algorithm 题目推荐: Best Currency Path 拓扑排序在计算机科学领域,有向图的拓扑排序是对其顶点的一种线性排序,使得对于从顶点 u 到顶点 v 的每个有向边 uv, u 在排序中都在之前。当且仅当图中没有定向环时(即有向无环图),才有可能进行拓扑排序。 典型的题目就是给你一堆课程,课程之间有先修关系,让你给出一种可行的学习路径方式,要求先修的课程要先学。任何有向无环图至少有一个拓扑排序。已知有算法可以在线性时间内,构建任何有向无环图的拓扑排序。 Kahn 算法简单来说,假设 L 是存放结果的列表,先找到那些入度为零的节点,把这些节点放到 L 中,因为这些节点没有任何的父节点。然后把与这些节点相连的边从图中去掉,再寻找图中的入度为零的节点。对于新找到的这些入度为零的节点来说,他们的父节点已经都在 L 中了,所以也可以放入 L。重复上述操作,直到找不到入度为零的节点。如果此时 L 中的元素个数和节点总数相同,说明排序完成;如果 L 中的元素个数和节点总数不同,说明原图中存在环,无法进行拓扑排序。 123456789101112131415161718192021222324252627282930313233343536def topologicalSort(graph): \"\"\" Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph using BFS \"\"\" indegree = [0] * len(graph) queue = collections.deque() topo = [] cnt = 0 for key, values in graph.items(): for i in values: indegree[i] += 1 for i in range(len(indegree)): if indegree[i] == 0: queue.append(i) while queue: vertex = queue.popleft() cnt += 1 topo.append(vertex) for x in graph[vertex]: indegree[x] -= 1 if indegree[x] == 0: queue.append(x) if cnt != len(graph): print(\"Cycle exists\") else: print(topo)# Adjacency List of Graphgraph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []}topologicalSort(graph) 最小生成树首先我们来看下什么是生成树。 首先生成树是原图的一个子图,它本质是一棵树,这也是为什么叫做生成树,而不是生成图的原因。其次生成树应该包括图中所有的顶点。 如下图由于没有包含所有顶点,换句话说所有顶点没有在同一个联通域,因此不是一个生成树。 黄色顶点没有包括在内 你可以将生成树看成是根节点不确定的多叉树,由于是一棵树,那么一定不包含环。如下图就不是生成树。 因此不难得出,最小生成树的边的个数是 n - 1,其中 n 为顶点个数。 接下来我们看下什么是最小生成树。 最小生成树是在生成树的基础上加了最小关键字,是最小权重生成树的简称。从这句话也可以看出,最小生成树处理正是有权图。生成树的权重是其所有边的权重和,那么最小生成树就是权重和最小的生成树,由此可看出,不管是生成树还是最小生成树都可能不唯一。 最小生成树在实际生活中有很强的价值。比如我要修建一个地铁,并覆盖 n 个站,这 n 个站要互相都可以到达(同一个联通域),如果建造才能使得花费最小?由于每个站之间的路线不同,因此造价也不一样,因此这就是一个最小生成树的实际使用场景,类似的例子还有很多。 (图来自维基百科) 不难看出,计算最小生成树就是从边集合中挑选 n - 1 个边,使得其满足生成树,并且权值和最小。 Kruskal 和 Prim 是两个经典的求最小生成树的算法,这两个算法又是如何计算最小生成树的呢?本节我们就来了解一下它们。 KruskalKruskal 相对比较容易理解,推荐掌握。 Kruskal 算法也被形象地称为加边法,每前进一次都选择权重最小的边,加入到结果集。为了防止环的产生(增加环是无意义的,只要权重是正数,一定会使结果更差),我们需要检查下当前选择的边是否和已经选择的边联通了。如果联通了,是没有必要选取的,因为这会使得环产生。因此算法上,我们可使用并查集辅助完成。关于并查集,我们会在之后的进阶篇进行讲解。 下面代码中的 find_parent 部分,实际上就是并查集的核心代码,只是我们没有将其封装并使用罢了。 Kruskal 具体算法: 对边按照权值从小到大进行排序。 将 n 个顶点初始化为 n 个联通域 按照权值从小到大选择边加入到结果集,每次贪心地选择最小边。如果当前选择的边是否和已经选择的边联通了(如果强行加就有环了),则放弃选择,否则进行选择,加入到结果集。 重复 3 直到我们找到了一个联通域大小为 n 的子图 代码模板: 其中 edge 是一个数组,数组每一项都形如: (cost, fr, to),含义是 从 fr 到 to 有一条权值为 cost的边。 12345678910111213141516171819202122232425262728293031323334353637383940class DisjointSetUnion: def __init__(self, n): self.n = n self.rank = [1] * n self.f = list(range(n)) def find(self, x: int) -> int: if self.f[x] == x: return x self.f[x] = self.find(self.f[x]) return self.f[x] def unionSet(self, x: int, y: int) -> bool: fx, fy = self.find(x), self.find(y) if fx == fy: return False if self.rank[fx] < self.rank[fy]: fx, fy = fy, fx self.rank[fx] += self.rank[fy] self.f[fy] = fx return Trueclass Solution: def Kruskal(self, edges) -> int: n = len(points) dsu = DisjointSetUnion(n) edges.sort() ret, num = 0, 1 for length, x, y in edges: if dsu.unionSet(x, y): ret += length num += 1 if num == n: break return ret PrimPrim 算法也被形象地称为加点法,每前进一次都选择权重最小的点,加入到结果集。形象地看就像一个不断生长的真实世界的树。 Prim 具体算法: 初始化最小生成树点集 MV 为图中任意一个顶点,最小生成树边集 ME 为空。我们的目标是将 MV 填充到 和 V 一样,而边集则根据 MV 的产生自动计算。 在集合 E 中 (集合 E 为原始图的边集)选取最小的边 其中 u 为 MV 中已有的元素,而 v 为 MV 中不存在的元素(像不像上面说的不断生长的真实世界的树),将 v 加入到 MV,将 加到 ME。 重复 2 直到我们找到了一个联通域大小为 n 的子图 代码模板: 其中 dist 是二维数组,dist[i][j] = x 表示顶点 i 到顶点 j 有一条权值为 x 的边。 1234567891011121314151617181920class Solution: def Prim(self, dist) -> int: n = len(dist) d = [float(\"inf\")] * n # 表示各个顶点与加入最小生成树的顶点之间的最小距离. vis = [False] * n # 表示是否已经加入到了最小生成树里面 d[0] = 0 ans = 0 for _ in range(n): # 寻找目前这轮的最小d M = float(\"inf\") for i in range(n): if not vis[i] and d[i] < M: node = i M = d[i] vis[node] = True ans += M for i in range(n): if not vis[i]: d[i] = min(d[i], dist[i][node]) return ans 两种算法比较为了后面描述方便,我们令 V 为图中的顶点数, E 为图中的边数。那么 KruKal 的算法复杂度是 $O(ElogE)$,Prim 的算法时间复杂度为 $E + VlogV$。因此 Prim 适合适用于稠密图,而 KruKal 则适合稀疏图。 大家也可以参考一下 维基百科 - 最小生成树 的资料作为补充。 另外这里有一份视频学习资料,其中的动画做的不错,大家可以作为参考,地址:https://www.bilibili.com/video/BV1Eb41177d1/ 大家可以使用 LeetCode 的 1584. 连接所有点的最小费用 来练习该算法。 其他算法A 星寻路算法A 星寻路解决的问题是在一个二维的表格中找出任意两点的最短距离或者最短路径。常用于游戏中的 NPC 的移动计算,是一种常用启发式算法。一般这种题目都会有障碍物。除了障碍物,力扣的题目还会增加一些限制,使得题目难度增加。 这种题目一般都是力扣的困难难度。理解起来不难, 但是要完整地没有 bug 地写出来却不那么容易。 在该算法中,我们从起点开始,检查其相邻的四个方格并尝试扩展,直至找到目标。A 星寻路算法的寻路方式不止一种,感兴趣的可以自行了解一下。 公式表示为: f(n)=g(n)+h(n)。 其中: f(n) 是从初始状态经由状态 n 到目标状态的估计代价, g(n) 是在状态空间中从初始状态到状态 n 的实际代价, h(n) 是从状态 n 到目标状态的最佳路径的估计代价。 如果 g(n)为 0,即只计算任意顶点 n 到目标的评估函数 h(n),而不计算起点到顶点 n 的距离,则算法转化为使用贪心策略的最良优先搜索,速度最快,但可能得不出最优解;如果 h(n)不大于顶点 n 到目标顶点的实际距离,则一定可以求出最优解,而且 h(n)越小,需要计算的节点越多,算法效率越低,常见的评估函数有——欧几里得距离、曼哈顿距离、切比雪夫距离;如果 h(n)为 0,即只需求出起点到任意顶点 n 的最短路径 g(n),而不计算任何评估函数 h(n),则转化为单源最短路径问题,即 Dijkstra 算法,此时需要计算最多的顶点; 这里有一个重要的概念是估价算法,一般我们使用 曼哈顿距离来进行估价,即 H(n) = D * (abs ( n.x – goal.x ) + abs ( n.y – goal.y ) )。 (图来自维基百科 https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95 ) 一个完整的代码模板: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100grid = [ [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 1, 0], [0, 0, 0, 0, 1, 0],]\"\"\"heuristic = [[9, 8, 7, 6, 5, 4], [8, 7, 6, 5, 4, 3], [7, 6, 5, 4, 3, 2], [6, 5, 4, 3, 2, 1], [5, 4, 3, 2, 1, 0]]\"\"\"init = [0, 0]goal = [len(grid) - 1, len(grid[0]) - 1] # all coordinates are given in format [y,x]cost = 1# the cost map which pushes the path closer to the goalheuristic = [[0 for row in range(len(grid[0]))] for col in range(len(grid))]for i in range(len(grid)): for j in range(len(grid[0])): heuristic[i][j] = abs(i - goal[0]) + abs(j - goal[1]) if grid[i][j] == 1: heuristic[i][j] = 99 # added extra penalty in the heuristic map# the actions we can takedelta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # go up # go left # go down # go right# function to search the pathdef search(grid, init, goal, cost, heuristic): closed = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) ] # the reference grid closed[init[0]][init[1]] = 1 action = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) ] # the action grid x = init[0] y = init[1] g = 0 f = g + heuristic[init[0]][init[0]] cell = [[f, g, x, y]] found = False # flag that is set when search is complete resign = False # flag set if we can't find expand while not found and not resign: if len(cell) == 0: return \"FAIL\" else: # to choose the least costliest action so as to move closer to the goal cell.sort() cell.reverse() next = cell.pop() x = next[2] y = next[3] g = next[1] if x == goal[0] and y == goal[1]: found = True else: for i in range(len(delta)): # to try out different valid actions x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >= 0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost f2 = g2 + heuristic[x2][y2] cell.append([f2, g2, x2, y2]) closed[x2][y2] = 1 action[x2][y2] = i invpath = [] x = goal[0] y = goal[1] invpath.append([x, y]) # we get the reverse path from here while x != init[0] or y != init[1]: x2 = x - delta[action[x][y]][0] y2 = y - delta[action[x][y]][1] x = x2 y = y2 invpath.append([x, y]) path = [] for i in range(len(invpath)): path.append(invpath[len(invpath) - 1 - i]) print(\"ACTION MAP\") for i in range(len(action)): print(action[i]) return patha = search(grid, init, goal, cost, heuristic)for i in range(len(a)): print(a[i]) 典型题目1263. 推箱子 二分图二分图我在这两道题中讲过了,大家看一下之后把这两道题做一下就行了。其实这两道题和一道题没啥区别。 0886. 可能的二分法 0785. 判断二分图 推荐顺序为: 先看 886 再看 785。 总结理解图的常见概念,我们就算入门了。接下来,我们就可以做题了。 一般的图题目有两种,一种是搜索题目,一种是动态规划题目。 对于搜索类题目,我们可以: 第一步都是建图 第二步都是基于第一步的图进行遍历以寻找可行解 如果题目说明了是无环图,我们可以不使用 visited 数组,否则大多数都需要 visited 数组。当然也可以选择原地算法减少空间复杂度,具体的搜索技巧会在专题篇的搜索篇进行讨论。 图的题目相对而言比较难,尤其是代码书写层面。但是就面试题目而言, 图的题目类型却不多。 就搜索题目来说,很多题目都是套模板就可以解决。因此建议大家多练习模板,并自己多手敲,确保可以自己敲出来。 而对于动态规划题目,一个经典的例子就是Floyd-Warshall 算法,理解好了之后大家不妨拿 787. K 站中转内最便宜的航班 练习一下。当然这要求大家应该先学习动态规划,关于动态规划,我们会在后面的《动态规划》以及《背包问题》中进行深度讲解。 \b 常见的图的板子题有以下几种: 最短路。算法有 DJ 算法, floyd 算法 和 bellman 算法。这其中有的是单源算法,有的是多源算法,有的是贪心算法,有的是动态规划。 拓扑排序。拓扑排序可以使用 bfs ,也可以使用 dfs。相比于最短路,这种题目属于知道了就简单的类型。 最小生成树。最小生成树是这三种题型中出现频率最低的,可以最后突破。 A 星寻路和二分图题目比例非常低,大家可以根据自己的情况选择性掌握。","categories":[{"name":"图","slug":"图","permalink":"https://lucifer.ren/blog/categories/图/"}],"tags":[{"name":"图","slug":"图","permalink":"https://lucifer.ren/blog/tags/图/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"《算法通关之路》邀请你来试读","slug":"new-book","date":"2021-10-23T16:00:00.000Z","updated":"2023-01-05T12:24:49.539Z","comments":true,"path":"2021/10/24/new-book/","link":"","permalink":"https://lucifer.ren/blog/2021/10/24/new-book/","excerpt":"新书出版已经有一段时间了,也陆续收到了一些读者的反馈。今天咱就回答一些读者常见的问题以及《算法通关之路》一些内容剧透。 其实出版后已经有不少读者看完了并且给了非常优质的读后感。下面我挑选几个章节的优质留言给大家。","text":"新书出版已经有一段时间了,也陆续收到了一些读者的反馈。今天咱就回答一些读者常见的问题以及《算法通关之路》一些内容剧透。 其实出版后已经有不少读者看完了并且给了非常优质的读后感。下面我挑选几个章节的优质留言给大家。 读者留言 第 6 章二分法,虽然二分法是一个比较经典的算法,但对于大部分人一直是个头痛的问题。我每次刷 LeetCode 的时候,常常看不出来要使用二分法,或者知道要用二分法,但花了很长的时间在调试二分法的边界问题。通过学习此章很大程度上解决了我的烦恼,让我对二分法有了全方面的了解。它从二分法的经典问题开始讲起,再到后面的二分法的变种问题。其中详细介绍了什么时候用二分法,以及编写二分法的过程中所需要注意的边界问题。如果你也对二分法感到烦恼,十分推荐你阅读它,相信可以从这一章节学习到二分法的精髓。 第 7 章位运算,与很多经典算法相比位运算的算法普适性并不高,很多程序员对此并不熟悉,这一章节由一位音视频架构师主笔,音视频的处理里,充斥着大量对二进制数据的处理。听他的说法,每次看到位运算的问题,都有亲切的感觉,让人忍不住想看一看他的见解。人们习惯了使用十进制的计算规则,但如果能够有二进制的思维,能够将数据二进制话,然后运用位运算进行处理,一定能打开新的思路。 非常喜欢第 8 章设计这一章节,让我更深入的了解了高级数据结构的设计。从我入行开始,在经历的面试当中,算法(algos)和系统设计(system design) 基本是必考的两个方向,尤其是在面北美、欧洲的公司的时候。而且这两年发现,国内的巨头也开始使用算法题和系统设计题来作为面试的内容。对于要做“卷中王者”的我们来讲,这是必须要掌握的知识。这一章由浅入深地讲解了常见的几种高级数据结构。个人尤其喜欢对于 LRU、LFU 和 跳表的讲解,一步步让你去了解设计的缘由和取舍。其中的每个知识点,既可以出在算法的面试题里面,也可以作为 system design 的基础考点。另外值得一说的是,这一章节的时间复杂度的推理非常缜密,并且在延展部分给出了相关领域的论文。总而言之,很值得一看。 双指针和滑动窗口在 LeetCode 中是两个 tag,但本质上可以将滑动窗口看做双指针的特殊应用。本质上,可以将双指针看做对数组、链表、字符串的两个索引,依照这个思路,甚至可以出现多个索引的情况。而滑动窗口是利用两个索引,来完成两索引内的一系列操作。考虑到窗口的大小是否固定、窗口的起始位置等,可以对这类问题进行很多优化。本书的这两节,也都给出了不同的解题思路。非常推荐研究一下里面的题目。 说说公认最难的动态规划题目,这本书中不仅有专门的章节带你循序渐进的学习,还通过游戏、博弈和股票系列专题带你巩固基础,触类旁通。一直懵懵懂懂关于动态规划与其他几种算法思想的异同和关联,通过读完此书获得了不少答案。 在刷 leetcode 当中,以 分治法 作为 tag 的题目并不多,并且总和 dp、dfs 等算法知识混起来,增加理解难度。非常喜欢这本书对于分治的讲解,尤其是开头总结了在笔试、面试中可能面临的大部分分治类型题目,替我省了很多的精力。比如对于“合并 k 个排序列表”的题目,一步步从暴力法到最优解法,学习坡度变得平缓,而不是难度的陡增,非常推荐一观。 贪心法是一个最让我摸不着头脑的算法,每道题的题解都相差较大,很难找到一个共性的东西。 第十五章讲解了很多常见的贪心策略,例如问题拆解,限制条件等等,这些贪心策略让我面对贪心题目有了更加清晰的思路,以及更多的选择方向。此外,章节末尾还有对解题技巧的总结,这些技巧比较精炼,启发了我以后如何提高贪心题目的解题能力,非常贴心,推荐大家好好阅读这一章节。相比于其它算法,回溯法的算法思想比较固定,但怎么理解回溯法,快速应用回溯法是一个较大的难点。第十六章开篇就详细介绍了回溯法的解题模板,并在一道经典的组合问题上应用,让我对回溯法有了很直观的理解。同时,后文还讲解了不同背景下回溯法的应用题目,在提高应用模板能力的同时,学习到各种场景下的回溯技巧,相信能够在以后更加灵活地应用回溯模板,推荐大家阅读。 第十八章是我比较喜欢的一个章节,它总结了常见的解题模板,帮助我快速地学习各种套路,加深解题模板的理解。这些解题模板可以作为复习的材料,在面试或者笔试前重新理顺套路题目的思路。同时,解题模板来自于前面章节的内容,个人认为可以遮住代码,尝试根据套路背景自己编写代码,以此检验前面内容是否掌握。 非常感谢大家的认可,也希望大家拿到自己满意的 offer! 最后来回答几个读者感兴趣的问题。 1. 需要按照顺序看么?完全不需要。实际上,我也非常建议大家跳着看。优先看自己正在学习的内容。 比如你正在学习动态规划,可以直接看动态规划章节,股票章节以及游戏章节。比如你对复杂度分析不太懂就可以先看第一章。 我做了一个”脑图“给大家快速预览书的主要内容。如果大家实在不知道阅读顺序,推荐用这个脑图中的顺序从上到下看。 2. 我已经看了你的 Github 了,还需要这本书么?不管你是看了我的 Github(地址:https://github.com/azl397985856/leetcode),还是参加了我的 《91 天学算法》活动。我都强烈建议你购买一本。 原因有三: 虽然知识点还是那么多,几乎没有新增知识点。但是相同的主题,书的讲述方式和风格完全不同。书内容更加严谨,搭配 Github 和《91 天学算法》讲义进行学习效果更好。 比如书中二分章节中《153. 寻找旋转排序数组中的最小值》,这道题是一个很简单的二分。但是在证明复杂度的时候就使用了两种方法来证明。 这种严谨的态度贯穿整本书。通常来说,复杂的时间复杂度我也会给出分析,而不是直接贴出一个答案给大家。虽然不一定会像这个二分一样给出多种证明方式,但是也力求完整和准确。 简单来说,这本书在很大程度上都是和 Github 以及我的其他算法资料的补充,具体内容需要大家可以买到书后自己翻翻来体会啦。 很多题目由于我在书中做了讲解,因此就没有公开到 Github 等资料。因此如果你想看这些题目的题解就要通过书来看。 书的代码比较全。很多同学反馈想增加 xx 语言,但是确实没有太多精力增加语言。但是我给这本书所有的题目都增加了三种主流语言的代码,包括 Python,CPP 和 Java,另外部分题目也提供了 JS 版本。 最后强烈建议大家搭配《算法通关之路》来学习,尤其是那些参加《91 天学算法》的同学。 哦,对了!《91 天学算法》每个月都会在坚持每天打卡的人中抽取 3 人免费获取我们的《算法通关之路》! 《如何参加 91 天学算法(第五期)?》 https://lucifer.ren/blog/2021/08/21/91algo-5/ 3. 有试读版么?有的。https://leetcode-solution.cn/book-intro 这里有: amazon 官方提供的复杂度分析的部分试读 西法提供的设计章节的部分试读。 大家赶紧点 ta,感受书的全面和严谨吧~ (_^▽^_) 4. 有购书活动吗?西法打算增加一个活动。 规则是凡在活动期间购买《算法通关之路》并好评的同学截图给我,验证后可以免费参加当前一期《91 天学算法》。 注意只能免费参加购书时间当前一期哦,比如你是在第五期之后第六期结束前购买的,那就只能免费参加第六期的。 不过由于这一期已经开始了,现在搞这个活动会损害一部分已经参加了 91 活动并且买了我的书的朋友,因此西法打算下期再开启这个活动。 下期什么时候?大约在冬季(12 月份) 勘误首先,一些读者很热心地给我反应印刷错误。比如复杂度分析部分单调栈的代码是错误的,少个部分代码,应该是印刷问题,之前校验 word 的时候没有这个问题。大家可以访问官方网站的配套代码查看正确的代码。 官方网站地址:https://leetcode-solution.cn/book 如果大家发现其他问题也可以反馈到我这里,或者直接在读者交流群众反馈。","categories":[{"name":"《算法通关之路》","slug":"《算法通关之路》","permalink":"https://lucifer.ren/blog/categories/《算法通关之路》/"}],"tags":[{"name":"《算法通关之路》","slug":"《算法通关之路》","permalink":"https://lucifer.ren/blog/tags/《算法通关之路》/"}]},{"title":"聊聊刷题中的**顿悟**时刻","slug":"algo-fakers","date":"2021-10-15T16:00:00.000Z","updated":"2023-01-05T12:24:50.075Z","comments":true,"path":"2021/10/16/algo-fakers/","link":"","permalink":"https://lucifer.ren/blog/2021/10/16/algo-fakers/","excerpt":"和几位一直坚持刷题好朋友讨论了一下刷题的顿悟时刻,他们几位大都取得了不错的 offer,比如 Google 微软,Amazon,BAT 等。 通过和他们的沟通,我发现了大家顿悟时刻都是类似的。那具体他们有哪些相似点呢?我们一起来看下。","text":"和几位一直坚持刷题好朋友讨论了一下刷题的顿悟时刻,他们几位大都取得了不错的 offer,比如 Google 微软,Amazon,BAT 等。 通过和他们的沟通,我发现了大家顿悟时刻都是类似的。那具体他们有哪些相似点呢?我们一起来看下。 1. 同样的类型要刷很多才能顿悟比如你想顿悟二分,那么首先你需要做足够多的二分题。 而由于二分其实是一个大的分类。因此理论上你如果想对二分大类顿悟,那么必不可少的是先做足够多的二分题,并且这些题目可以覆盖所有的二分类型。 比如西法总结的基础二分,最左最右二分以及能力检测二分,其中大部分有点困难的题目都是能力检测二分。 对二分不熟悉的可以看下西法之前总结的《二分专题》: 几乎刷完了力扣所有的二分题,我发现了这些东西(上) 几乎刷完了力扣所有的二分题,我发现了这些东西(下) 那么推而广之,如果你想对刷算法题整体进行顿悟,那么就不得不先做足够多的题目,且这些题目能覆盖所有你想顿悟的考点。 这也就是说为什么你看的大佬中的大佬都刷了上千道题的原因。因为没有上千道题目的积累,你很难对所有题目类型都顿悟的。当然你如果只是应付大多数的考点并且不参与竞赛的话,也许小几百道也是 ok 的。 2. 回顾做过的题目有的同学比较直接,他们就是直接复习做过的题目。而有的同学则是通过做新的题目回想到之前做过的某些题,从而达到复习的作用。 不管是哪种类型。他们都必须经过一个阶段,那就是和已经做过的题目建立联系。如果你只是盲目做题的话,效率肯定上不去。 最开始刷题的时候,我会建立一些 anki 卡片。这其实就是为了强制回顾做过的题目。另外做新的题目的时候,我会强迫自己思考: 这道题考察了什么知识? 和之前做过的哪些题可以建立联系? 是否可以用之前刷题的解法套? corner case 有哪些? 。。。 经过这些思考,慢慢就会把做过的题目有机地结合起来,而不是让这些题目变成彼此的信息孤岛。 3. 对做过的题目进行抽象这个是我要讲的最后一点,但是这点却尤为重要,说它是最重要也不过分。 一方面,如果一道题目没有经过抽象,那么我们很难记住,很难在未来回忆起来。另一方面,如果一道题目能够抽象为纯粹的题目,那么说明你对这个题目看的比较透彻了。将来碰到换皮题,你一抽象,就会发现: 这不就是之前 xxxx 的换皮题么? 经常看我题解和文章的同学知道我之前写过不少换皮题的扒皮解析,这就是我做题和写文章风格。 在这里,我再举个例子。 注意:下面举的三道题例子都需要你掌握二分法的能力检测二分,如果不了解建议先看下我上面的文章。 Shopee 的零食柜这是 shopee 的校招编程题。 题目描述1234567891011121314151617shopee的零食柜,有着各式各样的零食,但是因为贪吃,小虾同学体重日益增加,终于被人叫为小胖了,他终于下定决心减肥了,他决定每天晚上去操场跑两圈,但是跑步太累人了,他想转移注意力,忘记痛苦,正在听着音乐的他,突然有个想法,他想跟着音乐的节奏来跑步,音乐有7种音符,对应的是1到7,那么他对应的步长就可以是1-7分米,这样的话他就可以转移注意力了,但是他想保持自己跑步的速度,在规定时间m分钟跑完。为了避免被累死,他需要规划他每分钟需要跑过的音符,这些音符的步长总和要尽量小。下面是小虾同学听的歌曲的音符,以及规定的时间,你能告诉他每分钟他应该跑多少步长?输入描述:输入的第一行输入 n(1 ≤ n ≤ 1000000,表示音符数),m(1<=m< 1000000, m <= n)组成,第二行有 n 个数,表示每个音符(1<= f <= 7)输出描述:输出每分钟应该跑的步长示例1输入8 5 6 5 6 7 6 6 3 1输出11 链接:https://www.nowcoder.com/questionTerminal/24a1bb82b3784f86babec24e4a5c93e0?answerType=1&f=discussion来源:牛客网 思路经过抽象,这道题本质上就是给你一个数组(数组值范围是 1 到 7 的整数),让你将数组分为最多 m 子数组,求 m 个子数组和的最小值。 直接回答子数组和最小值比较困难,但是回答某一个具体的值是否可以达到相对容易。 比如回答子数组和最小值为 100 可以不可以相对容易。因为我们只需要遍历一次数组,如果连续子数组大于 100 就切分新的一块,这样最后切分的块数小于等于 m 就意味着 100 可以。 另外一个关键点是这种检测具有单调性。比如 100 可以,那么任何大于 100 的数(比如 101)肯定都是可以的。如果你看过我上面的《二分专题》或者做过不少能力检测二分的话, 不难想到可以利用这种单调性做能力检测二分得到答案。并且我们要找到满足条件的最小的数,因此可以套用最左能力检测二分得到答案。 代码暂时不写,因为这道题和后面的一道题是一样的。 410. 分割数组的最大值题目描述12345678910111213141516171819202122232425262728给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。 示例 1:输入:nums = [7,2,5,10,8], m = 2输出:18解释:一共有四种方法将 nums 分割为 2 个子数组。 其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。示例 2:输入:nums = [1,2,3,4,5], m = 2输出:9示例 3:输入:nums = [1,4,4], m = 3输出:4 提示:1 <= nums.length <= 10000 <= nums[i] <= 1061 <= m <= min(50, nums.length) 来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/split-array-largest-sum 思路这道题官方难度是 hard。和前面题抽象后一模一样,不用我多解释了吧? 你看经过这样的抽象,是不是有种殊途同归的顿悟感觉? 代码代码支持:Python3 Python3 Code: 123456789101112131415161718192021class Solution: def splitArray(self, nums: List[int], m: int) -> int: lo, hi = max(nums), sum(nums) def test(mid): cnt = acc = 0 for num in nums: if acc + num > mid: cnt += 1 acc = num else: acc += num return cnt + 1 <= m while lo <= hi: mid = (lo + hi) // 2 if test(mid): hi = mid - 1 else: lo = mid + 1 return lo 你以为这就完了么? 类似的题目简直不要太多了。西法再给你举个例子。 LCP 12. 小张刷题计划题目描述1234567891011121314151617181920212223242526272829为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。示例 1:输入:time = [1,2,3,3], m = 2输出:3解释:第一天小张完成前三题,其中第三题找小杨帮忙;第二天完成第四题,并且找小杨帮忙。这样做题时间最多的一天花费了 3 的时间,并且这个值是最小的。示例 2:输入:time = [999,999,999], m = 4输出:0解释:在前三天中,小张每天求助小杨一次,这样他可以在三天内完成所有的题目并不花任何时间。 限制:1 <= time.length <= 10^51 <= time[i] <= 100001 <= m <= 1000 来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/xiao-zhang-shua-ti-ji-hua 思路和前面的题目类似。经过抽象,这道题本质上就是给你一个数组(数组值范围是 1 到 10000 的整数),让你将数组分为最多 m 子数组,每个子数组可以删除最多一个数,求 m 个子数组和的最小值。 和上面题目唯一的不同是,这道题允许我们在子数组中删除一个数。显然,我们应该贪心地删除子数组中最大的数。 因此我的思路就是能力检测部分维护子数组的最大值,并在每次遍历过程中增加判断:如果删除子数组最大值后以后可以满足子数组和小于检测值(也就是 mid)。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122232425class Solution: def minTime(self, time: List[int], m: int) -> int: def can(mid): k = 1 # 需要多少天 t = 0 # 当前块的总时间 max_time = time[0] for a in time[1:]: if t + min(max_time, a) > mid: t = 0 k += 1 max_time = a else: t += min(max_time, a) max_time = max(max_time, a) return k <= m l, r = 0, sum(time) while l <= r: mid = (l+r)//2 if can(mid): r = mid - 1 else: l = mid + 1 return l 时间复杂度的话三道题都是一样的,我们来分析一下。 我们知道,时间复杂度分析就看执行次数最多的代码即可,显然这道题就是能力检测函数中的代码。由于能力检测部分我们需要遍历一次数组,因此时间为 $O(n)$,而能力检测函数执行的次数是 $logm$。因此时间复杂度都是 $nlogm$,其中 n 为数组长度,m 为数组和。 总结顿悟真的是一种非常美妙的感觉,我通过采访几位大佬发现大家顿悟的经历都是类似的,那就是: 同样的类型要刷很多才能顿悟 回顾做过的题目 对做过的题目进行抽象 对第三点西法通过三道题给大家做了细致的讲解,希望大家做题的时候也能掌握好节奏,举一反三。最后祝大家刷题快乐,offer 多多。","categories":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"用什么语言刷题最有排面?","slug":"programming-idioms","date":"2021-10-09T16:00:00.000Z","updated":"2023-01-05T12:24:49.892Z","comments":true,"path":"2021/10/10/programming-idioms/","link":"","permalink":"https://lucifer.ren/blog/2021/10/10/programming-idioms/","excerpt":"很多读者向西法反应:“能不能在专题和题解里面增加 xx 语言”。 我的回答一直都是:我会尽量添加多种编程语言,但也请你不要依赖于某种具体的编程语言来学算法。 熟悉我的朋友都知道,我刚开始的时候,我使用 JS 比较多,之后使用 Python 比较多,现在也在陆续更新题解的 C++ 语言,之后会考虑其他的语言。接下来的话应该是优先考虑 Java,让大家学习曲线更平滑。 但是另一方面我也强烈建议大家不要依赖于某一种具体地编程语言。我当然知道学习和切换编程语言需要时间,也体会到使用自己熟悉的编程语言效率更高。","text":"很多读者向西法反应:“能不能在专题和题解里面增加 xx 语言”。 我的回答一直都是:我会尽量添加多种编程语言,但也请你不要依赖于某种具体的编程语言来学算法。 熟悉我的朋友都知道,我刚开始的时候,我使用 JS 比较多,之后使用 Python 比较多,现在也在陆续更新题解的 C++ 语言,之后会考虑其他的语言。接下来的话应该是优先考虑 Java,让大家学习曲线更平滑。 但是另一方面我也强烈建议大家不要依赖于某一种具体地编程语言。我当然知道学习和切换编程语言需要时间,也体会到使用自己熟悉的编程语言效率更高。 很多读者像我反应:“能不能在专题和题解里面增加 xx 语言”。 我的回答一直都是:我会尽量添加多种编程语言,但也请你不要依赖于某种具体的编程语言来学算法。 熟悉我的朋友都知道,我刚开始的时候,我使用 JS 比较多,之后使用 Python 比较多,现在也在陆续更新题解的 C++ 语言,之后会考虑其他的语言。接下来的话应该是优先考虑 Java,让大家学习曲线更平滑。 但是另一方面我也强烈建议大家不要依赖于某一种具体地编程语言。我当然知道学习和切换编程语言需要时间,也体会到使用自己熟悉的编程语言效率更高。 但问题是我们无法控制大环境。据我所知,经常更新文章的大佬使用的编程语言语法么都有,C++,Java,Python 都有的。 如果因为语言看不懂跳过,那么肯定会错过很多优秀的文章和题解。你说是不是很可惜? 那问题是我真的看不懂怎么办?一个超级有效的方式就是使用不同的编程语言刷题。比如你就定一个小目标比如用 C++刷 100 道题,这样慢慢你就对 C++ 的最最基础的特性了解了,这样下次看到别人的 C++ 题解你在看看,是不是能看懂了?这是因为大家刷题很少用一些高深的语言特性,尤其是那些大佬的题解,它们会注意这些的。因此你稍微练习一下,基本上以后看其他语言的题解就不成问题的。 很多人刚开始切换到其他编程语言会深深地感到不适应。比如怎么创建一个数组呢?怎么反转一个数组?怎么新建一个哈希表?等等等 一个个查真的是效率很低,以至于很多人都坚持不下来。 我其实刚刚在用新语言的时候也是一样的,今天介绍的网站就整理了很多常见操作的不同语言对比实现 以 C++ 的 reverse 为例: 你可以点击上面的编程语言查看其他语言的 reverse 是如何实现的,目前该网站已经提供了 277 个语言特性,这个工具网站对那些刚开始学习新语言的人非常有用。 我们甚至可以直接开启对比模式,以 Python 和 C++ 对比为例: 地址:https://programming-idioms.org/idiom/19/reverse-a-list 最后回答一个相关的问题,读者问的也比较多。那就是:有没有推荐的刷题语言? 其实这个问题之前回答过,今天再讲一次。一句话回答就是:建议选择一门动态语言和一门静态语言,比如选择 Python 和 C++。 原因是什么呢? 刷题以及打比赛都讲究速度,天下武功唯快不破。 这个快,一方面是运行速度快,另一方面是编码速度快。你可以看出很多人刷题,打比赛都会不断切换语言的。我们要承认不同语言效率是不一样的,这个效率可能是执行,也可能是编码。具体使用哪种语言,看你的需求。 论编码速度,那肯定动态语言快,论执行速度那肯定静态语言快。 所以我的建议是大家至少掌握一静一动,即掌握一个动态语言,一个静态语言。 我个人动态语言用的 Python 和 JS,静态语言用的 Java 和 CPP,大家可以作为参考。 一个小建议是你选择的语言要是题解比较热门的。那什么语言是热门的?其实很容易。力扣题解区,语言排名高的基本就是了,如下图: 掌握语言不仅能帮助你在效率中运用自如,并且还容易看懂别人的题解。除此之外还有一个用,那就是回头复习的时候用。拿我来说, 我会不固定回去刷以前做过的题,但是一道题做过了就没新鲜感了,这个时候我就换个语言继续刷,又是一番滋味。","categories":[],"tags":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"写注释就能自动出代码?copilot 尝鲜","slug":"copilot","date":"2021-10-04T16:00:00.000Z","updated":"2023-01-07T12:20:39.959Z","comments":true,"path":"2021/10/05/copilot/","link":"","permalink":"https://lucifer.ren/blog/2021/10/05/copilot/","excerpt":"copilot 是一个基于 AI 的编程辅助工具。目前已经集成在了 vscode 中,后续可能集成到更多平台和工具,目前还是测试阶段。官网地址:https://copilot.github.com/","text":"copilot 是一个基于 AI 的编程辅助工具。目前已经集成在了 vscode 中,后续可能集成到更多平台和工具,目前还是测试阶段。官网地址:https://copilot.github.com/ 支持所有语言copilot 是利用网络中现有的公开数据,尤其是开源在 Github 上的代码, 然后基于机器学习算法训练出来的。因此 copilot 理论上支持所有编程语言。 目前我测试了 JS 和 Python,效果都还蛮不错的。官方提供了 ts,go,py 和 rb 语言的示例。 注释即代码你可以通过编写注释然后一路根据 copilot 的提示编写出完整的程序。 比如我想根据 Github 用户名获取用户信息。我只需要写下这样一行注释。以 JS 为例: 1// 根据 Github 用户名获取用户信息 copilot 是如何一步步引导你完成完整功能的呢?我们来看下。 第一步: 注意:注释下面的代码颜色是浅色的,是 copilot 提示出来的。下同,不再解释。 按下 tab 键就会浅色的代码就会被填充,并提示接下来的代码。 第二步: 再次按下 tab 键,整体的代码就生成了。 类似的例子还有很多,等待大家来探索。 代码补全IDE 的一个很重要的功能就是代码补全。 copilot 增强了 IDE 的补全功能。 copilot 可以根据你的代码仓库以及世界上公开的代码仓库提示你可能的输入,从而减少你敲击键盘的次数,在更短的时间写出更多的代码,获取更多的摸鱼时间。 举个例子,仍然以 JS 为例。我想发送一个 fetch 请求。 12fetch('https://www.leetcode-solution.cn', { 它就提示我: 接下来按照它的提示,只按 tab 不写代码的情况就可以写出如下代码。 12345678910111213fetch(\"https://leetcode-solution.cn\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\", }, body: JSON.stringify({ question_id: \"1\", lang: \"javascript\", code: \"console.log(1)\", }),}).then((res) => { console.log(res);}); 对我的仓库功能来说, 上面代码有一小部分是有问题的。 不过我只需要稍微改改就行了。效率提升还是不错的。 如何使用?在 vscode 插件市场搜索 github copilot,点击 install,然后按照提示安装即可。 安装好了就可以体验了! 写写注释?敲敲代码?按按 tab?代码 duang 的一下就生成了。 总结copilot 是一个类似 tabnine 的 ai 编程辅助工具,目前以 vscode 插件的形式提供免费服务,目前是测试阶段,还没有最终发行。它有自动提示,根据注释写代码等诸多激动人心的功能。 更多功能以及最新动态请访问官方网站:https://copilot.github.com/","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"VSCODE","slug":"工具/VSCODE","permalink":"https://lucifer.ren/blog/categories/工具/VSCODE/"}],"tags":[{"name":"AI","slug":"AI","permalink":"https://lucifer.ren/blog/tags/AI/"},{"name":"VSCODE","slug":"VSCODE","permalink":"https://lucifer.ren/blog/tags/VSCODE/"}]},{"title":"有了这个可视化插件,刷题调试更轻松","slug":"algo-vis","date":"2021-09-25T16:00:00.000Z","updated":"2023-01-05T12:24:49.527Z","comments":true,"path":"2021/09/26/algo-vis/","link":"","permalink":"https://lucifer.ren/blog/2021/09/26/algo-vis/","excerpt":"如何有效学习算法学习算法的基本思路就是:先学习算法思想,然后通过做题消化思想,并在做题过程中慢慢学习,掌握一些小技巧。这其中算法思想就是道,而经典题目以及做题技巧就是术。做题是通过术来完善道。 但是很多人都反应看讲义和做题之间断层严重,也就是一看就会,一些就废。这怎么办呢?","text":"如何有效学习算法学习算法的基本思路就是:先学习算法思想,然后通过做题消化思想,并在做题过程中慢慢学习,掌握一些小技巧。这其中算法思想就是道,而经典题目以及做题技巧就是术。做题是通过术来完善道。 但是很多人都反应看讲义和做题之间断层严重,也就是一看就会,一些就废。这怎么办呢? 除了多写,多练习之外,我认为以下两点可以帮助你: 做题的时候和讲义(学习资料)进行结合 这是一个很重要的也容易被忽略的点。拿《91 天学算法》来说:看讲义就是学思想,每日一题就是巩固消化思想。做每日一题的时候,要多往讲义上靠靠,比如想一下这道题对应讲义哪一部分,考察的是讲义中提到的哪一个知识点。 看讲义(学习资料)的时候将例题用可视化的方式自己跑一遍 我刚开始学习算法的时候,基本上也是这种思路。学习完思想做题的时候对例题都在电脑或者纸上画一下代码执行流程,然后和学习的算法思想进行结合。这样不仅算法思想容易吸收,而且也收效缓解了一看就会,一写就废的尴尬境地。 但是毕竟自己画图还是有点成本的,不是所有的人都有动力自己画图的。程序员都很懒,其实我刚开始刷题的时候一直有一个想法, 如果做题有可视化显示该有多好?最好是和我讲义图类似的那种, 这样无疑对新手来说吸收思想效率肯定高。 可视化调试插件无巧不成书,前几天《91 天学算法》群里有人提到 LeetCode 刷题调试。大家有的用 IDE 调试,有的用会员的调试功能在网页调试。 其实前一阵子我分享刷题技巧的时候也分享了调试插件,没有看过的同学可以看下 力扣刷题的正确姿势是什么?。 今天再分享一个适合新手的调试工具,简单易用,直观方便。更关键的是,其已经内置到我的刷题插件 leetcode-cheatsheet 中,直接开箱即用,插件版本大于等于 0.9.0 即可。虽然它暂时还无法自动生成像我讲义里面那么完整的图和动画,但是比文字要直观太多了。后期考虑集成更多的语言以及更多的语法特性以及更好的展示效果。 该使用方式非常简单,完全满足了大家偷懒的需求。你只需要: 安装刷题插件 leetcode-cheatsheet 插件如何下载与安装可以在公众号回复插件获取 打开 leetcode 中任意一道题目,写代码。 目前支持 Python3,CPP,JavaScript 点击下方的可视化调试 按提示修改代码后点击Visualize Execution按钮 如果无法修改代码,可以先点击 edit code 这里我就想吐槽一下 leetcode 了。干嘛每一道题函数名字都不一样,真没这个必要。比如都叫 solve 不好么?希望力扣可以考虑一下这个建议。 通过控制区域控制代码执行,右侧会自动同步的可视化地显示变量信息 最后友情提示一下。可视化调试推荐在看资料(比如 91 天学算法的讲义)的时候把其中的例题用可视化的方式调试一遍,填平思路到代码的鸿沟。 之后大家做题不要依赖调试功能,而是先在大脑中调试一下,然后用工具验证。也就是说这个工具,我仅推荐你在两种情况下使用: 看算法思想资料,做其中的例子的时候一步步调试学习。 代码有 case 跑不通,先在脑子中过一下,猜测大概出问题的点,然后用工具直接定位到附近通过可视化的方式帮助你分析。 最后大家有什么想要的 feature 可以给我公众号后台或交流群里留言。","categories":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"},{"name":"算法","slug":"插件/算法","permalink":"https://lucifer.ren/blog/categories/插件/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"}]},{"title":"五天100+题,怒进谷歌!","slug":"91algo-interview-kongshi","date":"2021-09-23T16:00:00.000Z","updated":"2023-01-05T12:24:49.831Z","comments":true,"path":"2021/09/24/91algo-interview-kongshi/","link":"","permalink":"https://lucifer.ren/blog/2021/09/24/91algo-interview-kongshi/","excerpt":"背景空识拿到了Google 的 offer,和狗头做起了同事。 他一个礼拜刷了 100 多道题,然后面试 Google 终于通过了。这个经历还是蛮具有代表性的,分享给正在准备面试的你。 ​","text":"背景空识拿到了Google 的 offer,和狗头做起了同事。 他一个礼拜刷了 100 多道题,然后面试 Google 终于通过了。这个经历还是蛮具有代表性的,分享给正在准备面试的你。 ​ 以下 Q 为 lucifer,A 为 空识。 采访Q: 你是什么时候开始接触数据结构与算法(以下简称算法)的? A: 大概是十年前读本科的时候……但是没有系统性的学习,只了解过一些特殊用途的算法(X 算法,RSA 算法,…)打算转码的时候才开始重头系统性的学习。 Q: 你是什么时候接触 91 天学算法(以下简称 91 天)的?从什么途径得知的? A: Github 上的题解->西法的公众号,在公众号里了解到的。 Q: 91 天有给你带来了什么样的变化么? A: 我变秃了,也变强了。从一开始看到 two sum 觉得精妙,周赛做不出 easy 到现在能 稳定写 2-3 题。一开始 oa 撞原题都写不好到现在不少 oa 直接做也能拿到面试机会了。 尤其印象深刻的是 一开始自己刷题,easy 可能都要磨蹭个 2-3 小时研究相关的 topic 搞明白题解,最后一周刚好要准备狗厂面试,五天就刷了 100+题…… 真的变秃了。 Q: 学习算法过程中有“顿悟”的时刻么? A: 有呀,正如六祖慧能在《坛经》里说到的,“迷闻经累劫,悟则刹那间”,在各种重复思考卡壳之后再去看题解的点悟往往会有那种开了一扇门的感觉,然后就悟了哈哈。 Q: 你比较擅长的算法是什么?可以给大家简单分享一下么? A: 其实是数学法,因为本身是学数学相关方向的,以前经常被同学分享这种很巧的题,所以练习得比较多。 Q: 有没有什么想和刚入坑算法的同学分享的? A: 一定一定一定一定要坚持不要放弃,每天再难至少要刷 1 ~ 3 题(具体看是否脱产),每天发掘一个小技巧,91 天就能学会很多很多东西。而且尤其是一开始,easy/medium 的题都刷的很艰难需要很多时间,这个时候很容易会怀疑自己的能力/天赋,但是千万不要绝望不要放弃,因为绝望/放弃其实没有任何作用,咬紧牙关坚持下去是最重要的,超越了觉悟,就能看到希望。 另外还有就是万事从简,我最早转码纠结于以后到底该做什么纠结了很久,其实这个回头看,其实都要学的,直接捡一个比较主流找得到不少题解的语言开始最重要。 Q: 相对而言,你觉得 91 天哪里做的还不够好?应该如何改进? A: 其实我觉得挺好的……感觉可能硬性要求开始的时候多打点卡比较好……?因为感觉一开始人比较容易有惰性,不想走出舒适区,可能需要一点 push。 Q: 有没有什么面试和刷题技巧或建议给大家 A: 我觉得很重要的几个点: 力扣官方有很好的 explore 卡片,那个可以很好的作为基础篇的补充,针对性的刷题可以帮你形成你自己的理解,有理解了刷题就快了。 力扣的公司 tag 是可以按照频率选题的,刷高频题通常能够给你更直观的一个关于“这个公司到底是在考什么”的体验。 打周赛,其实多少分不重要,能不能涨分也不重要,重要的是思路。 所以你可以随便打开任何一期周赛,然后用力扣网站的模拟面试功能打。然后设立个目标,目标应该是差不多一个小时做二望三。 Think out loud。面试的时候沟通很重要,所以一定要学会自言自语,写完主动手动跑一遍,想一次 edge case,然后算一遍复杂度,可以的话甚至可以想一想这个题可以怎么改编怎么 followup。 注:北美的小伙伴我推荐用 interviewing.io,我觉得练习很有效…… 一定一定一定一定要自信,振作起来,生活不容易,你是最棒的。 lucifer 总结空识最后给的五个建议都非常的实用,手动点赞。另外空识的建议已经收到了。我们后期会加大 push 力度,努力营造一个积极向上的刷题氛围,fighting! 此外,空识提到了很多次打模拟面试或者周赛。其实我也建议大家过一遍 tag 后就先打 20 次周赛,做几道题都不重要,就是锻炼自己的做题能力以及给定压力下的思考反应等。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"《算法通关之路》出版啦!","slug":"leetcode-solution-book","date":"2021-09-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.839Z","comments":true,"path":"2021/09/04/leetcode-solution-book/","link":"","permalink":"https://lucifer.ren/blog/2021/09/04/leetcode-solution-book/","excerpt":"其实很多朋友私底下问我: 新书什么时候出版? 可以预定么? 等等 其实我比大家更着急,只不过出版图书真的是一个非常严谨的过程。不比专栏,小册等电子读物可以一边上架一边修改。传统的纸质图书的要求和流程都是严格把控的。因此只能耐心等待和配合出版社。 而现在《算法通关之路》终于要和大家见面了!🌹🌹🌹","text":"其实很多朋友私底下问我: 新书什么时候出版? 可以预定么? 等等 其实我比大家更着急,只不过出版图书真的是一个非常严谨的过程。不比专栏,小册等电子读物可以一边上架一边修改。传统的纸质图书的要求和流程都是严格把控的。因此只能耐心等待和配合出版社。 而现在《算法通关之路》终于要和大家见面了!🌹🌹🌹 不少同学都早就已经预定了,甚至有的预定了几十倍,真的是让西法感动啊! 以下是部分给我发邮件预定的同学: 虽然内容早就已经定得差不多了,但是中间的过程异常曲折,总之就是各种事情,不过好在现在已经出版了。感谢大家的鼓励和支持,不然我恐怕很难坚持下来 来秀一张新书的封面给大家看看。 那这本书里面都讲了什么干货呢?这里西法给大家做一个简单的介绍。详细目录大家可以扫描文章末尾的二维码查看。 第 1 章对一些基础的数据结构和算法进行了总结与梳理,同时介绍了常见的衡量程序性能好坏的方法——大 O 表示法。 第 2 章是数学专题。很多算法题目需要你具备一些数学知识才能解决,那么需要的数学知识有哪些,难度如何?本章将会告诉你。 第 3 章讲的是一个经典的算法问题——回文问题。 第 4 章是游戏专题。游戏专题从问题角度可以分为求解和博弈两大类,博弈类的问题将在第 12 章专门分析,本章将重点关注求解类游戏。 第 5 章介绍了两种常见的搜索算法——深度优先遍历和广度优先遍历。本章将告诉你两种搜索各自的特点是什么,适合解决什么问题。 第 6 章将对二分法进行讲解,包括其基本形式、解题技巧及算法模板等。 第 7 章讲的是位运算,旨在让读者从二进制的角度思考问题。 第 8 章讲的是设计题,学习本章内容需要读者对常见的数据结构足够熟悉。 第 9 章对两种常见的双指针进行了详细的讲解。 第 10 章对经典的算法——动态规划循序渐进地进行了细致的剖析,并介绍了一种空间优化的方法——滚动数组。 第 11 章讲的是滑动窗口。这种算法使用两个指针界定窗口左右边界,并统计窗口内的信息。当窗口发生滑动时,仅考虑窗口变化的部分,最大化利用已有的运算结果,从而降低时间复杂度。 第 12 章讲的是博弈问题。这一类问题出现的频率同样很高,仅与石子游戏相关的问题就在力扣(LeetCode)中出现了很多次。博弈问题虽然没有固定的思维方法,但也有一些规律可循。 第 13 章讲股票问题,其属于动态规划的子问题。建议读者在看完第 10 章动态规划之后再来阅读本章内容。 第 14 章和 15 章分别讲的是分治法和贪心法。这两个专题和动态规划类似,难度上限都很高,也不容易掌握。这两章从几个具体的例子入手, 帮助读者厘清贪心法和分治法问题的适用场景及解题策略。 第 16 章则是对第 5 章内容的扩展,介绍了另一种常见的搜索算法——回溯法。回溯法是什么?如何利用回溯法来解决具体的算法问题?回溯法的代码如何书写?回溯程序如何优化?本章将告诉你答案。 第 17 章则是作者精选的几个有意思的题目,在这里分享给读者。 第 18 章是一些解题模板,是对前面内容的提炼,建议读者在阅读相应专题之后再来查看本章相应的模板。模板的意义在于提高解题速度,降低错误率,而不是被用来生搬硬套的,这一点读者要格外注意。 第 19 章提供了尽可能多的解法来拓展读者的思维,这与前面 18 章的做法不同。为了不影响阅读,前面的 18 章内容都是对单一的知识点进行讲解,同时为了和其内容匹配,有时也会放弃最优解而选择与本章内容匹配的解法。 第 20 章分享了一些作者认为非常不错的解题技巧。 新书就先秀到这里。 接下来就这本书在这里回答几个大家比较关心的问题。让我们进入 Q&A 环节吧! Q&A Q1:这本书是什么编程语言? A1: Python。不过我提供了配套网站。全部代码都提供了 Java,CPP,Python 三种代码,因此如果你不熟悉 Python,而只需要 Java 或者 CPP 也完全没有问题。另外部分题目还提供了 JS Code,后面我们也可能会根据读者的反馈增加其他语言。 本书配套网站地址:https://leetcode-solution.cn/book Q2:书的内容是 Github 仓库和公众号的内容么? A2:很多读者都是从我的 Github 过来的, Github 也提供了电子书版本。 Github 地址:https://github.com/azl397985856/leetcode 那么 Github 的电子书中的内容会和这本书重叠么? 答案是几乎没有任何重叠。本书内容几乎都是不曾公开的全新内容,大家不用担心买了一本开源书。 Q3:这本书适合小白么? A3:这本书就是为想科学高效刷题的人量身打造的。阅读这本书适合懂至少一门编程语言,能将思路转化为代码,并且了解常见数据结构人。如果你是这样的人,就可以买来阅读。 Q4:这本书上限高么?我想提高一下自己。 A4:这本书上限不高,难度基本上覆盖力扣中的简单,中等以及部分困难。也就是说看懂这本书可以解决大部分力扣题目。这种程度不足以应付算法比赛的,但是应付面试足以。 如果你还有什么问题,都可以给我留言。我会尽可能地回答大家~ ღ( ´・ᴗ・` )比心 粉丝福利五折优惠目前还是预售阶段,我给公众号的读者争取了一波福利,大家可以以更优惠的价格进行购买。 新书定价是 99 元,但是我帮大家争取到了五折优惠, 49.5 元就可以拿下了,另外前一万本书会附赠力扣的会员优惠券。 想入手的朋友现在入手非常划算,扫描下方二维码(或者使用这个链接 https://u.jd.com/gKbUGbR)就可以购买了。 免费送书另外力扣加加在粉丝中抽三位免费送!后续会不定期在这个号上抽奖送书,大家可以关注一下! 参与方式请仔细阅读哦: 在我的公众号力扣加加后台,发送【抽奖】这 2 个字(不加任何符号 or 表情),即可参与抽奖。 点击关注上方账号,回复【抽奖】即可参与 提醒下哦:是在公众号后台哦,不是在这评论区、不是这号后台发消息、也不是发微信哈。 9 月 9 日 12:00 自动开奖,开奖后微信会自动通知。 抽奖由第三方平台开奖,抽奖、兑奖过程中有任何疑问请添加小秘书微信(微信号:wxid_d5q3rgueie4r22) 另外公众号脑洞前端也在做同样的抽奖活动,在脑洞前端后台回复抽奖同样可以参与抽奖哦。两个号都参与,中奖率翻倍!","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/categories/书/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/tags/书/"}]},{"title":"入职一个月,远程办公感受如何?","slug":"wfh","date":"2021-09-03T16:00:00.000Z","updated":"2023-01-07T12:34:34.657Z","comments":true,"path":"2021/09/04/wfh/","link":"","permalink":"https://lucifer.ren/blog/2021/09/04/wfh/","excerpt":"上一次给大家讲了我现在已经全职远程办公(简称 wfh),算起来有超过一个月的时间了。很多同学对此比较感兴趣,我们就来聊聊我最近工作如何。","text":"上一次给大家讲了我现在已经全职远程办公(简称 wfh),算起来有超过一个月的时间了。很多同学对此比较感兴趣,我们就来聊聊我最近工作如何。 未经审视的人生不值得过。我认为我属于那种经历逆境比较少的人,印象中最大的逆境似乎就是科目三考了好几次才过?但很多时候,反推力才是人类前进的最直接、最有效的动力。因此我的决断就是自己尝试一下不同的选择,拒绝一成不变的老年生活。关于这些,我尝试了很多东西,其中就包括了远程办公,这时候有时间再给大家细聊。 我经常会思考自己想要什么样的生活。 那当然是幸福的生活啦。 我想了一下,如果要实现幸福,最主要的有这两个方面: 有充足的物质保障。可以做自己喜欢的事情,而不必为了某些原因做一些自己不喜欢的东西 受人尊敬 似乎每一个都很难。不过这才有意义嘛。 为了做到第一点,可以从两方面入手,一个是提高收入,一个是降低开支。也就是开源节流。提高收入的话最简单的就是增加自己的能力提高工资,拓宽收入的来源,还有就是提高理财能力。而节流的话就是控制自己的欲望,目前我的方法主要是延迟满足,每个月给自己设定一个开支计划,并且只有完成每个月的任务的百分之多少才能去花。 另外一点就是可以考虑去生活成本相对较低的城市,比如我的老家。我认为选择远程办公至少在节流上做到了。另外长期来看,远程办公可以对标更高的工资标准,因此长期上开源也做到了。另外选择的公司也相对节奏慢,这样我可以做一些其他副业拓宽自己的收入渠道。比如我会去做一些培训,写一些教程,书籍之类的。 为了做到第二点,可以提高自己的影响力,并真心地为他人着想。积极乐观面试生活,不断倾听和观察,必要时给予帮助。这其实并不是我的强项,只不过在慢慢改变罢了,这我就不展开说了。 现在的工作很自由回到开头的话题:聊聊我最近工作如何。 简单来说这一段时间的 wfh 给我的最大感受就是自由。我可以自由安排自己的工作时间。不仅仅是我,我的同事都是这样的一个状态。 以我为例,我就可以经常在“大家的工作时间”做一些和工作无关的事情(摸鱼?)。比如健身环大冒险,看看电影,逗逗宠物等等。 很多人问我:“wfh 会不会工作和生活没有界限,导致 007 的出现?” 我的回答是这取决于公司文化,如果是某些公司确实很有可能,而一些人性化的公司是大可不必担心这样的事情发生。 大家更关注的是你的成果,而不是关心你的过程。只要你不是很过分,基本就不会有问题。比如每次线上找你都需要好久才能响应,这就比较过分了。 如果暂时不能响应,只需要在工作软件上设置一个“离开状态”即可,最好加一个时间。比如“下午三点前勿扰” 另外外企假期也会有很多,年假和病假每年都有十多天,老员工会更多。你请个假出去玩回来也完全不影响工作。其他各种福利更不用说了。 在这里你只需要思考如何把工作做好就行了。大家不关心你是在家思考的方案还是在公园思考的方案。如果你效率高很快就完成了任务,那就可以自由安排你的时间。不需要像很多公司下班了不肯走,打死不做第一个走的人,假装工作实则摸鱼。 这种真实的感觉真好! 工作也很有挑战性目前在做的是公司内部开发引擎,具体细节就不透露了。总之这算是一个很有挑战性,需要有一定的经验和跨专业能力。经常要做一些技术调研以及技术储备。另外也会时不时和业界的一些开源大佬(具体就不透露了,非常著名的那种就是了)探讨一些技术问题。 内推你如果也想要这样的远程办公(wfh),工作和生活平衡(work life balance) 的工作,可以找我内推,大家加我的微信就好(可以提供免费面试咨询哦) 加好友请注明:内推","categories":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/categories/成长经历/"}],"tags":[{"name":"成长经历","slug":"成长经历","permalink":"https://lucifer.ren/blog/tags/成长经历/"}]},{"title":"github 又出新功能,布局云端 vscode?","slug":"vscode-dev-codespaces","date":"2021-09-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.942Z","comments":true,"path":"2021/09/02/vscode-dev-codespaces/","link":"","permalink":"https://lucifer.ren/blog/2021/09/02/vscode-dev-codespaces/","excerpt":"vscode 和 github 是微软的两大开源产品,二者在业界的影响力都是巨大的。 近日 Github 新出了一个功能,用户可以通过将 url 中的 .com 替换为 .dev 来直接打开在线版的 vscode 编辑器。","text":"vscode 和 github 是微软的两大开源产品,二者在业界的影响力都是巨大的。 近日 Github 新出了一个功能,用户可以通过将 url 中的 .com 替换为 .dev 来直接打开在线版的 vscode 编辑器。 如果大家听过之前出现的一个叫做 github1s 的第三方工具的话,那就很容易理解这个功能。和 github1s 的一样,用户通过修改 url 中的少量字符就可以直接在在线版的 vscode 中打开仓库。 比如 github 地址为: https://github.com/azl397985856/leetcode 那么你就可以通过访问如下地址直接在云端 ide 打开: https://github.dev/azl397985856/leetcode url 长度还是相同的,甚至还有一点整齐美 还有一种更简单的方法:只要你在任何 GitHub Repo 页面上按下 .键 会自动跳转到 github.dev 的网站。 笔者特意对 github1s 和 github.dev 进行了对比,结果如下图: 可以发现二者不管是 UI 还是功能都是非常类似的。大的不同点就是 github.dev 集成了 codespaces,这也是微软的下一个主战场。不难想象,将来不仅是云端 vscode 还是本地的 vscode 都会向 codespaces 发力。 codespaces 允许你使用云端的资源,而不仅仅是作为一个编辑器,整体感觉类似 gitpod,不过功能会更多。 gitpod 也是一款非常不错的产品,推荐大家使用 codespace 定位更高端,比如可以像 github actions 那样定制镜像,环境等。GitHub Codespaces 支持 Visual Studio Code 和新式 Web 浏览器。 借助在云端的开发,可无缝切换使用不同的工具,随时随地贡献代码。 想直接体验?戳这里:https://visualstudio.microsoft.com/zh-hans/services/github-codespaces/ 如下是 codespaces 的架构图: 可以看出其主要由两部分组成:一部分是编辑器,另外一部分是云端的虚拟机,而几乎所有的功能都可以在云端完成,比如 AI 提供的自动补全,根据注释写代码等功能。更多关于 codespaces 的介绍参考:https://docs.github.com/en/codespaces/overview#joining-the-beta 不过 codespaces 目前还没有大规模推广,期待这 codespaces 可以尽快推广,给广大像我一样的开发者带来便利。","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"VSCODE","slug":"工具/VSCODE","permalink":"https://lucifer.ren/blog/categories/工具/VSCODE/"}],"tags":[{"name":"VSCODE","slug":"VSCODE","permalink":"https://lucifer.ren/blog/tags/VSCODE/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"如何搞定不同公司的算法面试?(早早聊分享文字版)","slug":"zaozaoliao","date":"2021-09-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.973Z","comments":true,"path":"2021/09/02/zaozaoliao/","link":"","permalink":"https://lucifer.ren/blog/2021/09/02/zaozaoliao/","excerpt":"前几天西法参加了《前端早早聊》第 24 界的分享。我的分享主题是《如何搞定不同公司的算法面试?》 这是这次分享的文字版,供大家查看。如果大家需要分享的原版 ppt,也可以到我的公众号《脑洞前端》中回复早早聊获取。","text":"前几天西法参加了《前端早早聊》第 24 界的分享。我的分享主题是《如何搞定不同公司的算法面试?》 这是这次分享的文字版,供大家查看。如果大家需要分享的原版 ppt,也可以到我的公众号《脑洞前端》中回复早早聊获取。 正文本次分享我们会按照如下顺序进行: 第一部分是前端算法面试都考什么?(其实这部分内容不同公司差异不大,我列举的是总体公司的大部分面试考察点,差不多可以覆盖 90% ) 第二部分是不同公司算法面试有何区别?(由于不同公司的算法难度和侧重不尽相同,有时候甚至是天差地别,因此我们有必要对不同类型公司的算法面试进行区分) 第三部分是如何准备算法面试?(知道了算法面试的考察内容和不同公司的面试特点,就可以根据自己心仪的公司对号入座了。那学习的顺序是什么,重点又是什么?这部分告诉你) 第四部分是中等难度算法题实战(通过一个具体的例子,带大家看一下,解决一道具体的算法题背后的思路究竟是如何的,我是如何和已有的知识建立建立的,希望能给大家一点点启发) 最后一部分是我的工具与方法论(这部分是我一直在使用的一些工具和方法论,个人觉得不错,这里分享给大家) 为什么是我? 我从 17 年开始研究算法面试题,到现在已经接近 4 年了。可以说经验算是比较丰富的了。 另外我本人刷了大概 2000 道左右的算法题。就大家比较熟悉的力扣平台而言,我几乎将他们所有的树,链表,堆,二分,动态规划等刷完了。除此之外,我也会刷一些其他 OJ 平台,比如牛客,Binary-Search 等等。在刷题过程中总结了大量的经验和套路。 除此之外,我也经常在力扣上分享自己的刷题心得以及题解。当力扣刚推出等级系统的时候,我就已经是满级作者了。除了在力扣上分享经验,我还在其他渠道进行分享,比如 公众号和 Github 。Github 上关于刷题的项目已经超过 4w star 了,非常感谢大家的认可。 另外在算法面试这个点上,我已经做了三期的算法面试培训了,累计有 1000 多人参加,很多人拿到大厂 offer 也会过来还愿。 最后一点我觉得最重要。 由于我本人是前端,也参加了无数的面试,包括面试别人和被别人面试。从面试官的角度我能知道广大求职者的面试状态,从求职者的角度,我能知道各个公司的考察形式和内容。这一来一回,一正一反,使得我对算法面试更加了解。当然除了我本人亲自面试的经验,我还经常和其他公司的朋友以及群友交流面试题目,他们提供面试真题,我提供免费的算法题目指导。通过这样的过程,我积累了相当可观数目的算法面试真题。使得我对很多公司的面试情况更加了解。 一点点说明为了方便描述,我将公司分成四个梯队。 T1:世界头部公司,比如 Google, Amazon, Facebook 等 T2:国内头部公司,比如 BAT ,头条 ,美团等 T3:其他中大型公司,比如滴滴,有赞等 T4:小微公司(这类不在我们的论述范围,因此它们几乎没有算法题) 右边是一个金字塔图,表示的意思是 T4 公司是最多的,其次是 T3,T2,T1。 前端算法面试都考什么?接下来开始我们本次分享的第一个主要内容 - 《前端算法面试都考什么?》 有的人可能会问,我的这个前端算法面试考点数据来源是哪?有事实依据么?这里我说一句。 这里的考察知识点的数据来源就是我前面做自我介绍时候提到的“亲身经历+好友反馈“。 我从大的方向,将考察点分成了两类。第一类是数据结构与算法基础知识。 数据结构与算法基础知识这部分,我又做了一个小小的细分,将其分为两个小点。 各种数据结构的特性与基本操作。比如数组,队列,栈,链表,树,图等。对于前端,尤其需要掌握的栈和树。这是因为前端使用到栈和树的地方实在太多了。比如 DOM 树(虚拟 DOM 树),树形选择器,浏览器执行栈,浏览器历史记录栈等等。另外题目上围绕栈和树的题目也相当多,从最简单直接的树形数据结构转化复杂一点的数据结构解析,基本就是栈+DFS 都可以搞定,而做 DFS 的时候通常都围绕树型结构进行递归求解的。所以这两个数据结构对前端非常重要。面试频率非常高,这里我敲一下重点,希望大家认真对待这部分。 复杂度分析。复杂度分析是学习数据结构与算法的基础,也是核心。我建议大家一定要先学会分析算法的复杂度再去学习具体算法。这部分内容包括时间复杂度和空间复杂度分析,其中每一种复杂度都有最好,最坏以及均摊复杂度。而一般我们使用最坏复杂度比较多,而我写的新书《lucifer 的算法之路》中的全部复杂度也全部都是最坏复杂度,而且这部分内容是在全书的最开始,可见其重要性。另外分析复杂度除了分析迭代,也要会分析递归,递归栈的空间开销经常被大家所忽略,这点值得引起大家的注意。 OK,以上是第一个考点:《数据结构与算法基础知识》,接下来我们来看第二个考点。 算法思想(90%考点)第二类是算法思想,需要大家在掌握了上面内容基础上再来学习。 这里我列举了五个考点,它们分别是: 搜索(BFS,DFS,回溯,二分等) 暴力优化(双指针,单调栈,前缀和等) 动态规划 分治 贪心 以上内容覆盖了前端算法面试的 90% 考点。一些比较“冷门”的知识比如二分图,跳表,蓄水池抽样算法等考察频率很低,我就没有列出来。 我希望大家集中精力将重心投入到这 90% 考点中。其他知识点大家可以根据自己的情况学习(比如你想进 T1 或者其他学的差不多了想精进)。 不同公司算法面试有何区别?接下来,我们来看下本次分享的核心主题 - 《不同公司算法面试有何区别?》 首先我声明一点:即使是同样的公司,同样的岗位不同时期题型和难度都不一定相同。因此以下内容仅表示历史数据,不表示之后的情况。如果大家想获取第一手的不同公司算法面试情况,可以在本次演讲结束后关注我的个人公众号。 前面我们已经对不同公司进行了一个简单的分类,那么这里就直接根据前面的分类进行逐一讲解,比较一下不同公司的算法面试有什么不同。 T1难度和题型首先我们来看下 T1 级别的公司。 T1 级别的公司算法面试题难度几乎没有上限,可能会超出普通 OJ 平台的难度,且题目非常灵活,他们经常会时不时原创题库, 因此碰不到原题或者类似题是很正常的。 还有一点有必要和大家强调从一下。 很多人觉得社招算法难度比校招要大,这是不对的。 一般而言,校招会更看重基础,这是因为校招的人通常项目经验都比较欠缺。而算法就是这些基础中非常重要的一项。社招的话,国内公司还是以项目经验和解决问题能力为主,对于算法的要求通常比校招的要求低。因此大家不要觉得校招的算法都这么难,那我社招岂不是更难? 不要有这样的想法。 T1 公司题型也更加广泛,除了上面列举的 90% 考点,还会有一些图论,前缀树,KMP 等其他公司不太会问到的“冷门”知识。虽然 T1 公司很多题目也可以在网上找到原题。不过区别于 T2,T3,他们的出题形式会更加有关联,循序进阶,且会伴随着一些 follow up。 比如第一道题是两数和,第二道题可能就是三数和,甚至是 k 数和。另外还会提出一些进阶要求,比如要求不借助额外空间等。 而且 T1 通常会定期扩充原创题库,这是其他 T2, T3 级别公司(尤其是 T3)很难做到的。 这些题库的题目经过一段时间也会逐步扩散到网上,进而又变成了大家所谓的“网上的原题”。 面试形式T1 公司之前很多是白板面试,即让你在一块白板上书写。白板书写对你的调试能力, api 了解能力,甚至代码组织能力都有很高的要求。不过最近 T1 公司基本都改成了视频面试的形式,而不是传统的 onsite,因此白板也变成了云编辑器。 T1 公司通常要求思路正确,代码 bug free(有时候也会要求你不经过提示的情况下一次通过),一般也对复杂度有一定要求。不仅需要你能够正确地分析出算法的复杂度,还需要对算法进行改进以达到尽可能优的复杂度。 前面我已经提到了,复杂度分析是基础中的基础,这部分大家注意一下。 面试完成后会有一份完整的面试报告,记录了你每一道题的回答情况甚至用时情况。面试委员会会根据这份面试报告来决定面试是否通过以具体的定级之类的。 一个典型的 t1 公司算法面试流程接下来,我们来看下《一个典型的 t1 公司算法面试流程》。 面试官:我把题目贴到了屏幕左侧,你可以在右侧编辑器中进行作答了。我现在给你放第一题。 求职者:OK。(看了一会)题目的意思是不是。。。(复述题目,确保理解正确,询问限制条件,比如数据规模) 面试官:(回答求职者的问题) 求职者:我的思路是。。。,这种做法的时间复杂度为。。。空间复杂度为 。。。 (如果面试官觉得没有问题,你就可以开始写代码了。写代码的时候要边写边交流) 面试官:你可以对你的算法进行优化么?(如果求职者给的算法不是最优解) (面试官会在求职者做题的时候记录求职者的回答情况以及每一部分所花费的时间) 最后将几道题(一般是 2 - 4 道)的回答情况汇总到面试报告中 如上是我模拟的一个典型的教科书般的面试流程,大家可以参考这位求职者的回答。 其中核心点就在于: 复述题目,确保理解正确,询问限制条件,比如数据规模 先讲思路,再写代码。讲思路后记得用复杂度对算法进行评估。 如何可能,继续优化你的代码。 T2难度和题型T2 公司难度基本不会超过中等,且题目比较固定(就是一些 OJ 原题或者稍微改改)。以我的经历来说,我面试了很多国内顶尖公司,比如头条,阿里,腾讯等。只有腾讯遇到了 hard,还不让你用 hard 解法就能过关(用了 hard 解法反而可能不够,这个和面试官有关,建议你先用最笨的解法,有必要再优化)。 另外从其他朋友的反馈来看,前端岗位算法面试难度为困难的题目很少,且题目比较固定,比如 LRU,分组反转链表等。(可能是公司题库就那几道困难题目?) 题型上来看,前面提到的算法思想基本都可以完全覆盖,很少会有一些刁钻的题目。并且面试的题目大部分都是可以在网上找到原题或者类似的题。这也是 T2 和 T1 的一个显著区别。 这里要提一下校招的笔试题。校招的笔试题大家都觉得是新题, 其实不然,以我这么久和校招题目打交道的过程来看。题目基本都是换皮,而且是那种换了个说法而已的换皮。 面试形式T2 公司通常是采用云编辑器和本地编辑器的考察形式。比如头条大多数使用的是牛客的编辑器,阿里有些用的是内部开发的伯乐系统,这两个公司通常需要你在云端编辑器完成。再比如腾讯就使用的是腾讯文档,但是允许你在自己的本地编辑器中写代码。这一点还是非常人性化的。 T2 公司对算法题目的要求通常是思路正确,bug free。(不一定一次通过,通常给你几次机会,具体几次视题目难度而定。比如简单题可能就不给你错的机会,困难题允许你错一次或者可以给提示这样) 面试完成后会有一份面试报告,这部分面试报告通常不会像 T1 公司的那样完整,一般只是记录了你最终完成的代码。 需要注意的是你的每一次面试都会记录下来,之后当你再次参加他们公司的面试,有时候也会参考之前的面试记录(通常只会参考最近半年或者一年的面试记录,毕竟每个人都会成长的)。 一个典型的 t2 公司算法面试流程t2 公司面试流程和 t1 公司有很大的相似性。 虽然考察的形式是差不多的,也就是说非常形似。不过 t2 公司的算法面试题的难度和数量一般会比 t1 的低一些,t1 公司的面试报告也会更加详细一些。 T3难度和题型T3 级别的公司难度绝大多数不会超过中等,且基本都是网上非常常见的面试题。如果你想通过这些公司的算法面试,你可以选择完全放弃困难题目。没错,网上常见的困难题也没有必要做了。并且基本上只需要刷我提到的算法思想部分,然后将网上的面经遇到的题目练习一下就差不多够了。 考察形式考察形式上也通过是让你说思路或者写出伪代码。通常要求思路正确,由于通常是说思路和伪代码,因此也不要求 bug free。 T3 公司面试完成后会有一份简短的面试反馈,一般是对面试人能力的大体概括。这个面试报告可能是在招聘系统中直接给出,也可能是口头的。 一个典型的 t3 公司算法面试流程t3 公司流程就和 t1 ,t2 有很大差别了。它们有时候根本就没有算法题。如果有的话,通常也是一个加分项。面试的难度也会更低,并且碰上原题的概率会很大,毕竟不是所有公司都有自己的题库的。就连 t2 公司的题库也使用了一些外部题目。 还有一点我发现的一个有意思的点是: T3 公司通常你做出来就行了,不太会让你去优化。比如,我有一次参加一个 T3 公司的面试,它们给我出了一个括号匹配(力扣简单难度)。我先用栈完成了,心想面试官肯定会让我优化成 $O(1)$ 空间。结果是没有,并且面试官对我很满意,觉得我写的很快。 实际上这不是巧合,我的经验告诉我。如果你的算法性能不是差到离谱,只要能做出来就行。什么叫差到离谱?比如两数和,你用 $O(n^2)$ 做出来,这就是离谱。 最后我给一个汇总性的对比表格方便大家查看。 大家可以通过这个表感受不同类型公司算法面试的区别。 我该如何准备算法面试?前面已经对各种不同的公司从多个维度进行了详细对比。接下来给大家一点实操建议。即如何准备算法面试。 这部分我总结了四点,在这里分享给大家。 1. 先学习数据结构与算法基础知识。第一个我要给大家的建议是:先学习数据结构与算法基础知识。 我发现很多同学喜欢一下子就钻到刷题中去,他们甚至连复杂度分析以及各种数据结构的特点都还不清楚。 这是万万不可取的做法。一定要注意,做题只是巩固知识的手段,如果你根本没有知识,或者知识不足,那么做题是几乎没有任何效果的。 具体的基础知识内容可以参考我后面要讲的学习路线图。 01.数组,栈,队列 02.链表 03.树 04.哈希表 05.双指针 06.图(加餐) 模拟和枚举 前缀和与差分 如何衡量算法的性能 各种排序 2. 找到你心仪的公司,通过多种渠道了解其出题风格和难度挑选出你心仪的几家公司,看看他们属于 T 几,先有一些大体的映象和心理准备。接下来,你需要从多个渠道了解它们算法题目的类型和难度。我提供几个渠道给大家: 牛客网讨论区 力扣讨论区 一亩三分地 社群和好友 如果你愿意的话,也可以直接来咨询我。我会尽可能帮助你的 之后你要做的就是根据上面步骤总结的信息针对性刷题,这样可以使你的效率最大化。 3. 不依赖自己顺手的编辑器平时练习的过程中大家可能习惯了自己顺手的 IDE。比如自己本地的 IDE 或者平台提供的 IDE。 笔试的时候很多情况下是没有条件让你这么做的,所以大家在面试前需要自己适应一下脱离 IDE 写代码。 一个不顺手的 IDE 确实会让你的效率大大降低,比如没有智能提示,没有语法高亮,缺乏良好的格式化,很多快捷键不支持等。当你没有这些良好的条件的时候,会加大你面试的紧张情绪,进而影响你的发挥。因此不依赖你自己顺手的编辑器是一个相当重要的点。 4. 重点突破搜索类和动态规划最后一个,我觉得或许是对大家最有用的一个建议了,这部分再次划重点。(这是本次分享的第二个重点了。还记得第一个重点么?第一个重点是两个数据结构,栈和树) 回忆一下前面我提到的五种占比 90%的算法思想: 搜索(BFS,DFS,回溯,二分等) 暴力优化(双指针,单调栈,前缀和等) 动态规划 分治 贪心 前端算法面试 90% 的题目都不超过这几项,尤其是非 T1 公司。 如果要从从这五项再说重点的话,我推荐大家刷: 搜索 动态规划 而搜索的话,又以树为主。其中的原因我在前面也进行了分析,这里就不赘述了。 至于动态规划,大家也应该着重准备。比如很多公司特别喜欢考察的背包问题,爬楼梯问题,这些都是动态规划。悄悄告诉你,这些问题我本人也都在实际面试中遇到过,可见面试频率还是蛮高的,值得大家重点投入。 而如果你面试国外的公司的话,除了谷歌据说考察动态规划的很少。 举个栗子接下来上干货,带大家来解决一道算法题。 我们以力扣上的 402. 移掉 K 位数字 为例,讲解一下如何思考一道题目的解法。 之所以要选这道题是因为: 难度适中。 没有很难,但也绝对不简单。 这题很经典,搞懂这道题你可以 AC 好多道题,我在后面的 PPT 给大家都列好了,大家等会可以看下。另外这道题的题解让我收获了几千的点赞和收藏,可见质量还是不错的。 我们来看一下这道题。 题目描述123456789101112131415161718192021给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。注意:num 的长度小于 10002 且 ≥ k。num 不会包含任何前导零。示例 1 :输入: num = "1432219", k = 3输出: "1219"解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。示例 2 :输入: num = "10200", k = 1输出: "200"解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。示例 3 :输入: num = "10", k = 2输出: "0"解释: 从原数字移除所有的数字,剩余为空就是0。 前置知识 数学 栈 思路这道题让我们从一个字符串数字中删除 k 个数字,使得剩下的数最小。也就说,我们要保持原来的数字的相对位置不变。 以题目中的 num = 1432219, k = 3 为例,我们需要返回一个长度为 4 的字符串,问题在于: 我们怎么才能求出这四个位置依次是什么呢? (图 1) 暴力法的话,我们需要枚举C_n^(n - k) 种序列(其中 n 为数字长度),并逐个比较最大。这个时间复杂度是指数级别的,必须进行优化。 一个思路是: 从左到右遍历 对于每一个遍历到的元素,我们决定是丢弃还是保留 问题的关键是:我们怎么知道,一个元素是应该保留还是丢弃呢? 这里有一个前置知识:对于两个数 123a456 和 123b456,如果 a > b, 那么数字 123a456 大于 数字 123b456,否则数字 123a456 小于等于数字 123b456。也就说,两个相同位数的数字大小关系取决于第一个不同的数的大小。 因此我们的思路就是: 从左到右遍历 对于遍历到的元素,我们选择保留。 但是我们可以选择性丢弃前面相邻的元素。 丢弃与否的依据如上面的前置知识中阐述中的方法。 以题目中的 num = 1432219, k = 3 为例的图解过程如下: (图 2) 由于没有左侧相邻元素,因此没办法丢弃。 (图 3) 由于 4 比左侧相邻的 1 大。如果选择丢弃左侧的 1,那么会使得剩下的数字更大(开头的数从 1 变成了 4)。因此我们仍然选择不丢弃。 (图 4) 由于 3 比左侧相邻的 4 小。 如果选择丢弃左侧的 4,那么会使得剩下的数字更小(开头的数从 4 变成了 3)。因此我们选择丢弃。 那我们要继续丢弃 1 么?不用,因为这样会造成数字更大(从 1xxx 变成了 3xxx)。 。。。 后面的思路类似,我就不继续分析啦。 然而需要注意的是,如果给定的数字是一个单调递增的数字,那么我们的算法会永远选择不丢弃。这个题目中要求的,我们要永远确保丢弃 k 个矛盾。 一个简单的思路就是: 每次丢弃一次,k 减去 1。当 k 减到 0 ,我们可以提前终止遍历。 而当遍历完成,如果 k 仍然大于 0。不妨假设最终还剩下 x 个需要丢弃,那么我们需要选择删除末尾 x 个元素。 上面的思路可行,但是稍显复杂。 (图 5) 我们需要把思路逆转过来。刚才我的关注点一直是丢弃,题目要求我们丢弃 k 个。反过来说,不就是让我们保留 $n - k$ 个元素么?其中 n 为数字长度。 那么我们只需要按照上面的方法遍历完成之后,再截取前n - k个元素即可。 按照上面的思路,我们来选择数据结构。由于我们需要保留和丢弃相邻的元素,因此使用栈这种在一端进行添加和删除的数据结构是再合适不过了,我们来看下代码实现。 代码代码支持: Python3, JS Pythopn3 Code: 1234567891011class Solution(object): def removeKdigits(self, num, k): stack = [] remain = len(num) - k for digit in num: while k and stack and stack[-1] > digit: stack.pop() k -= 1 stack.append(digit) # 为了类似 \"10200\" /n 这种 case 过不了,需要移除前导0。 另外为了防止将 0 全部移除,我们需要判空 return ''.join(stack[:remain]).lstrip('0') or '0' JS Code: 123456789101112var removeKdigits = function (num, k) { const stack = []; const remain = num.length - k; for (const digit of num) { while (k && stack && stack[stack.length - 1] > digit) { stack.pop(); k -= 1; } stack.push(digit); } return stack.slice(0, remain).join(\"\").replace(/^0+/, \"\") || \"0\";}; 复杂度分析 令 n 为数字长度。 时间复杂度:虽然内层还有一个 while 循环,但是由于每个数字最多仅会入栈出栈一次,因此时间复杂度仍然为 $O(n)$。 空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 $O(n)$。 提示: 如果题目改成求删除 k 个字符之后的最大数,我们只需要将 stack[-1] > digit 中的大于号改成小于号即可。 相关题目大家做完这个题,就可以 AC 下面几个题啦~ 去除重复字母(困难) 拼接最大数(困难) 不同字符的最小子序列(中等) 我的工具与方法论最后是我的工具和方法论,主要介绍一下我学习路上的一些好用的工具,以及我是如何刷题的经验分享。 学习路线这里我给出一个适合前端同学的一个算法学习路线。 复杂度分析:如何衡量算法的性能? 基础的数据结构:线性数据结构 (数组,链表,栈,队列,哈希表) 基础的数据结构:非线性数据结构 (树 和 图) 排序算法:经典排序算法 递归: 一种”高级“的思维技巧 暴力搜索篇: 回溯,BFS, DFS 暴力优化篇:剪枝,滑动窗口,双指针,单调栈 高级搜索篇:二分法与位运算 动态规划篇:记忆化搜索与动态规划 分治:大事化小,小事化了 贪心:简单高效但”不靠谱“的算法 逆向思考 大家可以根据自己的实际情况从不同阶段开始。如果你是刚开始接触算法,我建议你从头开始跟着这个学习路线坚持下去,我相信一定会有突然间成长的一天。 多做总结,多写题解大家在学习的过程这一定要及时总结和复习,而写题解就是总结和复习很好的一个途径。我本人写的题解数量已经有几百篇了。通过写题解能够加深我对算法的理解能力。下一次碰到相同或者类似的题目,则可以从大脑中提取更多信息。 使用 anki 管理知识前面提到了要及时复习。而 anki 就是一个帮助你管理复习计划的软件。它是一个能够根据遗忘周期来自动制定复习计划的软件。我在前期学习算法的时候制作了一部分的复习卡片,它们帮助了我度过了学习算法最艰难的入门期。 等到你刷了足够数量的题目了,就不需要它们了。但是在前期,它确实可以有效地帮助到你。 刷题插件 leetcode-cheatsheet最后提一下我自己制作的一个刷题插件,现在有几千个人在用了。通过它可以帮助你更有效率刷题和写题解。 他提供了学习路线可以帮助你理清不同专题的基本题型和套路。 他提供了代码模板可以帮助你快速写出 bug free 的代码。 他提供了数据结构可视化和题解模板则可以帮你快速写出格式良好的题解。 他提供了复杂度速查, 这是一个帮助你快速判断算法是否可行的参考工具。 推荐一本好书给大家最后推荐一本书大家。《算法第四版》- 一本非常适合初中级选手的算法学习书籍。 这本书给了我很多帮助,少走了一些弯路。这里推荐给大家,希望也可以帮助到正在学习算法的你。这本书我买了好多本,不仅自己看,还送给了我的朋友和学员。 这本书只是让你入门,以及学习一些经典算法。仅靠这个去参加比赛或者 T1 面试是不够的。大家还需要准备一些别的资料。不过我觉得入门是最难的,相信你入门之后就可以自己去挑选学习资料了。比如 oi wiki。 关注我我本人最近也在写一个关于前端算法面试的专栏,目前正在寻找合作平台,如果你是平台方可以微信联系我。如果你对内容感兴趣可以订阅我的公众号《脑洞前端》,第一时间消息会在公众号同步大家。 另外我的 《91 天学算法》第四期已经快要开始了,现在还没有开始报名哦,活动介绍:https://leetcode-solution.cn/91。开始报名时间大概是 2021.5.1 - 2021.6.1 之间。具体时间请关注我的公众号《力扣加加》,第一时间获取最新消息。 以上就是本次分享的全部内容了,大家有什么问题的话都可以提,我会尽量回答。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"前端","slug":"算法/前端","permalink":"https://lucifer.ren/blog/categories/算法/前端/"},{"name":"面试","slug":"算法/前端/面试","permalink":"https://lucifer.ren/blog/categories/算法/前端/面试/"}],"tags":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第五期)","slug":"91algo-5","date":"2021-08-20T16:00:00.000Z","updated":"2023-01-07T13:56:10.279Z","comments":true,"path":"2021/08/21/91algo-5/","link":"","permalink":"https://lucifer.ren/blog/2021/08/21/91algo-5/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 第五期本来想和力扣官方合作一起搞,这样打卡就可以无缝衔接,如果你有力扣会员甚至可以免费参加。可是力扣官方给的感觉是:快了,已经在新建文件夹了。 就好像我虽然还是 行号 0, 列号 0,字数 0,但是却和催更的读者说快写好了一样。 因此第五期我们就先开始吧! ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 第五期本来想和力扣官方合作一起搞,这样打卡就可以无缝衔接,如果你有力扣会员甚至可以免费参加。可是力扣官方给的感觉是:快了,已经在新建文件夹了。 就好像我虽然还是 行号 0, 列号 0,字数 0,但是却和催更的读者说快写好了一样。 因此第五期我们就先开始吧! ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 和上一期有什么不同?首先介绍一些第五期和往期的不同。 移除了进阶篇的 - 高频面试题,将自习篇内容改为非自习内容,也就是基础篇的枚举篇和图 改为非自习内容。 自习指的是不给每日一题的做题时间,需要自己找时间学习和练习。因此这次改动意味着除了先导篇,其他全部内容都变为非自习。 讲义更新,以及题库部分题目更新。这就不用多解释了,每一期我们都会完善讲义内容和题目,使得讲义内容更完善,题目难度梯度更加科学。具体大纲我们后面会讲。 网站上直接打卡,不用跳转到 Github 打卡,在一个网站完成所有操作。 每天找到当天打卡的题目后,就可以看到下方有一个评论区,大家将自己的答案贴到这里就好了。 活动时间2021-09-10 至 2021-12-10 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容&时间安排本期理论上全部内容可直接在我们的官网上进行,体验更棒哦~ 先导篇 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图 模拟,枚举与递推 专题篇 二分法 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 背包 分治 贪心 位运算 进阶篇 Trie 并查集 剪枝 字符串匹配(BF&RK&KMP) 堆 跳表 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 五期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在每日一题下方打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 实在不会的可以看下我们提供的官方题解。另外,如果你自己写完之后也可以参考一下官方题解,观察一下是否可以改进。算法能力就是在这一点一滴的努力中提升出来的。 本期有共五位讲师,每个专题由一位具体的讲师负责,大家有不会的问题可以进行提问。如果讲师来不及回答,大家可以在仓库中提 issue。 关于每一个专题的负责讲义,我们会在 91 官网中的“讲师”模块给出,如上图所示。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试 一次,我的新书《算法通关之路》一本,科学上网兑换码 90 天等。 连续打卡七天可以获得补签卡一张哦 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 前 100 扫码进群。如果提示不能进入,说明已经超过 100 名了,需要找 lucifer 手动拉。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元。第 50 到 100 入群的请自觉缴纳 10 元哦 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可。 当你满足以下三个条件: 前 50 名 分享海报满 3 天 已经付费 则可联系 lucifer,并告知你的 Github 登录名即可。 分享返现如果你没有抢到前 50 名免费的学习机会也不要气馁。我们贴心地为大家搞了分享返现活动,手慢照样可以免费参加哦~ 活动规则: 发送宣传海报到你的朋友圈不屏蔽好友保留三天,三天之后加 lucifer 微信好友(微信号:DevelopeEngineer)进行验证,验证通过全额返现。 不到三天就没必要联系我验证了,必须不屏蔽好友满三天才可以验证。 朋友圈文案统一为: 91 天,遇见更好的自己。发送本海报到朋友圈,不屏蔽好友保留三天即可免费学习(文案需保留)。快扫描下方二维码报名吧! 海报: 朋友圈分享海报示例: FAQ Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第五期和前四内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。第四期增加了模拟章节,调整了章节顺序,更改了题目难度梯度设置等。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到网站,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在网站中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。网站地址:https://leetcode-solution.cn/91 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和网站,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在网站和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 微信新版可以对群里成员设置特别关注。如果你有这个功能,则可以尝试一下特别关注群主。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/ 官方网站从上一期起,我们开始制作自己的官方网站:https://leetcode-solution.cn/91 本次在上次的基础上,增加了网站打卡功能。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"你不知道的 LeetCode 技巧(第二篇)","slug":"ydk-leetcode-2","date":"2021-08-16T16:00:00.000Z","updated":"2023-01-05T12:24:49.400Z","comments":true,"path":"2021/08/17/ydk-leetcode-2/","link":"","permalink":"https://lucifer.ren/blog/2021/08/17/ydk-leetcode-2/","excerpt":"上一篇 你不知道的 LeetCode 技巧(第一篇) 讲述了三个 JS 刷题的小技巧。今天来分享几个 leetcode 通用小技巧,不管你是用什么语言刷题都可以使用。","text":"上一篇 你不知道的 LeetCode 技巧(第一篇) 讲述了三个 JS 刷题的小技巧。今天来分享几个 leetcode 通用小技巧,不管你是用什么语言刷题都可以使用。 tip1 - 中英文切换力扣中国部分题目描述由于翻译的原因,会变得难以理解,甚至出现翻译错误导致题意发生变化的情况。 这个时候推荐大家使用切换语言功能,将其转化为英文。 如下是中文题目描述: 接下来,可以点击下面的图标切换语言。 这只是切换单个题目的语言,如果你想切换整个网站的语言,可以点击力扣中国顶部右侧的这个按钮。 tip2 - 快捷键使用快捷键可以显著提高效率,刷力扣也是一样。 如下是我常用的两个快捷键,强烈推荐使用。 提交代码:cmd + enter 执行代码(测试):cmd + ` 目前力扣编辑器提供八个快捷键绑定,更多快捷键可以参考力扣的提示。如下所示: 另外如果你是 windows 那么上述快捷键肯定不生效,那么你可以按照如上方式查看文档了解具体的快捷键绑定。 tip3 - 限定时间刷题之前我提到过:推荐大家给自己做题设定一个时间限制。可以采用如下方式: 力扣模拟面试 力扣周赛 大家可以做题时点击这个给自己限定时间。 值得注意的是,默认限制时间是 30 分钟。建议大家逐步缩短时间,做到 15 分钟以内,有条件的话挑战一下五分钟。 tip4 - 在线面试如果你是面试官,也可直接使用力扣来出题。力扣中的很多功能都可以使用。 你可以自由添加力扣原题,当然也可以自己出题。 更有意思的的是,竟然可以出前端题 😹。其实前端题就是一个类似于 stackblitz 的东西。不得不说,如果你是前端面试官,这个在线面试功能可以说很全面了。 另外也支持文字聊天,语音,视频,在线评价等常见的面试功能。 简单适用了一下,一个月可以免费创建 10 次面试,每次面试不能超过三个小时,总体来说还蛮方便的。 tip5 - 刷题插件我自己做了一个刷题插件 leetcode-cheatsheet,它扩展了力扣平台的功能。 比如: 一键写题解 一键复制所有内置测试用例 数据结构可视化 复杂度对照表, 直接根据数据规模反猜答案的算法复杂度 功能太多了,不一一介绍了。 更多功能以及如果获取插件可以参考之前我写的文章: 力扣刷题插件 总结文章写于 2021-08-17。如果后续力扣改版,那么相应功能可能会发生变化。如果后续力扣推出新的功能,力扣加加也会第一时间同步大家。比如力扣刚刚发布 APP 的时候,我就参与了内测第一时间给大家带来了这篇文章 力扣 APP 全新改版,史诗级增强!。 本文给大家介绍了五个在力扣中国刷题的技巧: 使用中英文切换功能,防止因为翻译问题使自己看不懂题目描述。 使用快捷键提供自己的效率,尤其是执行和提交这两个功能。 限定时间刷题,创造面试的紧张感。 如果你是面试官,不妨试试力扣的在线面试功能。 如果你刷力扣,强烈推荐我开发的力扣刷题插件。更多功能,等你来提。","categories":[{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/categories/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"你不知道的 LeetCode 技巧(第一篇)","slug":"ydk-leetcode","date":"2021-08-05T16:00:00.000Z","updated":"2023-01-07T12:34:34.709Z","comments":true,"path":"2021/08/06/ydk-leetcode/","link":"","permalink":"https://lucifer.ren/blog/2021/08/06/ydk-leetcode/","excerpt":"今天来给使用 JS 刷题的朋友分享三个 LeetCode 上你或许不知道的刷题技巧。","text":"今天来给使用 JS 刷题的朋友分享三个 LeetCode 上你或许不知道的刷题技巧。 tip1 - ES6+首先穿插一个小知识:我们提交的 JS 是如何被 LeetCode 执行的? 我们在力扣提交的代码是放到力扣后台运行的, 而 JS 代码在力扣后台是在 node 中以 —harmony 方式运行的。 大概是这样: 1node --harmony index.js 其中 index.js 就是你提交的代码。 比如: 12345678// 前面 LeetCode 会添加一些代码function sum(a, b) { // you code}// 这里是 LeetCode 的测试用例expect(sum(1, 2)).toBe(3);expect(sum(1, 8)).toBe(9); // 如果测试用例不通过,则直接抛出错误给前端 因此 ES6 特性是完全支持的,大家可以放心使用。 比如我们可以使用 ES6 的解构语法完成数组两个值的交换。 1[a, b] = [b, a]; 如下就是使用了 ES6 的数组解构语法,更多 ES6+ 请参考相关文档。 tip2 - lodash在 LeetCode 中 lodash 默认可直接通过 _ 访问。 这是因为 LeetCode 直接将 lodash require 进来了。类似: 1234567891011const _ = require(\"lodash\");// 前面 LeetCode 会添加一些代码function sum(a, b) { // you code // 你的代码可以通过 _ 访问到 lodash 的所有功能。}// 这里是 LeetCode 的测试用例expect(sum(1, 2)).toBe(3);expect(sum(1, 8)).toBe(9); // 如果测试用例不通过,则直接抛出错误给前端 lodash 有很多有用的功能可直接使用。西法建议你如果让你手写你能够写出,那么就可以放心的使用 lodash 提供的功能。 比如数组拍平: 12_.flatten([1, [2, [3, [4]], 5]]);// => [1, 2, [3, [4]], 5] 再比如深拷贝: 12345var objects = [{ a: 1 }, { b: 2 }];var deep = _.cloneDeep(objects);console.log(deep[0] === objects[0]);// => false 更多 API 可参考官方文档。 tip3 - queue & priority-queue为了弥补 JS 内置数据结构的缺失。除了 JS 内置数据结构之外,LeetCode 平台还对 JS 提供了两种额外的数据结构,它们分别是: queue priority-queue 这两个数据结构都使用的是第三方 datastructures-js 实现的版本,代码我看过了,还是很容易看懂的。 queueLeetCode 提供了 JS 对队列的支持。 12345// empty queueconst queue = new Queue();// from an arrayconst queue = new Queue([1, 2, 3]); 其中 queue 的实现也是使用数组模拟。不过不是直接使用 shift 来删除头部元素,因为直接使用 shift 删除最坏情况时间复杂度是 $O(n)$。这里它使用了一种标记技巧,即每次删除头部元素并不是真的移除,而是标记其已经被移除。 这种做法时间复杂度可以降低到 $O(1)$。只不过如果不停入队和出队,空间复杂度会很高,因为会保留所有的已经出队的元素。因此它会在每次出队超过一半的时候执行一次缩容(类似于数组扩容)。这样时间复杂度会增大到 $O(logn)$,但是空间会省。 详细用法可以参考:https://github.com/datastructures-js/queue 另外西法我自己实现了一套 queue,我是使用链表实现的,理论上复杂度更好,插入和删除时间复杂度都是 O(1),也不会有空间的浪费,核心代码就20 行。但实际使用的话性能不一定谁好,为什么呢,大家可以思考一下? 西法自己实现的 deque priority-queue除了普通队列,LeetCode 还提供了一种特殊的队列 - 优先队列。 12345678// empty queue with default priority the element value itself.const numbersQueue = new MinPriorityQueue();// empty queue, will provide priority in .enqueueconst patientsQueue = new MinPriorityQueue();// empty queue with priority returned from a prop of the queued objectconst biddersQueue = new MaxPriorityQueue({ priority: (bid) => bid.value }); priority-queue 的 api 则可以参考 https://github.com/datastructures-js/priority-queue 同样,西法也实现了堆,大家可以参考一下。 西法自己实现的 heap 值得一提的是,西法还实现了一些堆的高级功能,详情参考 indexed_priority_queue 总结LeetCode 对 JS 的支持主要有: ES6+ 语法的支持 内置 lodash 库,可直接通过 _ 来使用其上的功能函数。 内置数据结构支持队列和优先队列。 文中提到的我自己实现的数据结构代码来自 js-algorithm-light,它是轻量级的 JavaScript 数据结构与算法库。为了让使用 JS 刷题的朋友学习和使用一些常用的数据结构,我开辟了这个仓库,暂定的目标是对标 Python 所有的内置数据结构和算法。 贴一下西法已经实现的数据结构。 求个一键三连支持一下,点赞多的话西法立马就安排下一篇。下一次给大家分享几个 你不知道的 LeetCode 通用小技巧。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"刷题技巧","slug":"LeetCode/刷题技巧","permalink":"https://lucifer.ren/blog/categories/LeetCode/刷题技巧/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题技巧","slug":"刷题技巧","permalink":"https://lucifer.ren/blog/tags/刷题技巧/"}]},{"title":"从零到谷歌程序员:「狗头」的面试刷题心得","slug":"91algo-interview-yixiao","date":"2021-07-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.802Z","comments":true,"path":"2021/07/29/91algo-interview-yixiao/","link":"","permalink":"https://lucifer.ren/blog/2021/07/29/91algo-interview-yixiao/","excerpt":"本文作者:易潇 她的 Github:https://github.com/lilyzhaoyilu 大家好,我是易潇,也是 91 算法群里大家熟悉的狗头。最近申请和面试基本结束,刚刚过了Google 的 HC。 我的本科读的是商学,所以算是文科转码和0基础转码的一员。在这里想跟大家分享一下我面对面试刷题的心得~ 注:Google 的招人模式比较特别,过了 HC 可以视为被 Google 接受并且已经结束了所有技术性面试,简称过了。具体关于申请 Google 的信息会在后续的文章中跟大家分享,敬请期待。 ​","text":"本文作者:易潇 她的 Github:https://github.com/lilyzhaoyilu 大家好,我是易潇,也是 91 算法群里大家熟悉的狗头。最近申请和面试基本结束,刚刚过了Google 的 HC。 我的本科读的是商学,所以算是文科转码和0基础转码的一员。在这里想跟大家分享一下我面对面试刷题的心得~ 注:Google 的招人模式比较特别,过了 HC 可以视为被 Google 接受并且已经结束了所有技术性面试,简称过了。具体关于申请 Google 的信息会在后续的文章中跟大家分享,敬请期待。 ​ 我的刷题情况在谷歌面试的时候,我大概刷了 550 道题左右:其中大概 150 道简单,310 道中等,90 道困难。其中有中国站一些剑指和面试经典的重复题。对于前 200 和一些高频题,我刷了两遍以上或者更多。 如上图是我刷图论的模板题。左侧是我的提交记录,右侧是我的代码。 我在四月份之前断断续续的大概刷过 60 道题左右,然后在四月份的时候我新开了一个进程并且开始好好刷题。完成最后一个面试是 7 月 20 日。 刷题心得按照一个基于 tag/topic 的计划开始刷题tag/topic 的定义:在力扣题库界面的上方可以看到不同的标签(tag),点进去就可以进入每个标签的列表。 按照标签刷题的好处是它能帮助你巩固你对某个数据结构的理解,并且不断的改正和进步。 网上有很多大佬分享过刷题顺序和题单,或者你也可以自己创建一个列表。我觉得不用太纠结“到底哪个列表最好”,作为一个刚开始刷题的人,今天先学 A 或者 B 一般来说影响不会太大 —- 反正到最后都是要学的。最重要的是“开始学”,不要害怕计划不完美。计划肯定不会是完美的,是会随着自己的认知和进度而修正的。 对于我个人来说,我幸运的参加了力扣加加举办的 91 天算法。91 算法要求每天一起打卡一道题,并且每个礼拜的题都是围绕一个数据结构。这样按照一个标签集中刷题很好的验证自己对某个数据结构的知识并且修正了自己一些错误的认知。 建立一个有正向反馈的 Todo List为什么我这么推荐按照计划刷题呢,还有一个原因是 Todo List 是一个很方便建立正向反馈的工具。为什么建立正向反馈很重要呢 —- 你如果打过游戏,那么你一定有过“呀就打了一会儿游戏怎么就这么点啦”的感觉。打游戏的时候时间过的飞快就是因为大多数设计好的游戏都会不断的给予玩家正向反馈。同理,我们想要一直刷题,那么就要给自己创造一个有持续正向反馈的环境。 Todo List 就是这样一个工具。我刷题的时候使用的是ipad + goodnotes + ElenaLin_青青的电子手账模板 + apple pencil。一般来说,我每天会制定第二天需要做什么事情,并且尽量把它拆分成小的、容易完成的步骤。比如说我想学习字典树,那么我就会把计划拆分成: 看 91 算法的字典树专题 写 91 算法的字典树模板题 LC208 写 91 算法的字典树推荐题 LC211 LC212 LC472 LC648 LC820 LC1032 (如果还有精力)做力扣上字典树标签里的高频 5 道题 一般来说,除了学习某个专题,我的一天还会有一些其他事情,比如打卡 91 算法每日一题,打卡力扣每日一题,看邮件,投递申请等等。大部分时候,我起床之后坐在电脑前,都本能的觉得“太难了”,以至于“我不想干”。每当我有这种感觉的时候,我就在所有的待办事项中选一个我觉得最容易完成的项目并且开始做。换句话说 —- 别想那么多,兄弟,冲就完了!(好吧我是个 fps 游戏玩家)。我一般会选择先做 91 每日一题,因为它不会太难,而且打卡了之后会被其他刷题小伙伴看到,这让我有种觉得自己很棒棒的感觉。只要开始建立 “克服困难” -> “执行任务” -> “完成之后感觉自己很棒棒” 的行为模式之后,一切都会变得简单起来。 ElenaLin 的电子手账使用教程可参考这个视频 https://www.bilibili.com/video/BV1Ai4y1w7ZM 贴一个我中期的日程表,这个时候我已经建立了比较好的循环,所以会看到每天完成的任务多了起来,而且我也已经不太依赖“在完成每个任务之后划掉”这个动作了。我的笔记也并没有很整洁或者美观,只要自己能看懂就可以。 当然,在工具上不一定要跟我一样。本子和笔也可以。不过我强烈推荐用一个本子,因为周末和月末的时候回顾看看也会继续加深上面提到的有正向反馈的行为模式。 学会站在巨人的肩膀上一道题看10 分钟如果没有思路就不要太浪费时间在错误的方法上面,学会看和学习别人的题解。 一般力扣解题区的高票题解都很好。一个题解如果看不懂也没关系,首先照着它写一遍,然后通过各种打印和调试搞清楚每一行代码都是在做什么;如果还是不清楚为什么,可以把某一段代码注释掉再点提交。这个时候系统会报错,你再拿着这个输入一行行的分析题解,就能明白这一段代码在做什么、为什么必须要存在在题解里。(这样提交成功率会变低,但是变低又怎么样呢,我们的目标又不是成为提交成功率最高的仔)。 当然,问其他人也可以,但是我更推荐带有具体目的性的提问,而不是“这道题怎么做?”这种宽泛的问题。一般来说,问题越具体,越好回答,被回答的几率越高。 数量还是质量?其实都需要。一定的数量是熟练度的保证和见过大部分题型的基础。 熟练度在面试中很重要:面试一共那么长时间,其他条件不变,写代码越快,能做的其他事情就越多。其他事情可以是多跟面试官交流,回答一些 follow up,做一些优化等等。这些都是面试加分的项目。 见过大部分题型也很重要:见题型多的本质其实是对于某一类问题能否更好的抽象。比如对于堆来说,91 算法一直强调它的本质是“动态求极值”。刚读完我是不理解的,做了一道题我可能稍微有点理解了,做了三道题我就会觉得“哇塞还能这样,哦果然能这样!”拥有抽象问题的能力就等同于拥有以不变应万变的能力,在遇到没见过的题的时候就可以不慌。在面试中遇见原题的概率确实也不大,所以锻炼抽象能力很重要。 当然,刷题的质量也重要。当我们说刷题的质量的时候,我们在说什么呢?我觉得质量是指:一道题是否真的理解它在求什么。对于每一种这道题可能的思路,运用了哪些数据结构,用这种数据结构的优势和劣势在哪里。比如两数和,哈希表是常见的时间优化解法。这里利用了哈希表能用 O(1)时间获得对应 key 的值的特性,牺牲了空间复杂度,而优化了时间复杂度。 复习和重复复习和重复是很重要的,毕竟“孰能生巧”嘛。对于我来说,每个数据类型的模板题是我写的最多的,多到我能一遍过的基础上每隔一段时间还要刷一下。我在面试之前对于每个常见的数据类型都有自己喜欢的模板写法,并且能根据每道题对模板进行改进。 我记得我当时学堆的时候觉得太难了,我就照着 91 算法讲师小漾写过的堆写了一个礼拜 — 就是从早到晚一直写的那种一个礼拜。之后我经常打趣说 “就算你半夜三点把我拉起来让我手写一个堆都不会有任何 bug”。 凑巧的是,我在面试某个 pre-ipo 公司的时候就考到了跟堆相关的知识。当时的运行环境是 hacker rank,需要把代码跑出来并且通过实例(注:并不是每个公司都要求代码能在环境中运行,有的公司面试更像在白板上写代码)。我当即给面试官表演了一段速写最大堆,因为太熟了,所以行云流水一气呵成。写完了之后我发现两位面试官的眼睛放光,后来 recruiter 主动反馈说面试结果非常非常好。在给 offer 的时候给了比申请的级别高了一点,而且最后谈薪资的时候还主动加了 12%。 有很多一起刷题的小伙伴刷题,尤其是一开始刷题,都是令人感到困难的。很多时候这件事并不难,但是这种“好难啊”的感觉才是真正阻碍我们开始的东西。当有了很多小伙伴的时候,有了周围人的压力和鼓励,这种感觉会减弱一些。 刷题的中后期也会经常感到迷茫和焦虑,有很多小伙伴也能很好的减轻迷茫感。甚至很多时候,因为刷题群、技术群里很多大佬都上岸了,大佬们还会分享职位信息,可谓是一举两得。 对于我来说,我在决定要刷题的初期在网上加了很多技术群和刷题群。在我刷题的大部分时间里, 我每天早上睁眼看微信都是一页的刷题群未读消息。每次看到这个,我就有种我也要赶紧起床刷题的紧迫感。每次刷题觉得郁闷了,去群里跟大家聊聊天互相鼓励,也会一会儿就觉得元气满满。 在哪里找这些群呢 —- 力扣题解区有一些活跃的大佬们的刷题群都不错,力扣的讨论区也经常有人拉刷题群。 适应面试环境就算是算法面试,和在家自己悠闲刷题的感觉还是不一样的。算法面试要求快速的锁定思路并且进行精准打击。(呃,好像游戏打多了…)这方面我强烈推荐力扣的周赛和进行一些模拟面试。模拟面试可以找朋友,也有一些提供类似服务的网站。我在 interviewing.io 模拟了三次,感觉还是挺有用的。 我现在的竞赛分大概是 1780。曾经我也很焦虑的问 Lucifer 竞赛/面试一紧张没思路怎么办。Lucifer 的回复很简单也很有效:“先打 20 场再说”。 Just do it“先打 20 场再说”是面向面试刷题中最重要的指导思想了。 开始总是让人觉得非常困难,但是事情往往没有想的那么难。我也常常因为害怕自己“做不好”而浪费很多时间在寻找“最完美的”开始方法上 —- 实际上,当一个人开始学习某一项技能的时候,大概率这个学习方法是不完美的。所以如果你也想开始面向面试刷题,请把它想成一款游戏 —- 先开始打了再说,打了之后再根据需求和网上的攻略调整自己打游戏的方式就能慢慢变成大神啦。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"},{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/categories/谷歌/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"},{"name":"谷歌","slug":"谷歌","permalink":"https://lucifer.ren/blog/tags/谷歌/"}]},{"title":"史诗级更新,VSCODE 可无缝调试浏览器了!","slug":"vscode-brower-debug","date":"2021-07-27T16:00:00.000Z","updated":"2023-01-07T12:34:34.654Z","comments":true,"path":"2021/07/28/vscode-brower-debug/","link":"","permalink":"https://lucifer.ren/blog/2021/07/28/vscode-brower-debug/","excerpt":"2021-07-16 微软发布了一篇博客专门介绍了这个功能,VSCODE 牛逼! 在此之前,你想要在 vscode 内调试 chrome 或者 edge 需要借助于 Chrome Debugger 或者 the Microsoft Edge Debugger extension 这两款 vscode 扩展。 并且更重要的是,其仅能提供最基本的控制台功能,其他的诸如 network,element 是无法查看的,我们仍然需要到浏览器中查看。","text":"2021-07-16 微软发布了一篇博客专门介绍了这个功能,VSCODE 牛逼! 在此之前,你想要在 vscode 内调试 chrome 或者 edge 需要借助于 Chrome Debugger 或者 the Microsoft Edge Debugger extension 这两款 vscode 扩展。 并且更重要的是,其仅能提供最基本的控制台功能,其他的诸如 network,element 是无法查看的,我们仍然需要到浏览器中查看。 这是个什么功能更新之后,我们可以直接在 vscode 中 open link in chrome or edge,并且直接在 vscode 内完成诸如查看 element,network 等几乎所有的常见调试需要用到的功能。 效果截图: (edge devtools) (debug console) 如何使用使用方式非常简单,大家只需要在前端项目中按 F5 触发调试并进行简单的配置即可。这里给大家贴一份 lauch.json 配置,有了它就可以直接开启调试浏览器了。 123456789101112{ \"version\": \"0.2.0\", \"configurations\": [ { \"type\": \"pwa-msedge\", \"request\": \"launch\", \"name\": \"Launch Microsoft Edge and open the Edge DevTools\", \"url\": \"http://localhost:8080\", \"webRoot\": \"${workspaceFolder}\" } ]} 大家需要根据自己的情况修改 url 和 webRoot 等参数。 原理原理其实和 chrome debugger 扩展原理类似。也是基于 Chrome 的 devtool 协议,建立 websocket 链接。通过发送格式化的 json 数据进行交互,这样 vscode 就可以动态拿到运行时的一些信息。比如浏览器网络线程发送的请求以及 DOM 节点信息。 你可以通过 chrome devtool protocol 拿到很多信息,比如上面提到的 network 请求。 由于是 websocket 建立的双向链接,因此在 VSCODE 中改变 dom 等触发浏览器的修改也变得容易。我们只需要在 VSCODE(websocket client) 中操作后通过 websocket 发送一条 JSON 数据到浏览器(websocket server)即可。浏览器会根据收到的 JSON 数据进行一些操作,从效果上来看和用户直接在手动在浏览器中操作并无二致。 值得注意的,chrome devtool protocol 的客户端有很多,不仅仅是 NodeJS 客户端,Python,Java,PHP 等各种客户端一应俱全。 更多 Easier browser debugging with Developer Tools integration in Visual Studio Code vscode-js-debug chrome devtools-protocol Microsoft Edge (Chromium) DevTools Protocol overview","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"},{"name":"VSCODE","slug":"工具/VSCODE","permalink":"https://lucifer.ren/blog/categories/工具/VSCODE/"}],"tags":[{"name":"VSCODE","slug":"VSCODE","permalink":"https://lucifer.ren/blog/tags/VSCODE/"},{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"}]},{"title":"【91专访】 微软大佬 cabbage 分享算法面试心得","slug":"91algo-interview-cabbage","date":"2021-07-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.614Z","comments":true,"path":"2021/07/27/91algo-interview-cabbage/","link":"","permalink":"https://lucifer.ren/blog/2021/07/27/91algo-interview-cabbage/","excerpt":"背景最近得知 cabbage 拿到了微软的 offer,并在准备拿其他更大公司的 offer。就迫不及待地联系了他,希望他本人可以接受采访。于是这篇采访稿就和大家见面了。 cabbage 是一个做事情非常认真细致的人,对待工作一丝不苟,基本上事情交给他你就可以放心那种,这样的人谁不喜欢?我本人非常看好他,一定可以进更好的公司。 以下 Q 为 lucifer,A 为 cabbage。 ​","text":"背景最近得知 cabbage 拿到了微软的 offer,并在准备拿其他更大公司的 offer。就迫不及待地联系了他,希望他本人可以接受采访。于是这篇采访稿就和大家见面了。 cabbage 是一个做事情非常认真细致的人,对待工作一丝不苟,基本上事情交给他你就可以放心那种,这样的人谁不喜欢?我本人非常看好他,一定可以进更好的公司。 以下 Q 为 lucifer,A 为 cabbage。 ​ 采访 Q: 你是什么时候开始接触数据结构与算法(以下简称算法)的? A: 20 年准备跳大厂的时候才开始的,半路出家的前端开发,以前根本都用不到算法所以也就没去学过。 Q: 你是什么时候接触 91 天学算法(以下简称 91 天)的?从什么途径得知的? A: 通过 GitHub 上的 Lucifer 的算法 repo 关注的公众号,然后通过公众号了解到的。 Q: 91 天有给你带来了什么样的变化么? A: 从基本啥都不会到对基本的数据结构有一点了解,以前只会做 easy 的题,现在有部分 medium 的题也能做出来了,主要是学会了套模板。 Q: 学习算法过程中有“顿悟”的时刻么? A: 有吧,就突然有一天发现看到新题了会不自觉地去拿以前做题的套路去往上套,然后发现还真能用。 Q: 你比较擅长的算法是什么?可以给大家简单分享一下么? A: 说不上擅长,就是套模板的题我都还挺喜欢的,比如说找排列组合的上来就先写个 dfs,找满足条件的 substring 就先上一个 sliding window,大部分的时候套上模板就能解决了。 Q: 有没有什么想和刚入坑算法的同学分享的? A: 模板特别有用,不管会不会先背下来,背的同时就是一个理解的过程,91 算法的题解就可以帮你加深记忆也可以帮你更好的理解。另外刷题可以一次性把一个类型的都刷完,比如可以跟着 91 的分类刷,会更有效率。 Q: 相对而言,你觉得 91 天哪里做的还不够好?应该如何改进? A: 同类型的题可以再多加几道加深记忆,容易到困难的过渡不知道还能不能再 smooth 一点,没有 CS 背景的对于有些类型的题理解起来不容易,可能需要更多的 background,比如 DP 和 back track 这样的。(因为一直在做前端开发的缘故,思维总是习惯性的线性思维,不容易绕过来) Q: 在你学习算法的过程中,对你帮助最大的学习资料是什么? A: LeetCode 的题解讨论区以及油管的一些视频。 lucifer 注:有的 leetcode 题解和油管视频真的做的不错,我们也在《91 天学算法》的讲义中给大家进行了推荐。 Q: 愿意把 91 天分享给你的朋友么? A: 愿意。 lucifer 点评收到 cabbage 的建议。 下一期《91 天学算法》西法打算做几点改变。 目前正在和力扣官方合作,希望可以将《91 天学算法》整合力扣的学习计划和LeetBook。这样大家学习起来更加无缝,效率更高。届时也会有专门的交流群。 增加图, 模拟与枚举 的题目,以前这部分是自习的 增加排序 章节 前面增加了几道题目,相应其他章节需要删除题目,我打算将高频面试题删掉。 部分题解不够完善,再次打磨。 部分难度梯度跨度较大,增加一点缓冲题目或者对跨度较大的进行大篇幅讲解,尽量让大家不掉队。 另外现在已经不建议大家参加这一期(第四期)的《91 天学算法》了,大家可以等待下一期。并且下期的周期可能缩短为一个月左右,而不是现在的 91 天。这是因为每天一道题节奏太慢,会导致学习效果变差。而如果你觉得 91 天都跟不上,一个月岂不是更跟不上?这种担心我觉得是多余的,因为跟不上和时间关系不大,更多的是学习的方法技巧以及不断做题后的顿悟。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"将命令行工具转为 Web 页面?","slug":"ttw","date":"2021-07-04T16:00:00.000Z","updated":"2023-01-05T12:24:50.133Z","comments":true,"path":"2021/07/05/ttw/","link":"","permalink":"https://lucifer.ren/blog/2021/07/05/ttw/","excerpt":"这是个什么东西作为程序员不可避免的会与命令行打交道。我们会用很多的命令行工具,甚至自己开发一些命令行工具。那么如何将一个命令行工具转成 web 页面,变成一个云端应用,方便地与队友共享呢? 比如我做了一个可以将命令行转为 web 页面的工具叫 ttw(terminal to web),此时我想将 vi 变成一个 web 页面。 首先,可以执行如下命令: 1ttw vi 然后就返回一个 web 页面地址,比如是 https://lucifer.ren/ttw/dsuh8643&8934 打开后会发现就是 vim 的页面,然后你可以像本地命令行一样去进行操作,并得到实时的返回效果。 至此,我们就完成了将命令行工具转化为 web 页面的功能。","text":"这是个什么东西作为程序员不可避免的会与命令行打交道。我们会用很多的命令行工具,甚至自己开发一些命令行工具。那么如何将一个命令行工具转成 web 页面,变成一个云端应用,方便地与队友共享呢? 比如我做了一个可以将命令行转为 web 页面的工具叫 ttw(terminal to web),此时我想将 vi 变成一个 web 页面。 首先,可以执行如下命令: 1ttw vi 然后就返回一个 web 页面地址,比如是 https://lucifer.ren/ttw/dsuh8643&8934 打开后会发现就是 vim 的页面,然后你可以像本地命令行一样去进行操作,并得到实时的返回效果。 至此,我们就完成了将命令行工具转化为 web 页面的功能。 如何实现我们可以将命令行工具看成是从标准输入或者命令行参数读取输入,然后做一些处理,最后做出响应(包括读写文件,输出等)。 而做成 web 页面后,除了输出其实都是还是在本地的电脑上进行就好了。 因此我们要做的其实就是将输出部分转到 web 上而已。 基于此,我们只需要: 代理命令行的输入和输出。 将输出通过 web socket 同步到 web 页面。 显然,我们可以将同步到多个客户端。 整个架构可以分为三个部分,命令行客户端,web socket 客户端 和 server 端。 命名行客户端负责解析输入,并 fork 子进程调用真实的命令行工具,同时将代理的输入传递给它。接下来开启一个服务端的监听,同时将真实命令行的输出通过 web socket 同步到 web socket 客户端。 这里我们甚至可以做一些权限控制,比如哪些人可以做编辑,哪些人不可以等等。 有什么用我们可以做一些在线的控制台。比如阿里云的控制台,可以直接在 web 上操作远程的主机。 比如我本地使用命令行报错了(比如 npm run dev 报错),就可以通过这个工具实现远程投屏效果,给别人最最精准的信息,甚至可以用它来报 bug 也行。 更多用处,等你来探索。 相关工具推荐原理差不多讲完了,那有没有现成的工具可以做到呢? 当然有了,以下这两款工具都可以将你的命令行转化为 web 页面。 gotty termpair","categories":[{"name":"命令行","slug":"命令行","permalink":"https://lucifer.ren/blog/categories/命令行/"}],"tags":[{"name":"命令行","slug":"命令行","permalink":"https://lucifer.ren/blog/tags/命令行/"}]},{"title":"从零实现 vite(先导篇)","slug":"mono-vite","date":"2021-07-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.466Z","comments":true,"path":"2021/07/03/mono-vite/","link":"","permalink":"https://lucifer.ren/blog/2021/07/03/mono-vite/","excerpt":"来实现一个 vite ?","text":"来实现一个 vite ? 基本知识假设我们有如下两个 JS 文件。 即两个 esm 的模块, 并且 main.mjs 依赖 utils.mjs。 如上代码可以被支持 ESM 的浏览器所识别,但并不意味着其可以直接被运行。 比如我的代码依赖了 npm 包和一些相对路径,这些浏览器是无法识别的。 而 vite 则解决了这个问题。由于 vite 本质还是依赖了浏览器的特性,因此可以直接利用浏览器的诸如缓存的特点来提高性能。 除此之外, 每次修改文件,比如修改上面的 main.mjs 或者 utils.mjs 中的任意一个文件并不会导致“打包”全部文件。这是因为 vite 根本没有打包过程, 而是直接将修改过的文件热更新到浏览器的内存中。 比如,我修改了 main.js,那么就直接发送一个 http 去请求最新的 main.mjs 文件,而 utils.mjs 则可以继续使用浏览器缓存中的内容即可。 我画了一个简单的原理图给大家参考一下。 模块之间的关系如上图所示。并且这个时序指的是更新一个文件之后的更新流程。 我将其分成了若干模块,它们分别是: 浏览器。用于处理 ESM 文件系统。用于存储源代码文件。 vite-server。 响应浏览器,并返回内容。这些内容主要是最新的文件系统中的文件,除此外还有注入到 client 中的代码等。 hrm-sever。用于根据模块的依赖关系确定应该更新的模块,并触发相应的回调函数。 watcher。 监听文件系统的变更,当文件内容发生变化的时候,通知 hmr-server。之后 hmr-server 再去通过 websocket 通知浏览器获取最新的模块(按需请求)。 如何确定需要更新的模块我们可以根据 esm 的 import 关系生成一个依赖图。并将图中的所有点都放入一个哈希表中,key 可以是文件的请求路径,value 可以是模块本身,这样就可以根据请求路径在 $O(1)$ 的时间获取到指定的节点。之后我们可以遍历依赖图,并依次发起浏览器的 http 请求获取最新内容,并触发回调函数。 如下图红色的模块被更新,我们通过 $O(1)$ 时间获取到它,然后依次遍历虚线的两个模块,发起请求获取其最新模块内容,最后触发注册到这三个模块上的回调函数即可。 回调函数通过 module.hot.accept 注册,具体参考 hmr 相关文档。 一个更复杂的例子: 之后我会根据这个原理图带大家一步步实现一个 mono-vite(等西法有时间的)。","categories":[{"name":"vite","slug":"vite","permalink":"https://lucifer.ren/blog/categories/vite/"}],"tags":[{"name":"vite","slug":"vite","permalink":"https://lucifer.ren/blog/tags/vite/"},{"name":"模块","slug":"模块","permalink":"https://lucifer.ren/blog/tags/模块/"}]},{"title":"一个可以让你肆意摸鱼的 vscode 插件","slug":"moyu-1","date":"2021-06-22T16:00:00.000Z","updated":"2023-01-05T12:24:49.954Z","comments":true,"path":"2021/06/23/moyu-1/","link":"","permalink":"https://lucifer.ren/blog/2021/06/23/moyu-1/","excerpt":"最近发现摸鱼插件挺火的,尤其是 vscode,各种插件层出不穷。","text":"最近发现摸鱼插件挺火的,尤其是 vscode,各种插件层出不穷。 比如: 看股票 玩游戏 看小说 二次元 逛知乎 刷 Leetcode 等等。。。 不过被别人看到你的屏幕也是很容易发现。 很多人采取的措施是:大屏放代码或者需求文档给别人看,小屏幕(比如笔记本)给自己摸鱼。 不过你那个屏幕一天到晚都不变,而且连个字都不打,是不是有点太假了? 今天给大家介绍的插件就可以很好地解决这个问题,可以让 vscode 自己写代码,从而让别人误以为你很忙,这样你就可以安心摸鱼了。 插件介绍这是一个基于 VSCode 的模拟写代码,划水,摸鱼神器。代码写的快,提早完工被压榨怎么办?你需要一个模拟写代码工具,让代码自己重写一遍。当然也可以用来 CodeReview 做酷炫的效果让团队其他人猜猜后面的代码如何写。 安装方法在 vscode 的扩展栏,在应用商店中搜索“swimming”,点击”install”或“安装” 使用方法此工具需要配合低声音键盘,即使抚摸键盘也可以完美演绎敲键盘的样子! 选中代码后,右击菜单中选中 Code Rewriting 即可,也可以右键暂停或继续。如下图: 如果想要快捷键暂停代码重写,可以直接使用以下按键暂停: win 默认:ctrl+alt+shift+p mac 默认:alt+cmd+shift+p 如果想要快捷键放弃代码重写,可以直接使用以下按键停止: win 默认:ctrl+alt+x mac 默认:alt+cmd+x 接下来,你就可以让大屏自己写代码,偶尔瞄一眼,假装在干活,然后专注小屏摸鱼就行了。 重要说明请先提交代码,否则代码丢失概不负责! 仓库地址https://github.com/zy445566/vscode-plugin-swimming","categories":[],"tags":[]},{"title":"我离职了~","slug":"quit-esign","date":"2021-06-17T16:00:00.000Z","updated":"2023-01-05T12:24:49.918Z","comments":true,"path":"2021/06/18/quit-esign/","link":"","permalink":"https://lucifer.ren/blog/2021/06/18/quit-esign/","excerpt":"","text":"离开我最近向公司提了离职,结束了这段为期两年的工作经历。 再见啦~ 前几天在公众号看了一篇文章 一群北京高薪 90 后决定集体裸辞:花光积蓄同住 1000㎡、不买菜不吃外卖,变穷也开心!,特别有感触,这不就是理想的生活该有的样子么 ?很羡慕他们的生活态度和方式。 不过也仅仅是羡慕一下子罢了。真正做到这种程度需要天时地利人和。 既然我不回家种地,那会做什么呢? 起航接下来我可能会入职一份 WFH (远程办公)的工作。 WFH 有很多好处,比如没有通勤成本,办公地点自由(我甚至可以回老家)等。 当然也有一个明显的坏处,那就是交际圈可能会因此雪上加霜。 霜也只能霜了,咱也没办法不是? 不过综合来说我还是比较喜欢这种模式的,很早之前我也提到过将来想找一份 WFH 的工作,这也算是给将来做一些铺垫吧。 大家如果也喜欢远程办公这种模式的话,可以关注我,后续会给大家做一些内推。如果怕错过信息,可以进我的内推群,公众号力扣加加回复内推获取入群方式。 关于面试我发现这种提供 WFH 的外企都是比较注重算法的。因此如果你也想找 WFH 的工作,需要有一定的算法基础才行哦~ 说到算法,容我先打个广告 ~ —- 广告开始 如果你对自己的算法不自信,可以参加我的《91 天学算法》活动。公众号力扣加加回复91 即可获取参与方式了。 —- 广告结束 最近也参加了几场面试,大公司小公司都有。也陆续接到了几个 Offer,薪资涨幅也不错(事实证明刷算法真的有用啊!)。 西法我面的是前端岗位。面试的内容除了前端基础知识外,还有这些问的比较多: 算法 网络 开放设计题 系统架构(不仅限于前端方向) 除了极个别面试体验极差之外,大部分体验都很不错。 最后讲一下我刚才提到的 WFH 的工作的面试。整个面试过程还蛮轻松愉快的,难度中等偏高。面试内容来说大部分都是场景题(或者说是设计题),问题都比较发散。问题也不仅限于前端,后端也有涉及。另外即使是前端,也不仅限于 web 端,客户端等都有涉及。 这些发散的问题可以很好地对你的知识面以及知识深度做一个检测。这比八股文有意思多了。 基本上每一面都会有一个算法题,难度都是由浅入深,刚开始的话一般是简单难度,做出来后会有一些进阶这样子。不过都比较常规,没有什么特别偏,特别难为人的。 题目不方便透露,不过可以讲一下涉及到的考点: 树的遍历 自底向上递推 滑动窗口 平衡二叉树 二分 关注我的人大多数其实不是前端。那我就聊点算法面试的准备吧。关于算法面试的话,反正我经历的面试算法面试难度都不大。不过从网友的供述 上来看,有的公司还是有一丢丢难度的。 如果你需要准备算法面试的话,一定有一个完整的科学的学习路线,接下来: 如果你很自律,那么建议自己找网上的资料看看就行了。 如果你不那么自律,可以考找一个组织互相监督或者加入我的 91 天学算法。 不管你是哪种人,在面试前我都建议你做一些模拟题,或者打一些周赛,找找做题的感觉,尤其是有压力的情况下做题的感觉。如果有条件的话,可以找下心仪公司的模拟面试(就好像考驾照时候租模拟考试车一样)。 ok,先就讲这么多吧!改天再唠吧。 接下来一段时间可能就用这个小桌子办公了。回头如果搬家的话换个大桌子。 我们下一站再见!","categories":[],"tags":[{"name":"技能","slug":"技能","permalink":"https://lucifer.ren/blog/tags/技能/"},{"name":"PPT","slug":"PPT","permalink":"https://lucifer.ren/blog/tags/PPT/"}]},{"title":"手把手教你刷搜索","slug":"search","date":"2021-06-01T16:00:00.000Z","updated":"2023-04-29T06:09:46.056Z","comments":true,"path":"2021/06/02/search/","link":"","permalink":"https://lucifer.ren/blog/2021/06/02/search/","excerpt":"大话搜索搜索一般指在有限的状态空间中进行枚举,通过穷尽所有的可能来找到符合条件的解或者解的个数。根据搜索方式的不同,搜索算法可以分为 DFS,BFS,A*算法等。这里只介绍 DFS 和 BFS,以及发生在 DFS 上一种技巧-回溯。 搜索问题覆盖面非常广泛,并且在算法题中也占据了很高的比例。我甚至还在公开演讲中提到了 前端算法面试中搜索类占据了很大的比重,尤其是国内公司。 搜索专题中的子专题有很多,而大家所熟知的 BFS,DFS 只是其中特别基础的内容。除此之外,还有状态记录与维护,剪枝,联通分量,拓扑排序等等。这些内容,我会在这里一一给大家介绍。 另外即使仅仅考虑 DFS 和 BFS 两种基本算法,里面能玩的花样也非常多。比如 BFS 的双向搜索,比如 DFS 的前中后序,迭代加深等等。 关于搜索,其实在二叉树部分已经做了介绍了。而这里的搜索,其实就是进一步的泛化。数据结构不再局限于前面提到的数组,链表或者树。而扩展到了诸如二维数组,多叉树,图等。不过核心仍然是一样的,只不过数据结构发生了变化而已。 ​","text":"大话搜索搜索一般指在有限的状态空间中进行枚举,通过穷尽所有的可能来找到符合条件的解或者解的个数。根据搜索方式的不同,搜索算法可以分为 DFS,BFS,A*算法等。这里只介绍 DFS 和 BFS,以及发生在 DFS 上一种技巧-回溯。 搜索问题覆盖面非常广泛,并且在算法题中也占据了很高的比例。我甚至还在公开演讲中提到了 前端算法面试中搜索类占据了很大的比重,尤其是国内公司。 搜索专题中的子专题有很多,而大家所熟知的 BFS,DFS 只是其中特别基础的内容。除此之外,还有状态记录与维护,剪枝,联通分量,拓扑排序等等。这些内容,我会在这里一一给大家介绍。 另外即使仅仅考虑 DFS 和 BFS 两种基本算法,里面能玩的花样也非常多。比如 BFS 的双向搜索,比如 DFS 的前中后序,迭代加深等等。 关于搜索,其实在二叉树部分已经做了介绍了。而这里的搜索,其实就是进一步的泛化。数据结构不再局限于前面提到的数组,链表或者树。而扩展到了诸如二维数组,多叉树,图等。不过核心仍然是一样的,只不过数据结构发生了变化而已。 ​ 搜索的核心是什么?实际上搜索题目本质就是将题目中的状态映射为图中的点,将状态间的联系映射为图中的边。根据题目信息构建状态空间,然后对状态空间进行遍历,遍历过程需要记录和维护状态,并通过剪枝和数据结构等提高搜索效率。 状态空间的数据结构不同会导致算法不同。比如对数组进行搜索,和对树,图进行搜索就不太一样。 再次强调一下,我这里讲的数组,树和图是状态空间的逻辑结构,而不是题目给的数据结构。比如题目给了一个数组,让你求数组的搜索子集。虽然题目给的线性的数据结构数组,然而实际上我们是对树这种非线性数据结构进行搜索。这是因为这道题对应的状态空间是非线性的。 对于搜索问题,我们核心关注的信息有哪些?又该如何计算呢?这也是搜索篇核心关注的。而市面上很多资料讲述的不是很详细。搜索的核心需要关注的指标有很多,比如树的深度,图的 DFS 序,图中两点间的距离等等。这些指标都是完成高级算法必不可少的,而这些指标可以通过一些经典算法来实现。这也是为什么我一直强调一定要先学习好基础的数据结构与算法的原因。 不过要讲这些讲述完整并非容易,以至于如果完整写完可能需要花很多的时间,因此一直没有动手去写。 另外由于其他数据结构都可以看做是图的特例。因此研究透图的基本思想,就很容易将其扩展到其他数据结构上,比如树。因此我打算围绕图进行讲解,并逐步具象化到其他特殊的数据结构,比如树。 状态空间结论先行:状态空间其实就是一个图结构,图中的节点表示状态,图中的边表示状态之前的联系,这种联系就是题目给出的各种关系。 搜索题目的状态空间通常是非线性的。比如上面提到的例子:求一个数组的子集。这里的状态空间实际上就是数组的各种组合。 对于这道题来说,其状态空间的一种可行的划分方式为: 长度为 1 的子集 长度为 2 的子集 。。。 长度为 n 的子集(其中 n 为数组长度) 而如何确定上面所有的子集呢。 一种可行的方案是可以采取类似分治的方式逐一确定。 比如我们可以: 先确定某一种子集的第一个数是什么 再确定第二个数是什么 。。。 如何确定第一个数,第二个数。。。呢? 暴力枚举所有可能就可以了。 这就是搜索问题的核心,其他都是辅助,所以这句话请务必记住。 所谓的暴力枚举所有可能在这里就是尝试数组中所有可能的数字。 比如第一个数是什么?很明显可能是数组中任意一项。ok,我们就枚举 n 种情况。 第二个数呢?很明显可以是除了上面已经被选择的数之外的任意一个数。ok,我们就枚举 n - 1 种情况。 据此,你可以画出如下的决策树。 (下图描述的是对一个长度为 3 的数组进行决策的部分过程,树节点中的数字表示索引。即确定第一个数有三个选择,确定第二个数会根据上次的选择变为剩下的两个选择) 决策过程动图演示: 一些搜索算法就是基于这个朴素的思想,本质就是模拟这个决策树。这里面其实也有很多有趣的细节,后面我们会对其进行更加详细的讲解。而现在大家只需要对解空间是什么以及如何对解空间进行遍历有一点概念就行了。 后面我会继续对这个概念进行加深。 这里大家只要记住状态空间就是图,构建状态空间就是构建图。如何构建呢?当然是根据题目描述了 。 DFS 和 BFSDFS 和 BFS 是搜索的核心,贯穿搜索篇的始终,因此有必要先对其进行讲解。 DFSDFS 的概念来自于图论,但是搜索中 DFS 和图论中 DFS 还是有一些区别,搜索中 DFS 一般指的是通过递归函数实现暴力枚举。 如果不使用递归,也可以使用栈来实现。不过本质上是类似的。 首先将题目的状态空间映射到一张图,状态就是图中的节点,状态之间的联系就是图中的边,那么 DFS 就是在这种图上进行深度优先的遍历。而 BFS 也是类似,只不过遍历的策略变为了广度优先,一层层铺开而已。所以BFS 和 DFS 只是遍历这个状态图的两种方式罢了,如何构建状态图才是关键。 本质上,对上面的图进行遍历的话会生成一颗搜索树。为了避免重复访问,我们需要记录已经访问过的节点。这些是所有的搜索算法共有的,后面不再赘述。 如果你是在树上进行遍历,是不会有环的,也自然不需要为了避免环的产生记录已经访问的节点,这是因为树本质上是一个简单无环图。 算法流程 首先将根节点放入stack中。 从stack中取出第一个节点,并检验它是否为目标。如果找到目标,则结束搜寻并回传结果。否则将它某一个尚未检验过的直接子节点加入stack中。 重复步骤 2。 如果不存在未检测过的直接子节点。将上一级节点加入stack中。重复步骤 2。 重复步骤 4。 若stack为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。 这里的 stack 可以理解为自实现的栈,也可以理解为调用栈 算法模板下面我们借助递归来完成 DFS。 12345678910111213const visited = {}function dfs(i) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } visited[i] = true // 将当前状态标为已搜索 for (根据i能到达的下个状态j) { if (!visited[j]) { // 如果状态j没有被搜索过 dfs(j) } }} 常用技巧前序遍历与后序遍历DFS 常见的形式有前序和后序。二者的使用场景也是截然不同的。 上面讲述了搜索本质就是在状态空间进行遍历,空间中的状态可以抽象为图中的点。那么如果搜索过程中,当前点的结果需要依赖其他节点(大多数情况都会有依赖),那么遍历顺序就变得重要。 比如当前节点需要依赖其子节点的计算信息,那么使用后序遍历自底向上递推就显得必要了。而如果当前节点需要依赖其父节点的信息,那么使用先序遍历进行自顶向下的递归就不难想到。 比如下文要讲的计算树的深度。由于树的深度的递归公式为: $f(x) = f(y) + 1$。其中 f(x) 表示节点 x 的深度,并且 x 是 y 的子节点。很明显这个递推公式的 base case 就是根节点深度为一,通过这个 base case 我们可以递推求出树中任意节点的深度。显然,使用先序遍历自顶向下的方式统计是简单而又直接的。 再比如下文要讲的计算树的子节点个数。由于树的子节点递归公式为: $f(x) = sum_{i=0}^{n}{f(a_i)}$ 其中 x 为树中的某一个节点,$a_i$ 为树中节点的子节点。而 base case 则是没有任何子节点(也就是叶子节点),此时 $f(x) = 1$。 因此我们可以利用后序遍历自底向上来完成子节点个数的统计。 关于从递推关系分析使用何种遍历方法, 我在《91 天学算法》中的基础篇中的《模拟,枚举与递推》子专题中对此进行了详细的描述。91 学员可以直接进行查看。关于树的各种遍历方法,我在树专题中进行了详细的介绍。 迭代加深迭代加深本质上是一种可行性的剪枝。关于剪枝,我会在后面的《回溯与剪枝》部分做更多的介绍。 所谓迭代加深指的是在递归树比较深的时候,通过设定递归深度阈值,超过阈值就退出的方式主动减少递归深度的优化手段。这种算法成立的前提是题目中告诉我们答案不超过 xxx,这样我们可以将 xxx 作为递归深度阈值,这样不仅不会错过正确解,还能在极端情况下有效减少不必须的运算。 具体地,我们可以使用自顶向下的方式记录递归树的层次,和上面介绍如何计算树深度的方法是一样的。接下来在主逻辑前增加当前层次是否超过阈值的判断即可。 主代码: 12345MAX_LEVEL = 20def dfs(root, level): if level > MAX_LEVEL: return # 主逻辑dfs(root, 0) 这种技巧在实际使用中并不常见,不过在某些时候能发挥意想不到的作用。 双向搜索有时候问题规模很大,直接搜索会超时。此时可以考虑从起点搜索到问题规模的一半。然后将此过程中产生的状态存起来。接下来目标转化为在存储的中间状态中寻找满足条件的状态。进而达到降低时间复杂度的效果。 上面的说法可能不太容易理解。 接下来通过一个例子帮助大家理解。 题目地址https://leetcode-cn.com/problems/closest-subsequence-sum/ 题目描述123456789101112131415161718192021222324252627282930313233给你一个整数数组 nums 和一个目标值 goal 。你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和为 sum ,你需要 最小化绝对差 abs(sum - goal) 。返回 abs(sum - goal) 可能的 最小值 。注意,数组的子序列是通过移除原始数组中的某些元素(可能全部或无)而形成的数组。 示例 1:输入:nums = [5,-7,3,5], goal = 6输出:0解释:选择整个数组作为选出的子序列,元素和为 6 。子序列和与目标值相等,所以绝对差为 0 。示例 2:输入:nums = [7,-9,15,-2], goal = -5输出:1解释:选出子序列 [7,-9,-2] ,元素和为 -4 。绝对差为 abs(-4 - (-5)) = abs(1) = 1 ,是可能的最小值。示例 3:输入:nums = [1,2,3], goal = -7输出:7 提示:1 <= nums.length <= 40-10^7 <= nums[i] <= 10^7-10^9 <= goal <= 10^9 思路从数据范围可以看出,这道题大概率是一个 $O(2^m)$ 时间复杂度的解法,其中 m 是 nums.length 的一半。 为什么?首先如果题目数组长度限制为小于等于 20,那么大概率是一个 $O(2^n)$ 的解法。 如果这个也不知道,建议看一下这篇文章 https://lucifer.ren/blog/2020/12/21/shuati-silu3/ 另外我的刷题插件 leetcode-cheatsheet 也给出了时间复杂度速查表供大家参考。 将 40 砍半恰好就可以 AC 了。实际上,40 这个数字就是一个强有力的信号。 回到题目中。我们可以用一个二进制位表示原数组 nums 的一个子集,这样用一个长度为 $2^n$ 的数组就可以描述 nums 的所有子集了,这就是状态压缩。一般题目数据范围是 <= 20 都应该想到。 这里 40 折半就是 20 了。 如果不熟悉状态压缩,可以看下我的这篇文章 状压 DP 是什么?这篇题解带你入门 接下来,我们使用动态规划求出所有的子集和。 令 dp[i] 表示选择情况如 i 所示的和。什么是选择情况如 i 所示呢? 比如我要求 nums 的子集和。那么 nums 的子集有 $2^n$ 个,即 nums 中每一个数都有选择和不选择两者情况。因此一共就有$2^n$种。如果用一个数字的二进制来表示这种选择情况,其中 0 表示选择 1 表示不选择,那么一个位数足够的数(二进制位数需要大于 n)可以用来表示一种可能的选择情况。 我们可以枚举数组的每一项,对于每一项我们都考虑将其加入到选择中。那么转移方程为 : dp[(1 << i) + j] = dp[j] + A[i],其中 j 为 i 的子集, i 和 j 的二进制表示的是 nums 的选择情况。 动态规划求子集和代码如下: 1234567def combine_sum(A): n = len(A) dp = [0] * (1 << n) for i in range(n): for j in range(1 << i): dp[(1 << i) + j] = dp[j] + A[i] # 加 i 加入选择 return dp 接下来,我们将 nums 平分为两部分,分别计算子集和: 123n = len(nums)c1 = combine_sum(nums[: n // 2])c2 = combine_sum(nums[n // 2 :]) 其中 c1 就是前半部分数组的子集和,c2 就是后半部分的子集和。 接下来问题转化为:在两个数组 c1 和 c2中找两个数,其和最接近 goal。而这是一个非常经典的双指针问题,逻辑类似两数和。 只不过两数和是一个数组挑两个数,这里是两个数组分别挑一个数罢了。 这里其实只需要一个指针指向一个数组的头,另外一个指向另外一个数组的尾即可。 代码不难写出: 12345678910111213141516def combine_closest(c1, c2): # 先排序以便使用双指针 c1.sort() c2.sort() ans = float(\"inf\") i, j = 0, len(c2) - 1 while i < len(c1) and j >= 0: _sum = c1[i] + c2[j] ans = min(ans, abs(_sum - goal)) if _sum > goal: j -= 1 elif _sum < goal: i += 1 else: return 0 return ans 上面这个代码不懂的多看看两数和。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122232425262728class Solution: def minAbsDifference(self, nums: List[int], goal: int) -> int: def combine_sum(A): n = len(A) dp = [0] * (1 << n) for i in range(n): for j in range(1 << i): dp[(1 << i) + j] = dp[j] + A[i] return dp def combine_closest(c1, c2): c1.sort() c2.sort() ans = float(\"inf\") i, j = 0, len(c2) - 1 while i < len(c1) and j >= 0: _sum = c1[i] + c2[j] ans = min(ans, abs(_sum - goal)) if _sum > goal: j -= 1 elif _sum < goal: i += 1 else: return 0 return ans n = len(nums) return combine_closest(combine_sum(nums[: n // 2]), combine_sum(nums[n // 2 :])) 复杂度分析 令 n 为数组长度, m 为 $\\frac{n}{2}$。 时间复杂度:$O(m*2^m)$ 空间复杂度:$O(2^m)$ 相关题目推荐: 16. 最接近的三数之和 1049. 最后一块石头的重量 II 1774. 最接近目标价格的甜点成本 这道题和双向搜索有什么关系呢? 回一下开头我的话:有时候问题规模很大,直接搜索会超时。此时可以考虑从起点搜索到问题规模的一半。然后将此过程中产生的状态存起来。接下来目标转化为在存储的中间状态中寻找满足条件的状态。进而达到降低时间复杂度的效果。 对应这道题,我们如果直接暴力搜索。那就是枚举所有子集和,然后找到和 goal 最接近的,思路简单直接。可是这样会超时,那么就搜索到一半, 然后将状态存起来(对应这道题就是存到了 dp 数组)。接下来问题转化为两个 dp 数组的运算。该算法,本质上是将位于指数位的常数项挪动到了系数位。这是一种常见的双向搜索,我姑且称为 DFS 的双向搜索。目的是为了和后面的 BFS 双向搜索进行区分。 BFSBFS 也是图论中算法的一种。不同于 DFS, BFS 采用横向搜索的方式,从初始状态一层层展开直到目标状态,在数据结构上通常采用队列结构。 具体地,我们不断从队头取出状态,然后将此状态对应的决策产生的所有新的状态推入队尾,重复以上过程直至队列为空即可。 注意这里有两个关键点: 将此状态对应的决策。 实际上这句话指的就是状态空间中的图的边,而不管是 DFS 和 BFS 边都是确定的。也就是说不管是 DFS 还是 BFS 这个决策都是一样的。不同的是什么?不同的是进行决策的方向不同。 所有新的状态推入队尾。上面说 BFS 和 DFS 是进行决策的方向不同。这就可以通过这个动作体现出来。由于直接将所有状态空间中的当前点的邻边放到队尾。由队列的先进先出的特性,当前点的邻边访问完成之前是不会继续向外扩展的。这一点大家可以和 DFS 进行对比。 最简单的 BFS 每次扩展新的状态就增加一步,通过这样一步步逼近答案。其实也就等价于在一个权值为 1 的图上进行 BFS。由于队列的单调性和二值性,当第一次取出目标状态时就是最少的步数。基于这个特性,BFS 适合求解一些最少操作的题目。 关于单调性和二值性,我会在后面的 BFS 和 DFS 的对比那块进行讲解。 前面 DFS 部分提到了不管是什么搜索都需要记录和维护状态,其中一个就是节点访问状态以防止环的产生。而 BFS 中我们常常用来求点的最短距离。值得注意的是,有时候我们会使用一个哈希表 dist 来记录从源点到图中其他点的距离。这个 dist 也可以充当防止环产生的功能,这是因为第一次到达一个点后再次到达此点的距离一定比第一次到达大,利用这点就可知道是否是第一次访问了。 算法流程 首先将根节点放入队列中。 从队列中取出第一个节点,并检验它是否为目标。 如果找到目标,则结束搜索并回传结果。 否则将它所有尚未检验过的直接子节点加入队列中。 若队列为空,表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传“找不到目标”。 重复步骤 2。 算法模板123456789101112131415const visited = {}function bfs() { let q = new Queue() q.push(初始状态) while(q.length) { let i = q.pop() if (visited[i]) continue for (i的可抵达状态j) { if (j 合法) { q.push(j) } } } // 找到所有合法解} 常用技巧双向搜索题目地址(126. 单词接龙 II)https://leetcode-cn.com/problems/word-ladder-ii/ 题目描述12345678910111213141516171819202122232425262728293031323334353637按字典 wordList 完成从单词 beginWord 到单词 endWord 转化,一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk 这样的单词序列,并满足:每对相邻的单词之间仅有单个字母不同。转换过程中的每个单词 si(1 <= i <= k)必须是字典 wordList 中的单词。注意,beginWord 不必是字典 wordList 中的单词。sk == endWord给你两个单词 beginWord 和 endWord ,以及一个字典 wordList 。请你找出并返回所有从 beginWord 到 endWord 的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。每个序列都应该以单词列表 [beginWord, s1, s2, ..., sk] 的形式返回。 示例 1:输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]输出:[["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]解释:存在 2 种最短的转换序列:"hit" -> "hot" -> "dot" -> "dog" -> "cog""hit" -> "hot" -> "lot" -> "log" -> "cog"示例 2:输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]输出:[]解释:endWord "cog" 不在字典 wordList 中,所以不存在符合要求的转换序列。 提示:1 <= beginWord.length <= 7endWord.length == beginWord.length1 <= wordList.length <= 5000wordList[i].length == beginWord.lengthbeginWord、endWord 和 wordList[i] 由小写英文字母组成beginWord != endWordwordList 中的所有单词 互不相同 思路这道题就是我们日常玩的成语接龙游戏。即让你从 beginWord 开始, 接龙的 endWord。让你找到最短的接龙方式,如果有多个,则全部返回。 不同于成语接龙的字首接字尾。这种接龙需要的是下一个单词和上一个单词仅有一个单词不同。 我们可以对问题进行抽象:即构建一个大小为 n 的图,图中的每一个点表示一个单词,我们的目标是找到一条从节点 beginWord 到节点 endWord 的一条最短路径。 这是一个不折不扣的图上 BFS 的题目。套用上面的解题模板可以轻松解决。唯一需要注意的是如何构建图。更进一步说就是如何构建边。 由题目信息的转换规则:每对相邻的单词之间仅有单个字母不同。不难知道,如果两个单词的仅有单个字母不同 ,就说明两者之间有一条边。 明白了这一点,我们就可以构建邻接矩阵了。 核心代码: 1234neighbors = collections.defaultdict(list)for word in wordList: for i in range(len(word)): neighbors[word[:i] + \"*\" + word[i + 1 :]].append(word) 构建好了图。 BFS 剩下要做的就是明确起点和终点就好了。对于这道题来说,起点是 beginWord,终点是 endWord。 那我们就可以将 beginWord 入队。不断在图上做 BFS,直到第一次遇到 endWord 就好了。 套用上面的 BFS 模板,不难写出如下代码: 这里我用了 cost 而不是 visitd,目的是为了让大家见识多种写法。下面的优化解法会使用 visited 来记录。 12345678910111213141516171819202122232425class Solution: def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]: cost = collections.defaultdict(lambda: float(\"inf\")) cost[beginWord] = 0 neighbors = collections.defaultdict(list) ans = [] for word in wordList: for i in range(len(word)): neighbors[word[:i] + \"*\" + word[i + 1 :]].append(word) q = collections.deque([[beginWord]]) while q: path = q.popleft() cur = path[-1] if cur == endWord: ans.append(path.copy()) else: for i in range(len(cur)): for neighbor in neighbors[cur[:i] + \"*\" + cur[i + 1 :]]: if cost[cur] + 1 <= cost[neighbor]: q.append(path + [neighbor]) cost[neighbor] = cost[cur] + 1 return ans 当终点可以逆向搜索的时候,我们也可以尝试双向 BFS。更本质一点就是:如果你构建的状态空间的边是双向的,那么就可以使用双向 BFS。 和 DFS 的双向搜索思想是类似的。我们只需要使用两个队列分别存储中起点和终点进行扩展的节点(我称其为起点集与终点集)即可。当起点和终点在某一时刻交汇了,说明找到了一个从起点到终点的路径,其路径长度就是两个队列扩展的路径长度和。 以上就是双向搜索的大体思路。用图来表示就是这样的: 如上图,我们从起点和重点(A 和 Z)分别开始搜索,如果起点的扩展状态和终点的扩展状态重叠(本质上就是队列中的元素重叠了),那么我们就知道了一个从节点到终点的最短路径。 动图演示: 看到这里有必要暂停一下插几句话。 为什么双向搜索就快了?什么情况都会更快么?那为什么不都用双向搜索?有哪些使用条件? 我们一个个回答。 为什么双向搜索更快了?通过上面的图我们发现通常刚开始的时候边比较少,队列中的数据也比较少。而随着搜索的进行,搜索树越来越大, 队列中的节点随之增多。和上面双向搜索类似,这种增长速度很多情况下是指数级别的,而双向搜索可以将指数的常系数移动到多项式系数。如果不使用双向搜索那么搜索树大概是这样的: 可以看出搜索树大了很多,以至于很多点我都画不下,只好用 ”。。。“ 来表示。 什么情况下更快?相比于单向搜索,双向搜索通常更快。当然也有例外,举个极端的例子,假如从起点到终点只要一条路径,那么无论使用单向搜索还是双向搜索结果都是一样。 如图使用单向搜索还是双向搜索都是一样的。 为什么不都用双向搜索?实际上做题中,我建议大家尽量使用单向搜索,因为写起来更节点,并且大多数都可以通过所有的测试用例。除非你预估到可能会超时或者提交后发现超时的时候再尝试使用双向搜索。 有哪些使用条件?正如前面所说:”终点可以逆向搜索的时候,可以尝试双向 BFS。更本质一点就是:如果你构建的状态空间的边是双向的,那么就可以使用双向 BFS。“ 让我们继续回到这道题。为了能够判断两者是否交汇,我们可以使用两个 hashSet 分别存储起点集合终点集。当一个节点既出现起点集又出现在终点集,那就说明出现了交汇。 为了节省代码量以及空间消耗,我没有使用上面的队列,而是直接使用了哈希表来代替队列。这种做法可行的关键仍然是上面提到的队列的二值性和单调性。 由于新一轮的出队列前,队列中的权值都是相同的。因此从左到右遍历或者从右到左遍历,甚至是任意顺序遍历都是无所谓的。(很多题都无所谓)因此使用哈希表而不是队列也是可以的。这点需要引起大家的注意。希望大家对 BFS 的本质有更深的理解。 那我们是不是不需要队列,就用哈希表,哈希集合啥的存就行了?非也!我会在双端队列部分为大家揭晓。 这道题的具体算法: 定义两个队列:q1 和 q2 ,分别从起点和终点进行搜索。 构建邻接矩阵 每次都尝试从 q1 和 q2 中的较小的进行扩展。这样可以达到剪枝的效果。 如果 q1 和 q2 交汇了,则将两者的路径拼接起来即可。 代码 语言支持:Python3 Python3 Code: 1234567891011121314151617181920212223242526272829303132333435363738class Solution: def findLadders(self, beginWord: str, endWord: str, wordList: list) -> list: # 剪枝 1 if endWord not in wordList: return [] ans = [] visited = set() q1, q2 = {beginWord: [[beginWord]]}, {endWord: [[endWord]]} steps = 0 # 预处理,空间换时间 neighbors = collections.defaultdict(list) for word in wordList: for i in range(len(word)): neighbors[word[:i] + \"*\" + word[i + 1 :]].append(word) while q1: # 剪枝 2 if len(q1) > len(q2): q1, q2 = q2, q1 nxt = collections.defaultdict(list) for _ in range(len(q1)): word, paths = q1.popitem() visited.add(word) for i in range(len(word)): for neighbor in neighbors[word[:i] + \"*\" + word[i + 1 :]]: if neighbor in q2: # 从 beginWord 扩展过来的 if paths[0][0] == beginWord: ans += [path1 + path2[::-1] for path1 in paths for path2 in q2[neighbor]] # 从 endWord 扩展过来的 else: ans += [path2 + path1[::-1] for path1 in paths for path2 in q2[neighbor]] if neighbor in wordList and neighbor not in visited: nxt[neighbor] += [path + [neighbor] for path in paths] steps += 1 # 剪枝 3 if ans and steps + 2 > len(ans[0]): break q1 = nxt return ans 总结我想通过这道题给大家传递的知识点很多。分别是: 队列不一定非得是常规的队列,也可以是哈希表等。不过某些情况必须是双端队列,这个等会讲双端队列给大家讲。 双向 BFS 是只适合双向图。也就是说从终点也往前推。 双向 BFS 从较少状态的一端进行扩展可以起到剪枝的效果 visitd 和 dist/cost 都可以起到记录点访问情况以防止环的产生的作用。不过 dist 的作用更多,相应空间占用也更大。 双端队列上面提到了 BFS 本质上可以看做是在一个边权值为 1 的图上进行遍历。实际上,我们可以进行一个简单的扩展。如果图中边权值不全是 1,而是 0 和 1 呢?这样其实我们用到双端队列。 双端队列可以在头部和尾部同时进行插入和删除,而普通的队列仅允许在头部删除,在尾部插入。 使用双端队列,当每次取出一个状态的时候。如果我们可以无代价的进行转移,那么就可以将其直接放在队头,否则放在队尾。由前面讲的队列的单调性和二值性不难得出算法的正确性。而如果状态转移是有代价的,那么就将其放到队尾即可。这也是很多语言提供的内置数据结构是双端队列,而不是队列的原因之一。 如下图: 上面的队列是普通的队列。 而下面的双端队列,可以看出我们在队头插队了一个 B。 动图演示: 思考:如果图对应的权值不出 0 和 1,而是任意正整数呢? 前面我们提到了是不是不需要队列,就用哈希表,哈希集合啥的存就行了? 这里为大家揭秘。不可以的。因为哈希表无法处理这里的权值为 0 的情况。 DFS 和 BFS 的对比BFS 和 DFS 分别处理什么样的问题?两者究竟有什么样的区别?这些都值得我们认真研究。 简单来说,不管是 DFS 还是 BFS 都是对题目对应的状态空间进行搜索。 具体来说,二者区别在于: DFS 在分叉点会任选一条深入进入,遇到终点则返回,再次返回到分叉口后尝试下一个选择。基于此,我们可以在路径上记录一些数据。由此也可以衍生出很多有趣的东西。 如下图,我们遍历到 A,有三个选择。此时我们可以任意选择一条,比如选择了 B,程序会继续往下进行选择分支 2,3 。。。 如下动图演示了一个典型的 DFS 流程。后面的章节,我们会给大家带来更复杂的图上 DFS。 BFS 在分叉点会选择搜索的路径各尝试一次。使用队列来存储待处理的元素时,队列中最多只会有两层的元素,且满足单调性,即相同层的元素在一起。基于这个特点有很多有趣的优化。 如下图,广度优先遍历会将搜索的选择全部选择一遍会才会进入到下一层。和上面一样,我给大家标注了程序执行的一种可能的顺序。 可以发现,和我上面说的一样。右侧的队列始终最多有两层的节点,并且相同层的总在一起,换句话说队列的元素在层上满足单调性。 如下动图演示了一个典型的 BFS 流程。后面的章节,我们会给大家带来更复杂的图上 BFS。 总结以上就是《搜索篇(上)》的所有内容了。总结一下搜索篇的解题思路: 根据题目信息构建状态空间(图)。 对图进行遍历(BFS 或者 DFS) 记录和维护状态。(比如 visited 维护访问情况, 队列和栈维护状态的决策方向等等) 我们花了大量的篇幅对 BFS 和 DFS 进行了详细的讲解,包括两个的对比。 核心点需要大家注意: DFS 通常都是有递推关系的,而递归关系就是图的边。根据递归关系大家可以选择使用前序遍历或者后序遍历。 BFS 由于其单调性,因此适合求解最短距离问题。 。。。 双向搜索的本质是将复杂度的常数项从一个影响较大的位置(比如指数位)移到了影响较小的位置(比如系数位)。 搜索篇知识点比较密集,希望大家多多总结复习。 下一节,我们介绍: 回溯与剪枝。 常用的指标与统计方法。具体包括: 树的深度与子树大小 图的 DFS 序 图的拓扑序 图的联通分量 下节内容会首发在《91 天学算法》。想参加的可以戳这里了解详情:https://github.com/azl397985856/leetcode/discussions/532","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"搜索","slug":"搜索","permalink":"https://lucifer.ren/blog/categories/搜索/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"BFS","slug":"BFS","permalink":"https://lucifer.ren/blog/tags/BFS/"},{"name":"搜索","slug":"搜索","permalink":"https://lucifer.ren/blog/tags/搜索/"},{"name":"DFS","slug":"DFS","permalink":"https://lucifer.ren/blog/tags/DFS/"},{"name":"回溯","slug":"回溯","permalink":"https://lucifer.ren/blog/tags/回溯/"}]},{"title":"某区块链公司竟然用这道算法题来面试","slug":"interview-fe-bi","date":"2021-05-23T16:00:00.000Z","updated":"2023-01-07T12:34:34.265Z","comments":true,"path":"2021/05/24/interview-fe-bi/","link":"","permalink":"https://lucifer.ren/blog/2021/05/24/interview-fe-bi/","excerpt":"最近有粉丝和我交流面试遇到的算法题。其中有一道题比较有意思,分享给大家。 ta 说自己面试了一家某大型区块链的公司的前端岗位,被问到了一道算法题。这道题也是一个非常常见的题目了,力扣中也有原题 110. 平衡二叉树,难度为简单。 不过面试官做了一点点小的扩展,难度瞬间升级了。我们来看下面试官做了什么扩展。","text":"最近有粉丝和我交流面试遇到的算法题。其中有一道题比较有意思,分享给大家。 ta 说自己面试了一家某大型区块链的公司的前端岗位,被问到了一道算法题。这道题也是一个非常常见的题目了,力扣中也有原题 110. 平衡二叉树,难度为简单。 不过面试官做了一点点小的扩展,难度瞬间升级了。我们来看下面试官做了什么扩展。 题目题目是《判断一棵树是否为平衡二叉树》,所谓平衡二叉树指的是二叉树中所有节点的左右子树的深度之差不超过 1。输入参数是二叉树的根节点 root,输出是一个 bool 值。 代码会被以如下的方式调用: 123console.log(isBalance([3, 9, 2, null, null, 5, 5]));console.log(isBalance([1, 1, 2, 3, 4, null, null, 4, 4])); 思路求解的思路就是围绕着二叉树的定义来进行即可。 对于二叉树中的每一个节点都: 分别计算左右子树的高度,如果高度差大于 1,直接返回 false 否则继续递归调用左右子节点,如果左右子节点全部都是平衡二叉树,那么返回 true。否则返回 false 可以看出我们的算法就是死扣定义。 计算节点深度比较容易,既可以使用前序遍历 + 参考扩展的方式,也可使用后序遍历的方式,这里我用的是前序遍历 + 参数扩展。 对此不熟悉的强烈建议看一下这篇文章 几乎刷完了力扣所有的树题,我发现了这些东西。。。 于是你可以写出如下的代码。 1234567891011121314function getDepth(root, d = 0) { if (!root) return 0; return max(getDepth(root.left, d + 1), getDepth(root.right, d + 1));}function dfs(root) { if (!root) return true; if (abs(getDepth(root.left), getDepth(root.right)) > 1) return false; return dfs(root.left) && dfs(root.right);}function isBalance(root) { return dfs(root);} 不难发现,这道题的结果和节点(TreeNode) 的 val 没有任何关系,val 是多少完全不影响结果。 这就完了么?可以仔细观察题目给的使用示例,会发现题目给的是 nodes 数组,并不是二叉树的根节点 root。 因此我们需要先构建二叉树。 构建二叉树本质上是一个反序列的过程。要想知道如何反序列化,肯定要先知道序列化。 而二叉树序列的方法有很多啊?题目给的是哪种呢?这需要你和面试官沟通。很有可能面试官在等着你问他呢!!! 反序列化我们先来看下什么是序列化,以下定义来自维基百科: 序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 可见,序列化和反序列化在计算机科学中的应用还是非常广泛的。就拿 LeetCode 平台来说,其允许用户输入形如: 1[1,2,3,null,null,4,5] 这样的数据结构来描述一颗树: ([1,2,3,null,null,4,5] 对应的二叉树) 其实序列化和反序列化只是一个概念,不是一种具体的算法,而是很多的算法。并且针对不同的数据结构,算法也会不一样。 前置知识阅读本文之前,需要你对树的遍历以及 BFS 和 DFS 比较熟悉。如果你还不熟悉,推荐阅读一下相关文章之后再来看。或者我这边也写了一个总结性的文章二叉树的遍历,你也可以看看。 前言我们知道:二叉树的深度优先遍历,根据访问根节点的顺序不同,可以将其分为前序遍历,中序遍历, 后序遍历。即如果先访问根节点就是前序遍历,最后访问根节点就是后序遍历,其它则是中序遍历。而左右节点的相对顺序是不会变的,一定是先左后右。 当然也可以设定为先右后左。 并且知道了三种遍历结果中的任意两种即可还原出原有的树结构。这不就是序列化和反序列化么?如果对这个比较陌生的同学建议看看我之前写的《构造二叉树系列》 有了这样一个前提之后算法就自然而然了。即先对二叉树进行两次不同的遍历,不妨假设按照前序和中序进行两次遍历。然后将两次遍历结果序列化,比如将两次遍历结果以逗号“,” join 成一个字符串。 之后将字符串反序列即可,比如将其以逗号“,” split 成一个数组。 序列化: 1234567891011121314class Solution: def preorder(self, root: TreeNode): if not root: return [] return [str(root.val)] +self. preorder(root.left) + self.preorder(root.right) def inorder(self, root: TreeNode): if not root: return [] return self.inorder(root.left) + [str(root.val)] + self.inorder(root.right) def serialize(self, root): ans = '' ans += ','.join(self.preorder(root)) ans += '$' ans += ','.join(self.inorder(root)) return ans 反序列化: 这里我直接用了力扣 105. 从前序与中序遍历序列构造二叉树 的解法,一行代码都不改。 1234567891011121314151617class Solution: def deserialize(self, data: str): preorder, inorder = data.split('$') if not preorder: return None return self.buildTree(preorder.split(','), inorder.split(',')) def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 实际上inorder 和 preorder 一定是同时为空的,因此你无论判断哪个都行 if not preorder: return None root = TreeNode(preorder[0]) i = inorder.index(root.val) root.left = self.buildTree(preorder[1:i + 1], inorder[:i]) root.right = self.buildTree(preorder[i + 1:], inorder[i+1:]) return root 实际上这个算法是不一定成立的,原因在于树的节点可能存在重复元素。也就是说我前面说的知道了三种遍历结果中的任意两种即可还原出原有的树结构是不对的,严格来说应该是如果树中不存在重复的元素,那么知道了三种遍历结果中的任意两种即可还原出原有的树结构。 聪明的你应该发现了,上面我的代码用了 i = inorder.index(root.val),如果存在重复元素,那么得到的索引 i 就可能不是准确的。但是,如果题目限定了没有重复元素则可以用这种算法。但是现实中不出现重复元素不太现实,因此需要考虑其他方法。那究竟是什么样的方法呢? 答案是记录空节点。接下来进入正题。 DFS序列化我们来模仿一下力扣的记法。 比如:[1,2,3,null,null,4,5](本质上是 BFS 层次遍历),对应的树如下: 选择这种记法,而不是 DFS 的记法的原因是看起来比较直观。并不代表我们这里是要讲 BFS 的序列化和反序列化。 序列化的代码非常简单, 我们只需要在普通的遍历基础上,增加对空节点的输出即可(普通的遍历是不处理空节点的)。 比如我们都树进行一次前序遍历的同时增加空节点的处理。选择前序遍历的原因是容易知道根节点的位置,并且代码好写,不信你可以试试。 因此序列化就仅仅是普通的 DFS 而已,直接给大家看看代码。 Python 代码: 123456789101112class Codec: def serialize_dfs(self, root, ans): # 空节点也需要序列化,否则无法唯一确定一棵树,后不赘述。 if not root: return ans + '#,' # 节点之间通过逗号(,)分割 ans += str(root.val) + ',' ans = self.serialize_dfs(root.left, ans) ans = self.serialize_dfs(root.right, ans) return ans def serialize(self, root): # 由于最后会添加一个额外的逗号,因此需要去除最后一个字符,后不赘述。 return self.serialize_dfs(root, '')[:-1] Java 代码: 12345678910111213141516public class Codec { public String serialize_dfs(TreeNode root, String str) { if (root == null) { str += \"None,\"; } else { str += str.valueOf(root.val) + \",\"; str = serialize_dfs(root.left, str); str = serialize_dfs(root.right, str); } return str; } public String serialize(TreeNode root) { return serialize_dfs(root, \"\"); }} [1,2,3,null,null,4,5] 会被处理为1,2,#,#,3,4,#,#,5,#,# 我们先看一个短视频: (动画来自力扣) 反序列化反序列化的第一步就是将其展开。以上面的例子来说,则会变成数组:[1,2,#,#,3,4,#,#,5,#,#],然后我们同样执行一次前序遍历,每次处理一个元素,重建即可。由于我们采用的前序遍历,因此第一个是根元素,下一个是其左子节点,下下一个是其右子节点。 Python 代码: 1234567891011121314def deserialize_dfs(self, nodes): if nodes: if nodes[0] == '#': nodes.pop(0) return None root = TreeNode(nodes.pop(0)) root.left = self.deserialize_dfs(nodes) root.right = self.deserialize_dfs(nodes) return root return Nonedef deserialize(self, data: str): nodes = data.split(',') return self.deserialize_dfs(nodes) Java 代码: 12345678910111213141516171819public TreeNode deserialize_dfs(List<String> l) { if (l.get(0).equals(\"None\")) { l.remove(0); return null; } TreeNode root = new TreeNode(Integer.valueOf(l.get(0))); l.remove(0); root.left = deserialize_dfs(l); root.right = deserialize_dfs(l); return root;}public TreeNode deserialize(String data) { String[] data_array = data.split(\",\"); List<String> data_list = new LinkedList<String>(Arrays.asList(data_array)); return deserialize_dfs(data_list);} 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:空间复杂度取决于栈深度,因此空间复杂度为 $O(h)$,其中 $h$ 为树的深度。 BFS序列化实际上我们也可以使用 BFS 的方式来表示一棵树。在这一点上其实就和力扣的记法是一致的了。 我们知道层次遍历的时候实际上是有层次的。只不过有的题目需要你记录每一个节点的层次信息,有些则不需要。 这其实就是一个朴实无华的 BFS,唯一不同则是增加了空节点。 Python 代码: 1234567891011121314class Codec: def serialize(self, root): ans = '' queue = [root] while queue: node = queue.pop(0) if node: ans += str(node.val) + ',' queue.append(node.left) queue.append(node.right) else: ans += '#,' return ans[:-1] 反序列化如图有这样一棵树: 那么其层次遍历为 [1,2,3,#,#, 4, 5]。我们根据此层次遍历的结果来看下如何还原二叉树,如下是我画的一个示意图: 动画演示: 容易看出: level x 的节点一定指向 level x + 1 的节点,如何找到 level + 1 呢? 这很容易通过层次遍历来做到。 对于给的的 level x,从左到右依次对应 level x + 1 的节点,即第 1 个节点的左右子节点对应下一层的第 1 个和第 2 个节点,第 2 个节点的左右子节点对应下一层的第 3 个和第 4 个节点。。。 接上,其实如果你仔细观察的话,实际上 level x 和 level x + 1 的判断是无需特别判断的。我们可以把思路逆转过来:即第 1 个节点的左右子节点对应第 1 个和第 2 个节点,第 2 个节点的左右子节点对应第 3 个和第 4 个节点。。。(注意,没了下一层三个字) 因此我们的思路也是同样的 BFS,并依次连接左右节点。 Python 代码: {13-16}12345678910111213141516171819202122232425262728def deserialize(self, data: str): if data == '#': return None # 数据准备 nodes = data.split(',') if not nodes: return None # BFS root = TreeNode(nodes[0]) queue = collections.deque([root]) # 已经有 root 了,因此从 1 开始 i = 1 while i < len(nodes) - 1: node = queue.popleft() lv = nodes[i] rv = nodes[i + 1] i += 2 # 对于给的的 level x,从左到右依次对应 level x + 1 的节点 # node 是 level x 的节点,l 和 r 则是 level x + 1 的节点 if lv != '#': l = TreeNode(lv) node.left = l queue.append(l) if rv != '#': r = TreeNode(rv) node.right = r queue.append(r) return root 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:$O(N)$,其中 $N$ 为节点的总数。 这样就结束了吗?有了上面的序列化的知识。 我们就可以问面试官是哪种序列化的手段。 并针对性选择反序列化方案构造出二叉树。最后再使用本文开头的方法解决即可。 以为这里就结束了吗? 并没有!面试官让他说出自己的复杂度。 读到这里,不妨自己暂停一下,思考这个解法的复杂度是多少? 1 2 3 4 5 ok,我们来揭秘。 时间复杂度是 $O(n) + O(n^2)$,其中 $O(n)$ 是生成树的时间,$O(n^2)$ 是判断是否是平衡二叉树的时间。 为什么判断平衡二叉树的时间复杂度是 $O(n^2)$? 这是因为我们对每一个节点都计算其深度,因此总的时间为所有节点深度之和,最差情况是退化到链表的情况,此时的高度之和为 $1 + 2 + … n$ ,根据等差数列求和公式可知,时间复杂度是 $O(n^2)$。 空间复杂度很明显是 $O(n)$。这其中包括了构建二叉树的 n 以及递归栈的开销。 面试官又追问:可以优化么? 读到这里,不妨自己暂停一下,思考这个解法的复杂度是多少? 1 2 3 4 5 ok,我们来揭秘。 优化的手段有两种。第一种是: 空间换时间。将 getDepth 函数的返回值记录下来,确保多次执行 getDepth 且参数相同的情况不会重复执行。这样时间复杂度可以降低到 $O(n)$ 第二种方法和上面的方法是类似的,其本质是记忆化递归(和动态规划类似)。 我在上一篇文章 读者:西法,记忆化递归究竟怎么改成动态规划啊? 详细讲述了记忆化递归和动态规划的互相转换。如果你看了的话,会发现这里就是记忆化递归。 第一种方法代码比较简单,就不写了。这里给一下第二种方法的代码。 定义函数 getDepth(root) 返回 root 的深度。 需要注意的是,如果子节点不平衡,直接返回 -1。 这样上面的两个函数功能(getDepth 和 isBalance)就可以放到一个函数中执行了。 1234567891011121314class Solution: def isBalanced(self, root: TreeNode) -> bool: def getDepth(root: TreeNode) -> int: if not root: return 0 lh = getDepth(root.left) rh = getDepth(root.right) # lh == -1 表示左子树不平衡 # rh == -1 表示右子树不平衡 if lh == -1 or rh == -1 or abs(rh - lh) > 1: return -1 return max(lh, rh) + 1 return getDepth(root) != -1 总结虽然这道面试题目是一个常见的常规题。不过参数改了一下,瞬间难度就上来了。如果面试官没有直接给你说 nodes 是怎么序列化来的,他可能是故意的。二叉树序列的方法有很多啊?题目给的是哪种呢?这需要你和面试官沟通。很有可能面试官在等着你问他呢!!! 这正是这道题的难点所在。 构造二叉树本质就是一个二叉树反序列的过程。 而如何反序列化需要结合序列化算法。 序列化方法根据是否存储空节点可以分为:存储空节点和不存储空节点。 存储空节点会造成空间的浪费,不存储空节点会造成无法唯一确定一个包含重复值的树。 而关于序列化,本文主要讲述的是二叉树的序列化和反序列化。看完本文之后,你就可以放心大胆地去 AC 以下两道题: 449. 序列化和反序列化二叉搜索树(中等) 297. 二叉树的序列化与反序列化(困难) 另外仅仅是暴力做出来还不够,大家要对自己提出更高的要求。 最起码你要会分析自己的算法,常用的就是复杂度分析。进一步如果你可以对算法进行优化会很加分。比如这里我就通过两种优化方法将时间优化到了 $O(n)$。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"}]},{"title":"读者:西法,记忆化递归究竟怎么改成动态规划啊?","slug":"dp-bottom-up","date":"2021-05-17T16:00:00.000Z","updated":"2023-01-07T13:54:06.072Z","comments":true,"path":"2021/05/18/dp-bottom-up/","link":"","permalink":"https://lucifer.ren/blog/2021/05/18/dp-bottom-up/","excerpt":"我在动态规划专题反复强调了先学习递归,再学习记忆化,最后再学动态规划。 其中原因已经讲得很透了,相信大家已经明白了。如果不明白,强烈建议先看看那篇文章。 尽管很多看了我文章的小伙伴知道了先去学记忆化递归,但是还是有一些粉丝问我:“记忆化递归转化为动态规划老是出错,不得要领怎么办?有没有什么要领呀?” 今天我就来回答一下粉丝的这个问题。 实际上我的动态规划那篇文章已经讲了将记忆化递归转化为动态规划的大概的思路,只是可能不是特别细,今天我们就尝试细化一波。 我们仍然先以经典的爬楼梯为例,给大家讲一点基础知识。接下来,我会带大家解决一个更加复杂的题目。","text":"我在动态规划专题反复强调了先学习递归,再学习记忆化,最后再学动态规划。 其中原因已经讲得很透了,相信大家已经明白了。如果不明白,强烈建议先看看那篇文章。 尽管很多看了我文章的小伙伴知道了先去学记忆化递归,但是还是有一些粉丝问我:“记忆化递归转化为动态规划老是出错,不得要领怎么办?有没有什么要领呀?” 今天我就来回答一下粉丝的这个问题。 实际上我的动态规划那篇文章已经讲了将记忆化递归转化为动态规划的大概的思路,只是可能不是特别细,今天我们就尝试细化一波。 我们仍然先以经典的爬楼梯为例,给大家讲一点基础知识。接下来,我会带大家解决一个更加复杂的题目。 爬楼梯题目描述一个人爬楼梯,每次只能爬 1 个或 2 个台阶,假设有 n 个台阶,那么这个人有多少种不同的爬楼梯方法? 思路由于第 n 级台阶一定是从 n - 1 级台阶或者 n - 2 级台阶来的,因此到第 n 级台阶的数目就是 到第 n - 1 级台阶的数目加上到第 n - 1 级台阶的数目。 记忆化递归代码: 1234567891011const memo = {};function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; if (n in memo) return memo[n]; ans = climbStairs(n - 1) + climbStairs(n - 2); memo[n] = ans; return ans;}climbStairs(10); 首先为了方便看出关系,我们先将 memo 的名字改一下,将 memo 换成 dp: {1,5,7}1234567891011const dp = {};function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; if (n in dp) return dp[n]; ans = climbStairs(n - 1) + climbStairs(n - 2); dp[n] = ans; return ans;}climbStairs(10); 其他地方一点没动,就是名字改了下。 那么这个记忆化递归代码如何改造成动态规划呢?这里我总结了三个步骤,根据这三个步骤就可以将很多记忆化递归轻松地转化为动态规划。 1. 根据记忆化递归的入参建立 dp 数组在动态规划专题中,西法还提过动态规划的核心就是状态。动态规划问题时间复杂度打底就是状态数,空间复杂度如果不考虑滚动数组优化打底也是状态数,而状态数是什么?不就是各个状态的取值范围的笛卡尔积么?而状态正好对应的就是记忆化递归的入参。 对应这道题,显然状态是当前位于第几级台阶。那么状态数就有 n 个。因此开辟一个长度为 n 的一维数组就好了。 我用 from 表示改造前的记忆化递归代码, to 表示改造后的动态规划代码。(下同,不再赘述) from: 12dp = {};function climbStairs(n) {} to: 123function climbStairs(n) { const dp = new Array(n);} 2. 用记忆化递归的叶子节点返回值填充 dp 数组初始值如果你模拟上面 dp 函数的执行过程会发现: if n == 1 return 1 和 if n == 2 return 2,对应递归树的叶子节点,这两行代码深入到叶子节点才会执行。接下来再根据子 dp 函数的返回值合并结果,是一个典型的后序遍历。 如果改造成迭代,如何做呢?一个朴素的想法就是从叶子节点开始模拟递归栈返回的过程,没错动态规划本质就是如此。从叶子节点开始,到根节点结束,这也是为什么记忆化递归通常被称为自顶向下,而动态规划被称为自底向上的原因。这里的底和顶可以看做是递归树的叶子和根。 知道了记忆化递归和动态规划的本质区别。 接下来,我们填充初始化,填充的逻辑就是记忆化递归的叶子节点 return 部分。 from: 12345const dp = {};function climbStairs(n) { if (n == 1) return 1; if (n == 2) return 2;} to: 12345function climbStairs(n) { const dp = new Array(n); dp[0] = 1; dp[1] = 2;} dp 长度为 n,索引范围是 [0,n-1],因此 dp[n-1] 对应记忆化递归的 dp(n)。因此 dp[0] = 1 等价于上面的 if n == 1: return 1。 如果你想让二者完全对应也是可以的,数组长度开辟为 n + 1,并且数组索引 0 不用即可。 3. 枚举笛卡尔积,并复制主逻辑 if (xxx in dp) return dp[xxx] 这种代码删掉 将递归函数 f(xxx, yyy, …) 改成 dp[xxx][yyy][….] ,对应这道题就是 climbStairs(n) 改成 dp[n] 将递归改成迭代。比如这道题每次 climbStairs(n) 递归调用了 climbStairs(n-1) 和 climbStairs(n-2),一共调用 n 次,我们要做的就是迭代模拟。比如这里调用了 n 次,我们就用一层循环来模拟执行 n 次。如果有两个参数就两层循环,三个参数就三层循环,以此类推。 from: 12345678const dp = {};function climbStairs(n) { // ... if (n in dp) return dp[n]; ans = climbStairs(n - 1) + climbStairs(n - 2); dp[n] = ans; return ans;} to: 12345678function climbStairs(n) { // ... // 这个循环其实就是咱上面提到的状态的笛卡尔积。由于这道题就一个状态,枚举一层就好了。如果状态有两个,那么笛卡尔积就可以用两层循环搞定。至于谁在外层循环谁在内层循环,请看我的动态规划专题。 for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 将上面几个步骤的成果合并起来就可以将原有的记忆化递归改造为动态规划了。 完整代码: 1234567891011function climbStairs(n) { if (n == 1) return 1; const dp = new Array(n); dp[0] = 1; dp[1] = 2; for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 有的人可能觉得这道题太简单了。实际上确实有点简单了。 而且我也承认有的记忆化递归比较难以改写,什么情况记忆化递归比较好写,改成动态规划就比较麻烦我也在动态规划专题给大家讲过了,不清楚的同学翻翻。 据我所知,如果动态规划可以过,大多数记忆化递归都可以过。有一些极端情况记忆化递归过不了:那就是力扣测试用例偏多,并且数据量大的测试用例比较多。这是由于力扣的超时判断是多个测试用例的用时总和,而不是单独计算时间。 接下来,我再举一个稍微难一点的例子(这个例子就必须使用动态规划才能过,记忆化递归会超时)。带大家熟悉我上面给大家的套路。 1824. 最少侧跳次数题目描述12345678910111213给你一个长度为 n 的 3 跑道道路 ,它总共包含 n + 1 个 点 ,编号为 0 到 n 。一只青蛙从 0 号点第二条跑道 出发 ,它想要跳到点 n 处。然而道路上可能有一些障碍。给你一个长度为 n + 1 的数组 obstacles ,其中 obstacles[i] (取值范围从 0 到 3)表示在点 i 处的 obstacles[i] 跑道上有一个障碍。如果 obstacles[i] == 0 ,那么点 i 处没有障碍。任何一个点的三条跑道中 最多有一个 障碍。比方说,如果 obstacles[2] == 1 ,那么说明在点 2 处跑道 1 有障碍。这只青蛙从点 i 跳到点 i + 1 且跑道不变的前提是点 i + 1 的同一跑道上没有障碍。为了躲避障碍,这只青蛙也可以在 同一个 点处 侧跳 到 另外一条 跑道(这两条跑道可以不相邻),但前提是跳过去的跑道该点处没有障碍。比方说,这只青蛙可以从点 3 处的跑道 3 跳到点 3 处的跑道 1 。这只青蛙从点 0 处跑道 2 出发,并想到达点 n 处的 任一跑道 ,请你返回 最少侧跳次数 。注意:点 0 处和点 n 处的任一跑道都不会有障碍。示例 1: 12345输入:obstacles = [0,1,2,3,0]输出:2解释:最优方案如上图箭头所示。总共有 2 次侧跳(红色箭头)。注意,这只青蛙只有当侧跳时才可以跳过障碍(如上图点 2 处所示)。示例 2: 1234输入:obstacles = [0,1,1,3,3,0]输出:0解释:跑道 2 没有任何障碍,所以不需要任何侧跳。示例 3: 12345678910输入:obstacles = [0,2,1,0,3,0]输出:2解释:最优方案如上图所示。总共有 2 次侧跳。提示:obstacles.length == n + 11 <= n <= 5 \\* 1050 <= obstacles[i] <= 3obstacles[0] == obstacles[n] == 0 思路 这个青蛙在反复横跳?? 稍微解释一下这个题目。 如果当前跑道后面一个位置没有障碍物,这种情况左右横跳一定不会比直接平跳更优,我们应该贪心地直接平跳(不是横跳)过去。这是因为最坏情况我们可以先平跳过去再横跳,这和先横跳再平跳是一样的。 如果当前跑道后面一个位置有障碍物,我们需要横跳到一个没有障碍物的通道,同时横跳计数器 + 1。 最后选取所有到达终点的横跳次数最少的即可,对应递归树中就是到达叶子节点时计数器最小的。 使用 dp(pos, line) 表示当前在通道 line, 从 pos 跳到终点需要的最少的横跳数。不难写出如下记忆化递归代码。 由于本篇文章主要讲的是记忆化递归改造动态规划,因此这道题的细节就不多介绍了,大家看代码就好。 我们来看下代码: 123456789101112131415161718192021class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): if (pos, line) in dp: return dp[(pos, line)] if pos == len(obstacles) - 1: return 0 # 贪心地平跳 if obstacles[pos + 1] != line: ans = f(pos + 1, line) dp[(pos, line)] = ans return ans ans = float(\"inf\") for nxt in [1, 2, 3]: if nxt != line and obstacles[pos] != nxt: ans = min(ans, 1 +f(pos, nxt)) dp[(pos, line)] = ans return ans return f(0, 2) 这道题记忆化递归会超时,需要使用动态规划才行。 那么如何将 ta 改造成动态规划呢? 还是用上面的口诀。 1. 根据记忆化递归的入参建立 dp 数组上面递归函数的是 dp(pos, line),状态就是形参,因此需要建立一个 m * n 的二维数组,其中 m 和 n 分别是 pos 和 line 的取值范围集合的大小。而 line 取值范围其实就是 [1,3],为了方便索引对应,这次西法决定浪费一个空间。由于这道题是求最小,因此初始化为无穷大没毛病。 from: 1234567class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): # ... return f(0, 2) to: 12345class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] # ... return min(dp[-1]) 2. 用记忆化递归的叶子节点返回值填充 dp 数组初始值不多说了,直接上代码。 from: 123456789class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): if pos == len(obstacles) - 1: return 0 # ... return f(0, 2) to: 123456class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] # ... return min(dp[-1]) 3. 枚举笛卡尔积,并复制主逻辑这道题如何枚举状态?当然是枚举状态的笛卡尔积了。简单,几个状态就几层循环呗。 上代码。 12345678class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] for pos in range(1, len(obstacles)): for line in range(1, 4): # ... return min(dp[-1]) 接下来就是把记忆化递归的主逻辑复制一下粘贴过来就行。 from: 123456789101112131415161718class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = {} def f(pos, line): # ... # 贪心地平跳 if obstacles[pos + 1] != line: ans = f(pos + 1, line) dp[(pos, line)] = ans return ans ans = float(\"inf\") for nxt in [1, 2, 3]: if nxt != line and obstacles[pos] != nxt: ans = min(ans, 1 +f(pos, nxt)) dp[(pos, line)] = ans return ans return f(0, 2) to: 1234567891011121314class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] for pos in range(1, len(obstacles)): for line in range(1, 4): if obstacles[pos - 1] != line: # 由于自底向上,因此是和 pos - 1 建立联系,而不是 pos + 1 dp[pos][line] = min(dp[pos][line], dp[pos - 1][line]) else: for nxt in range(1, 4): if nxt != line and obstacles[pos] != nxt: dp[pos][line] = min(dp[pos][line], 1 + dp[pos][nxt]) return min(dp[-1]) 可以看出我基本就是把主逻辑复制过来,稍微改改。 改的基本就是因为: 之前是递归函数,因此 return 需要去掉,比如改成 continue 啥的,不能让函数直接返回,而是继续枚举下一个状态。 之前是 dp[(pos, line)] = ans 现在则改成填充咱上面初始好的二维 dp 数组。 你以为这就结束了么? 那你就错了。之所以选这道题是有原因的。这道题直接提交会报错,是答案错误(WA)。 这里我要告诉大家的是:由于我们使用迭代模拟递归过程,使用多层循环枚举状态的笛卡尔积,而主逻辑部分则是状态转移方程,而转移方程的书写和枚举的顺序息息相关。 从代码不难看出:对这道题来说我们采用的是从小到大枚举,而 dp[pos][line] 也仅仅依赖 dp[pos-1][line] 和 dp[pos][nxt]。 而问题的关键是 nxt,比如处理到了 dp[2][1],d[2][1] 依赖了 dp[2][3] 的值,而实际上 dp[2][3] 是没有处理到的。 因此上面动态规划的的这一行代码有问题: 1dp[pos][line] = min(dp[pos][line], 1 + dp[pos][nxt]) 因为遍历到 dp[pos][line] 的时候,有可能 dp[pos][nxt] 还没计算好(没有枚举到),这就是产生了 bug。 那为什么记忆化递归就没问题呢? 其实很简单。递归函数里面的子问题都是没有计算好的,到叶子节点后再开始计算,计算好后往上返回,而返回的过程其实和迭代是类似的。 比如这道题的 f(0,2) 的递归树大概是这样的,其中虚线标识可能无法到达。 当从 f(0, 2) 递归到 f(0, 1) 或者 f(0, 3) 的的时候,都是没计算好的,因此都无所谓,代码会继续往叶子节点方向扩展,到达叶子节点返回后,所有的子节点肯定都已经计算好了,接下来的过程和普通的迭代就很像了。 比如 f(0,2) 递归到 f(0,3) ,f(0,3) 会继续向下递归知道叶子节点,然后向上返回,当再次回到 f(0,2) 的时候,f(0,3) 一定是已经计算好的。 形象点来说就是:f(0,2) 是一个 leader,告诉他的下属 f(0,3),我想要 xxxx,怎么实现我不管,你有的话直接给我(记忆化),没有的话想办法获取(递归)。不管怎么样,反正你给我弄出来送到我手上。 而如果使用迭代的动态规划,你有的话直接给我(记忆化)很容易做到。关键是没有的话想办法获取(递归)不容易做到啊,至少需要一个类似的循环去完成吧? 那如何解决这个问题呢? 很简单,每次只依赖已经计算好的状态就好了。 对于这道题来说,虽然 dp[pos][nxt] 可能没计算好了,那么 dp[pos-1][nxt] 一定是计算好的,因为 dp[pos-1][nxt] 已经在上一次主循环计算好了。 但是直接改成 dp[pos-1][nxt] 逻辑还对么?这就要具体问题具体分析了,对于这道题来说,这么写是可以的。 这是因为这里的逻辑是如果当前赛道的前面一个位置有障碍物,那么我们不能从当前赛道的前一个位置过来,而只能选择从其他两个赛道横跳过来。 我画了一个简图。其中 X 表示障碍物,O 表示当前的位置,数字表示时间上的先后循序,先跳 1 再跳 2 。。。 123-XO------ 在这里,而以下两种情况其实是等价的: 情况 1(也就是上面 dp[pos][nxt] 的情况): 123-X2--1--- 情况 2(也就是上面 dp[pos-1][nxt] 的情况): 123-X3-12--- 可以看出二者是一样的。没懂?多看看,多想想。 综上,我们将 dp[pos][nxt] 改成 dp[pos-1][nxt] 不会有问题。大家遇到其他问题也采取类似思路分析一波即可。 完整代码: 1234567891011121314class Solution: def minSideJumps(self, obstacles: List[int]) -> int: dp = [[float(\"inf\")] * 4 for _ in range(len(obstacles))] dp[0] = [0, 1, 0, 1] for pos in range(1, len(obstacles)): for line in range(1, 4): if obstacles[pos - 1] != line: # 由于自底向上,因此是和 pos - 1 建立联系,而不是 pos + 1 dp[pos][line] = min(dp[pos][line], dp[pos - 1][line]) else: for nxt in range(1, 4): if nxt != line and obstacles[pos] != nxt: dp[pos][line] = min(dp[pos][line], 1 + dp[pos-1][nxt]) return min(dp[-1]) 趁热打铁再来一个再来一个例子,1866. 恰有 K 根木棍可以看到的排列数目。 思路直接上记忆化递归代码: 12345678class Solution: def rearrangeSticks(self, n: int, k: int) -> int: @lru_cache(None) def dp(i, j): if i == 0 and j != 0: return 0 if i == 0 and j == 0: return 1 return (dp(i - 1, j - 1) + dp(i - 1, j) * (i - 1)) % (10**9 + 7) return dp(n, k) % (10**9 + 7) 咱不管这个题是啥,代码怎么来的。假设这个代码咱已经写出来了。那么如何改造成动态规划呢?继续套用三部曲。 1. 根据记忆化递归的入参建立 dp 数组由于 i 的取值 [0-n] 一共 n + 1 个, j 的取值是 [0-k] 一共 k + 1 个。因此初始化一个二维数组即可。 1dp = [[0] * (k+1) for _ in range(n+1)] 2. 用记忆化递归的叶子节点返回值填充 dp 数组初始值由于 i == 0 and j == 0 是 1,因此直接写 dp[0][0] = 1 就好了。 {2}12dp = [[0] * (k+1) for _ in range(n+1)]dp[0][0] = 1 3. 枚举笛卡尔积,并复制主逻辑就是两层循环枚举 i 和 j 的所有组合就好了。 {4-6}1234567dp = [[0] * (k+1) for _ in range(n+1)]dp[0][0] = 1for i in range(1, n + 1): for j in range(1, min(k, i) + 1): # ...return dp[-1][-1] 最后把主逻辑复制过来完工了。 比如: return xxx 改成 dp[形参一][形参二] = xxx 等小细节。 最终的一个代码就是: {7-11}123456789101112class Solution: def rearrangeSticks(self, n: int, k: int) -> int: dp = [[0] * (k+1) for _ in range(n+1)] dp[0][0] = 1 for i in range(1, n + 1): for j in range(1, min(k, i) + 1): dp[i][j] = dp[i-1][j-1] if i - 1 >= j: dp[i][j] += dp[i-1][j] * (i - 1) dp[i][j] %= 10**9 + 7 return dp[-1][-1] 总结有的记忆化递归比较难以改写,什么情况记忆化递归比较好写,改成动态规划就比较麻烦我也在动态规划专题给大家讲过了,不清楚的同学翻翻。 我之所以推荐大家从记忆化递归入手,正是因为很多情况下记忆化写起来简单,而且容错高(想想上面的青蛙跳的例子)。这是因为记忆化递归总是后序遍历,会在到达叶子节点只会往上计算。而往上计算的过程和迭代的动态规划是类似的。或者你也可以认为迭代的动态规划是在模拟记忆化递归的归的过程。 我们要做的就是把一些容易改造的方法学会,接下来面对难的尽量用记忆化递归。据我所知,如果动态规划可以过,大多数记忆化递归都可以过。有一个极端情况记忆化递归过不了:那就是力扣测试用例偏多,并且数据量大的测试用例比较多。这是由于力扣的超时判断是多个测试用例的用时总和,而不是单独计算时间。 将记忆化递归改造成动态规划可以参考我的这三个步骤: 根据记忆化递归的入参建立 dp 数组 用记忆化递归的叶子节点返回值填充 dp 数组初始值 枚举笛卡尔积,并复制主逻辑 另外有一点需要注意的是:状态转移方程的确定和枚举的方向息息相关,虽然不同题目细节差异很大。 但是我们只要牢牢把握一个原则就行了,那就是:永远不要用没有计算好的状态,而是仅适用已经计算好的状态。","categories":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/categories/动态规划/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"为了提高 Github 的阅读体验,我做了一个 Github 阅读器","slug":"github-reader","date":"2021-05-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.864Z","comments":true,"path":"2021/05/16/github-reader/","link":"","permalink":"https://lucifer.ren/blog/2021/05/16/github-reader/","excerpt":"这是个什么东西?虽然自从 Github 嫁给微软之后,做了很多的工作。不管是功能上,还是视觉 UI 上。因此在 Github 上看文档也比以前舒服多了。 可我仍然不是很喜欢这样的界面,我想让界面更加好看一点。 于是我就做了这么一个工具。 只需要输入 Github 地址,点击阅读就 OK 了。是不是很简单?","text":"这是个什么东西?虽然自从 Github 嫁给微软之后,做了很多的工作。不管是功能上,还是视觉 UI 上。因此在 Github 上看文档也比以前舒服多了。 可我仍然不是很喜欢这样的界面,我想让界面更加好看一点。 于是我就做了这么一个工具。 只需要输入 Github 地址,点击阅读就 OK 了。是不是很简单? 上面的 mardown 页面转换后的效果: 如何体验?体验地址:https://leetcode-solution.cn/github 界面非常简单。简单来说就是:输入一个 github 的 md 地址,点击阅读就行了。 另外你如果有一个 md 源码,想在线转化也是可以的。比如我用 md 写了下面一段话: 123456789101112131415161718192021## 思路这个是我的思路。上一个图片吧。![蓝色表示叶子节点](https://p.ipic.vip/z0syhs.jpg)## 代码```pydef f(): pass```**复杂度分析**令 N 为数组长度。- 时间复杂度:$O(N+max(0, K-N)^2)$- 空间复杂度:$O(max(1, K - N))$ 你可以将其复制粘贴到我这里的多行文本框,点击前往阅读即可。 如果你愿意的话,也可以将渲染结果复制粘贴到其他地方,比如一些云笔记平台。 值得注意的是,现在只支持 markdown,如果你输入的地址不是 markdown 是不可以的哦。 之后计划支持更多网站,不仅仅是 Github。 现在很多人都把周刊或者一些面试资料以 markdown 的形式放到 github 上, 如果你也经常看在 Github 上看 markdown 不妨尝试一下吧~ 推荐几个资源最后推荐几个 Github 上的阅读资源给大家: https://github.com/ruanyf/weekly https://github.com/sorrycc/weekly https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/get-started/ch1.md https://github.com/azl397985856/fe-interview/blob/master/docs/topics/network/tcp.md 有没有一个很突兀?","categories":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/categories/Github/"}],"tags":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/tags/Github/"}]},{"title":"努力上岸的元气少女","slug":"91-student-1","date":"2021-05-09T16:00:00.000Z","updated":"2023-01-05T12:24:50.104Z","comments":true,"path":"2021/05/10/91-student-1/","link":"","permalink":"https://lucifer.ren/blog/2021/05/10/91-student-1/","excerpt":"时间:2021-05-07 被采访人:易潇 活动介绍力扣加加,一个努力做西湖区最好的算法题解的团队。91 天学算法是力扣加加举办的一个算法训练活动,通过良好的学习计划 和 91 天的集中学习,帮助大家摆脱困境,征服算法。 现在 《91 天学算法》已经举办了四期了,第四期的活动时间是 2021-05-10 到 2021-08-08。 每一期都有很多优秀学员,我想将这些人的学习经验分享给大家,帮助大家少走弯路。由于我开辟了这个 91 天学算法优秀学员专访 活动。 这次,我们邀请到了远在美国的转码少女 - 易潇,一个元气满满的活力女孩。来看看她是如何学习的吧! ​","text":"时间:2021-05-07 被采访人:易潇 活动介绍力扣加加,一个努力做西湖区最好的算法题解的团队。91 天学算法是力扣加加举办的一个算法训练活动,通过良好的学习计划 和 91 天的集中学习,帮助大家摆脱困境,征服算法。 现在 《91 天学算法》已经举办了四期了,第四期的活动时间是 2021-05-10 到 2021-08-08。 每一期都有很多优秀学员,我想将这些人的学习经验分享给大家,帮助大家少走弯路。由于我开辟了这个 91 天学算法优秀学员专访 活动。 这次,我们邀请到了远在美国的转码少女 - 易潇,一个元气满满的活力女孩。来看看她是如何学习的吧! ​ 采访Q: 你是什么时候开始接触数据结构与算法(以下简称算法)的? A: 从很久之前就听说过数据结果与算法,但正式接触是打算转码的时候开始的。 Q: 你是什么时候接触 91 天学算法(以下简称 91 天)的?从什么途径得知的? A: 我是从第二期就开始接触 91 的,大概是关注公众号的时候关注到的。 Q: 91 天有给你带来了什么样的变化么? A: 91 让我从觉得用哈希表 O(N)写 two sum 很惊奇到了现在可以迷迷糊糊的状态下手写完堆(狗头 最近因为刷了 91,在做几个公司笔试初试(online coding challenge)的时候感觉很简单,也对自己的即将到来的面试更加有自信了~ Q: 学习算法过程中有“顿悟”的时刻么? A: 经常顿悟 — 顿悟源自于重复和每次重复带来的更深层次的理解。 Q: 你比较擅长的算法是什么?可以给大家简单分享一下么? A: 比较擅长的算法是看小漾怎么写的,然后默默的取长补短(就是都抄一遍的意思) Q: 有没有什么想和刚入坑算法的同学分享的? A: 我觉得刚入坑的同学们一定要学会处理自己的 “迷茫” 以及这种情绪带来的 “我不想做,这太难了”。我觉得在学习的过程中,只要能在自己感到迷茫的时候坚持再坚持,总会有那么一个 “顿悟”的时刻,然后就会觉得自己的坚持是值得的。我在正式学习编程之前的两三年里,也经常想过转码,但每次都会因为 “选什么语言” 或者 “初始化环境设定” 太难了等这种原因放弃。现在其实回头看,这些问题其实都很简单。 Q: 相对而言,你觉得 91 天哪里做的还不够好?应该如何改进? A:我希望 91 有更多的个人答题跟踪和一起打卡的人的榜单,这样大家可以相互鼓(shi)励(ya) Q: 愿意把 91 天分享给你的朋友么? A:当然愿意啦!我觉得新手入门的时候最难的就是选择 “学什么” , 而 91 恰好解决了这个问题。加入了 91 之后,只要根据 91 已经制定好的计划每天努力就可以了。而且问题是循序渐进的,对新手非常友好~ 再加上有这么多同学和超级棒的讲师们一起,我觉得 91 是最棒的刷题计划啦! lucifer 总结学习东西是需要循序渐进的。在这个循序渐进的路上会一次次地“顿悟”。如果你有一个良好的学习习惯和学习计划,那么我相信你效率一定很高,那么达到你理想的高度也只是时间的问题罢了。而如果你缺乏这样的学习习惯,不知道怎么去学习算法,可以试试 91 天学算法哦,活动介绍在文末的链接中,也可以直接在公众号力扣加加中回复 91 获取。 预告这几天西法倒腾了一个小功能 - Code Highlight lines,就是给一段代码中的某几行代码加高亮。 之所以搞这个小功能,是因为我最近在给大家贴代码的时候发现大多数代码都没啥意思,核心代码其实就几行。于是想要是能给某几行代码增加高亮就好了。可是找了半天也没发现什么特别好的解决方案,于是西法就自己实现了一下下(包括解析功能和 UI 美化)。 使用方法: 123456789101112131415```py {5-6,11-12}class Solution: def addToArrayForm(self, A: List[int], K: int) -> List[int]: carry = 0 for i in range(len(A) - 1, -1, -1): A[i], carry = (carry + A[i] + K % 10) % 10, (carry + A[i] + K % 10) // 10 K //= 10 B = [] # 如果全部加完还有进位,需要特殊处理。 比如 A = [2], K = 998 carry = carry + K while carry: B = [(carry) % 10] + B carry //= 10 return B + A``` 只需要在语言后面增加你想高亮的行即可。如上代码就是想要高亮第 5-6 行,以及第 11-12 行。 使用效果: 当然也可以直接高亮具体某一行,比如 py {3} 就是高亮第三行。 大家有什么想要的 feature, 也可以给我提,我会尽量满足大家。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第四期)","slug":"91algo-4","date":"2021-05-01T16:00:00.000Z","updated":"2023-01-07T13:56:10.278Z","comments":true,"path":"2021/05/02/91algo-4/","link":"","permalink":"https://lucifer.ren/blog/2021/05/02/91algo-4/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 最近我认识了一个新朋友 - 鸽子君,给大家介绍一下。 鸽子君呢?不见了~ 好吧,先不管它了。继续我们的正题~ 前几天我在微信群发了一个投票,让大家投下面两个选项: 现在开始,等不及了。 先不急,内部测试一下,等六一再上。 后来收集到的情况还是选择 1 的比较多。那我就差不多准备开始吧。为了稍微照顾一下选择 2 的同学,我将开始时间适当延后一点点,我们 05.10 开始。开始之前大家可以先熟悉节奏以及学习先导篇。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 最近我认识了一个新朋友 - 鸽子君,给大家介绍一下。 鸽子君呢?不见了~ 好吧,先不管它了。继续我们的正题~ 前几天我在微信群发了一个投票,让大家投下面两个选项: 现在开始,等不及了。 先不急,内部测试一下,等六一再上。 后来收集到的情况还是选择 1 的比较多。那我就差不多准备开始吧。为了稍微照顾一下选择 2 的同学,我将开始时间适当延后一点点,我们 05.10 开始。开始之前大家可以先熟悉节奏以及学习先导篇。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。91 天见证不一样的自己。 活动时间2021-05-10 至 2021-08-08 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少打卡成功一次,当天的题目必须当天打卡才算打卡成功,不是当天打卡算做补卡。 违反上述条件的人员会被强制清退 内容本期部分内容独立于 Github 仓库,直接在我们的官网上进行,体验更棒哦~ 自习篇 活动开始前大家预习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇 数组,队列,栈 链表 树与递归 哈希表 双指针 图(加餐) 专题篇 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 进阶篇 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 往期公开讲义 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划 【91 算法-专题篇】二分法 四期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定私有仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 第二天会对前一天的题目进行讲解。 本期有共五位讲师,每个专题由一位具体的讲师负责,大家有不会的问题可以进行提问。如果讲师来不及回答,大家可以在仓库中提 issue。 关于每一个专题的负责讲义,我们会在 91 官网中的“讲师”模块给出,如上图所示。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书,科学上网兑换码等 连续打卡七天可以获得补签卡一张哦 如何报名采用微信群 + 官网 + Github 的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 扫码进群(如果提示不能进入,说明已经超过 100 名了,需要找 lucifer 手动拉)。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元 101 - 500 收费 30 元 直接添加 lucifer 好友(微信号 DevelopeEngineer)发红包或者转账即可 当你满足以下三个条件: 前 50 名 分享海报满 3 天 已经付费 则可联系 lucifer,并告知你的 Github 登录名即可。 熟悉 91 的小伙伴可能发现相较于前三期涨价了。 其实涨价的目的是提供更好的服务,包括但不限于发放奖品,完善讲义,购买服务器(后期考虑),还望大家理解。如果你经济实在困难可以参加下面的返现活动哦。 分享返现如果你没有抢到前 50 名免费的学习机会也不要气馁。我们贴心地为大家搞了分享返现活动,手慢照样可以免费参加哦~ 活动规则: 发送宣传海报到你的朋友圈不屏蔽好友保留三天,三天之后加 lucifer 微信好友(微信号:DevelopeEngineer)进行验证,验证通过全额返现。 不到三天就没必要联系我验证了,必须不屏蔽好友满三天才可以验证。 朋友圈文案统一为: 91 天,遇见更好的自己。发送本海报到朋友圈,不屏蔽好友保留三天即可免费学习(文案需保留)。快扫描下方二维码报名吧! 海报: 朋友圈分享海报示例: FAQ Q: 为什么提示“很抱歉,当前页面部分内容需要付费且登录后才能访问~”? A: 可能是因为你没有付款。如果您确认已经付款或者拥有免费资格,请联系 lucifer 确认。 Q:第四期和前三期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。第三题主要完善了二分,位运算和动态规划。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到仓库,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在仓库中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和仓库,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在仓库和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/ Q:仓库在哪里?怎么进? A:仓库在 91 天学算法官网中的打卡模块中可以看到,官网地址:https://leetcode-solution.cn/91。如果你提示 404, 请耐心等待,我会在活动开始前给大家加。如果活动已经开始,请联系 lucifer 处理。 官方网站从这一期起,我们开始制作自己的官方网站:https://leetcode-solution.cn/91。 在这里大家可以做除了打卡(以及看别人打卡)之外的所有事,包括看目录,具体的讲义,日常安排,每天的题目以及官方题解等。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"他来了,他带着「 西法的刷题秘籍 2.0 」 走来了!","slug":"ebook-2.0","date":"2021-04-27T16:00:00.000Z","updated":"2023-01-05T12:24:49.629Z","comments":true,"path":"2021/04/28/ebook-2.0/","link":"","permalink":"https://lucifer.ren/blog/2021/04/28/ebook-2.0/","excerpt":"嫌文章长的,可以直接拉到底部,底部有 pdf 的获取方式,以下是正文。","text":"嫌文章长的,可以直接拉到底部,底部有 pdf 的获取方式,以下是正文。 之前我写的一些专题很大程度上是给自己复习用的。给自己复习用的内容肯定就比较随意一点,除了我能看懂,其他人很难完全 get 到我的点。 后来很多同学对我的仓库表示了认可,不断给我提 issue,pr 代码等等。 我就在想:这种给自己看的复习材料就能收到这么高的评价,那我从给别人讲懂的角度认真写岂不是更牛?于是我决定将自己仓库的专题装修一遍。把自己能看懂,变成人人都看得懂。 我目前已经完成了大概 20 多个专题,如果全部重新写一遍,这个工程肯定是很大的,西法前前后后肝了好几个月才写完。不过慢慢来,先把重要的重新写一遍再说。 目前我已经重写了 7 个专题,分别是: 数据结构与算法总览 数据结构有哪些?各自有什么特点?平时我们是如何使用它们的? 树 树的核心就是遍历,遍历无非就是 BFS 和 DFS。 链表 链表搞清楚指针就够了。 二分 二分就是让未知世界无机可乘的算法。最左最右二分解决所有二分问题。 堆 动态求极值就用堆。 动态规划 所谓动态就是阶段之间可以转移,所谓规划就是考虑如何转移才能利益最大化。动态规划题型有哪些?不同题型都有什么套路? 二叉树遍历 二叉树的各种遍历如何思维统一地解决?递归迭代思维可以统一么?可以! 每个专题不仅讲是什么,还讲原理,讲套路,讲模板,讲常见题型和技巧,并且结合具体的题目让你看我是如何运用这些技巧的,每一个专题都是精品。 这已经可以覆盖相当多的题型了!后面我会陆续更新其他专题,努力做西湖区最好的算法题解。 仅仅这七个专题就已经 10w + 字了。可见我写的还是蛮详细和认真的。 已经有不少小伙伴靠这份《刷题秘籍》成功拿到了 BAT offer。不管是应届生,还是工作几年想跳槽的人,这份刷题笔记都很值得看一波。 我们废话不多说,直接告诉大家怎么获取。 我推荐大家直接使用在线版 + latex Chrome 插件在线观看,不仅阅读体验好,而且可以享受自动更新的服务。在线地址:https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/ latex 插件我用的是 tex-all-the-things。 地址:https://chrome.google.com/webstore/detail/tex-all-the-things/cbimabofgmfdkicghcadidpemeenbffn 如果你喜欢电子书,也没关系。我还贴心地把这份《刷题秘籍》打包成了 PDF,方便大家阅读与学习。现在这本电子书限时免费下载! 公众号《力扣加加》回复电子书就可以获取了。注意:后期随时可能收费哦~ 另外西法我最近受邀参加《前端早早聊》算法专场,给大家分享我的刷题经验。如果你和我一样,也是一枚前端,并且对算法面试比较畏惧,那么一定要关注我,我的前端算法面试系列文章绝对是市面上绝无仅有的材料,不仅内容真实性高,并且内容质量高,一定让你有所收获。更多内容,关注公众号《力扣加加》,让算法飞起来。","categories":[{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/categories/书/"},{"name":"算法","slug":"书/算法","permalink":"https://lucifer.ren/blog/categories/书/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/tags/书/"},{"name":"草稿","slug":"草稿","permalink":"https://lucifer.ren/blog/tags/草稿/"}]},{"title":"动态规划到底有多难?","slug":"dp","date":"2021-04-19T16:00:00.000Z","updated":"2023-01-07T13:54:06.084Z","comments":true,"path":"2021/04/20/dp/","link":"","permalink":"https://lucifer.ren/blog/2021/04/20/dp/","excerpt":"动态规划是一个从其他行业借鉴过来的词语。 它的大概意思先将一件事情分成若干阶段,然后通过阶段之间的转移达到目标。由于转移的方向通常是多个,因此这个时候就需要决策选择具体哪一个转移方向。 ​","text":"动态规划是一个从其他行业借鉴过来的词语。 它的大概意思先将一件事情分成若干阶段,然后通过阶段之间的转移达到目标。由于转移的方向通常是多个,因此这个时候就需要决策选择具体哪一个转移方向。 ​ 动态规划所要解决的事情通常是完成一个具体的目标,而这个目标往往是最优解。并且: 阶段之间可以进行转移,这叫做动态。 达到一个可行解(目标阶段) 需要不断地转移,那如何转移才能达到最优解?这叫规划。 每个阶段抽象为状态(用圆圈来表示),状态之间可能会发生转化(用箭头表示)。可以画出类似如下的图: 那我们应该做出如何的决策序列才能使得结果最优?换句话说就是每一个状态应该如何选择到下一个具体状态,并最终到达目标状态。这就是动态规划研究的问题。 每次决策实际上不会考虑之后的决策,而只会考虑之前的状态。 形象点来说,其实是走一步看一步这种短视思维。为什么这种短视可以来求解最优解呢?那是因为: 我们将所有可能的转移全部模拟了一遍,最后挑了一个最优解。 无后向性(这个我们后面再说,先卖个关子) 而如果你没有模拟所有可能,而直接走了一条最优解,那就是贪心算法了。 没错,动态规划刚开始就是来求最优解的。只不过有的时候顺便可以求总的方案数等其他东西,这其实是动态规划的副产物。 好了,我们把动态规划拆成两部分分别进行解释,或许你大概知道了动态规划是一个什么样的东西。但是这对你做题并没有帮助。那算法上的动态规划究竟是个啥呢? 在算法上,动态规划和查表的递归(也称记忆化递归) 有很多相似的地方。我建议大家先从记忆化递归开始学习。本文也先从记忆化递归开始,逐步讲解到动态规划。 记忆化递归那么什么是递归?什么是查表(记忆化)?让我们慢慢来看。 什么是递归?递归是指在函数中调用函数自身的方法。 有意义的递归通常会把问题分解成规模缩小的同类子问题,当子问题缩写到寻常的时候,我们可以直接知道它的解。然后通过建立递归函数之间的联系(转移)即可解决原问题。 是不是和分治有点像? 分治指的是将问题一分为多,然后将多个解合并为一。而这里并不是这个意思。 一个问题要使用递归来解决必须有递归终止条件(算法的有穷性),也就是说递归会逐步缩小规模到寻常。 虽然以下代码也是递归,但由于其无法结束,因此不是一个有效的算法: 12def f(x): return x + f(x - 1) 上面的代码除非外界干预,否则会永远执行下去,不会停止。 因此更多的情况应该是: 123def f(n): if n == 1: return 1 return n + f(n - 1) 使用递归通常可以使代码短小,有时候也更可读。算法中使用递归可以很简单地完成一些用循环不太容易实现的功能,比如二叉树的左中右序遍历。 递归在算法中有非常广泛的使用,包括现在日趋流行的函数式编程。 递归在函数式编程中地位很高。 纯粹的函数式编程中没有循环,只有递归。 实际上,除了在编码上通过函数调用自身实现递归。我们也可以定义递归的数据结构。比如大家所熟知的树,链表等都是递归的数据结构。 1234Node { value: any; // 当前节点的值 children: Array<Node>; // 指向其儿子} 如上代码就是一个多叉树的定义形式,可以看出 children 就是 Node 的集合类,这就是一种递归的数据结构。 不仅仅是普通的递归函数本文中所提到的记忆化递归中的递归函数实际上指的是特殊的递归函数,即在普通的递归函数上满足以下几个条件: 递归函数不依赖外部变量 递归函数不改变外部变量 满足这两个条件有什么用呢?这是因为我们需要函数给定参数,其返回值也是确定的。这样我们才能记忆化。关于记忆化,我们后面再讲。 如果大家了解函数式编程,实际上这里的递归其实严格来说是函数式编程中的函数。如果不了解也没关系,这里的递归函数其实就是数学中的函数。 我们来回顾一下数学中的函数: 1在一个变化过程中,假设有两个变量 x、y,如果对于任意一个 x 都有唯一确定的一个 y 和它对应,那么就称 x 是自变量,y 是 x 的函数。x 的取值范围叫做这个函数的定义域,相应 y 的取值范围叫做函数的值域 。 而本文所讲的所有递归都是指的这种数学中的函数。 比如上面的递归函数: 123def f(x): if x == 1: return 1 return x + f(x - 1) x 就是自变量,x 的所有可能的返回值构成的集合就是定义域。 f(x) 就是函数。 f(x) 的所有可能的返回值构成的集合就是值域。 自变量也可以有多个,对应递归函数的参数可以有多个,比如 f(x1, x2, x3)。 通过函数来描述问题,并通过函数的调用关系来描述问题间的关系就是记忆化递归的核心内容。 每一个动态规划问题,实际上都可以抽象为一个数学上的函数。这个函数的自变量集合就是题目的所有取值,值域就是题目要求的答案的所有可能。我们的目标其实就是填充这个函数的内容,使得给定自变量 x,能够唯一映射到一个值 y。(当然自变量可能有多个,对应递归函数参数可能有多个) 解决动态规划问题可以看成是填充函数这个黑盒,使得定义域中的数并正确地映射到值域。 递归并不是算法,它是和迭代对应的一种编程方法。只不过,我们通常借助递归去分解问题而已。比如我们定义一个递归函数 f(n),用 f(n) 来描述问题。就和使用普通动态规划 f[n] 描述问题是一样的,这里的 f 是 dp 数组。 什么是记忆化?为了大家能够更好地对本节内容进行理解,我们通过一个例子来切入: 一个人爬楼梯,每次只能爬 1 个或 2 个台阶,假设有 n 个台阶,那么这个人有多少种不同的爬楼梯方法? 思路: 由于第 n 级台阶一定是从 n - 1 级台阶或者 n - 2 级台阶来的,因此到第 n 级台阶的数目就是 到第 n - 1 级台阶的数目加上到第 n - 2 级台阶的数目。 递归代码: 12345function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; return climbStairs(n - 1) + climbStairs(n - 2);} 我们用一个递归树来直观感受以下(每一个圆圈表示一个子问题): 红色表示重复的计算。即 Fib(N-2) 和 Fib(N-3) 都被计算了两次,实际上计算一次就够了。比如第一次计算出了 Fib(N-2) 的值,那么下次再次需要计算 Fib(N-2)的时候,可以直接将上次计算的结果返回。之所以可以这么做的原因正是前文提到的我们的递归函数是数学中的函数,也就是说参数一定,那么返回值也一定不会变,因此下次如果碰到相同的参数,我们就可以将上次计算过的值直接返回,而不必重新计算。这样节省的时间就等价于重叠子问题的个数。 以这道题来说,本来需要计算 $2^n$ 次,而如果使用了记忆化,只需要计算 n 次,就是这么神奇。 代码上,我们可以使用一个 hashtable 去缓存中间计算结果,从而省去不必要的计算。 我们使用记忆化来改造上面的代码: 123456789memo = {}def climbStairs(n): if n == 1:return 1 if n == 2: return 2 if n in memo: return memo[n] ans = climbStairs(n - 1) + climbStairs(n-2) memo[n] = ans return ansclimbStairs(10) 这里我使用了一个名为 memo 的哈希表来存储递归函数的返回值,其中 key 为参数,value 为递归函数的返回值。 key 的形式为 (x, y),表示的是一个元祖。通常动态规划的参数有多个,我们就可以使用元祖的方式来记忆化。或者也可采取多维数组的形式。对于上图来说,就可使用二维数组来表示。 大家可以通过删除和添加代码中的 memo 来感受一下记忆化的作用。 小结使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。这里我列举了几道算法题目,这几道算法题目都可以用递归轻松写出来: 递归实现 sum 二叉树的遍历 走楼梯问题 汉诺塔问题 杨辉三角 递归中如果存在重复计算(我们称重叠子问题,下文会讲到),那就是使用记忆化递归(或动态规划)解题的强有力信号之一。可以看出动态规划的核心就是使用记忆化的手段消除重复子问题的计算,如果这种重复子问题的规模是指数或者更高规模,那么记忆化递归(或动态规划)带来的收益会非常大。 为了消除这种重复计算,我们可使用查表的方式。即一边递归一边使用“记录表”(比如哈希表或者数组)记录我们已经计算过的情况,当下次再次碰到的时候,如果之前已经计算了,那么直接返回即可,这样就避免了重复计算。下文要讲的动态规划中 DP 数组其实和这里“记录表”的作用是一样的。 如果你刚开始接触递归, 建议大家先去练习一下递归再往后看。一个简单练习递归的方式是将你写的迭代全部改成递归形式。比如你写了一个程序,功能是“将一个字符串逆序输出”,那么使用迭代将其写出来会非常容易,那么你是否可以使用递归写出来呢?通过这样的练习,可以让你逐步适应使用递归来写程序。 当你已经适应了递归的时候,那就让我们继续学习动态规划吧! 动态规划讲了这么多递归和记忆化,终于到了我们的主角登场了。 动态规划的基本概念我们先来学习动态规划最重要的两个概念:最优子结构和无后效性。 其中: 无后效性决定了是否可使用动态规划来解决。 最优子结构决定了具体如何解决。 最优子结构动态规划常常适用于有重叠子问题和最优子结构性质的问题。前面讲了重叠子问题,那么最优子结构是什么?这是我从维基百科找的定义: 1如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。 举个例子:如果考试中的分数定义为 f,那么这个问题就可以被分解为语文,数学,英语等子问题。显然子问题最优的时候,总分这个大的问题的解也是最优的。 再比如 01 背包问题:定义 f(weights, values, capicity)。如果我们想要求 f([1,2,3], [2,2,4], 10) 的最优解。我们可以将其划分为如下子问题: 将第三件物品装进背包,也就是 f([1,2], [2,2], 10) 和不将第三件物品装进背包,也就是 f([1,2,3], [2,2,4], 9) 显然这两个问题还是复杂,我们需要进一步拆解。不过,这里不是讲如何拆解的。 原问题 f([1,2,3], [2,2,4], 10) 等于以上两个子问题的最大值。只有两个子问题都是最优的时候整体才是最优的,这是因为子问题之间不会相互影响。 无后效性即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。 继续以上面两个例子来说。 数学考得高不能影响英语(现实其实可能影响,比如时间一定,投入英语多,其他科目就少了)。 背包问题中 f([1,2,3], [2,2,4], 10) 选择是否拿第三件物品,不应该影响是否拿前面的物品。比如题目规定了拿了第三件物品之后,第二件物品的价值就会变低或变高)。这种情况就不满足无后向性。 动态规划三要素状态定义动态规划的中心点是什么?如果让我说的话,那就是定义状态。 动态规划解题的第一步就是定义状态。定义好了状态,就可以画出递归树,聚焦最优子结构写转移方程就好了,因此我才说状态定义是动态规划的核心,动态规划问题的状态确实不容易看出。 但是一旦你能把状态定义好了,那就可以顺藤摸瓜画出递归树,画出递归树之后就聚焦最优子结构就行了。但是能够画出递归树的前提是:对问题进行划分,专业点来说就是定义状态。那怎么才能定义出状态呢? 好在状态的定义都有特点的套路。 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 也就是说状态的定义通常有不同的套路,大家可以在做题的过程中进行学习和总结。但是这种套路非常多,那怎么搞定呢? 说实话,只能多练习,在练习的过程中总结套路。具体的套路参考后面的动态规划的题型 部分内容。之后大家就可以针对不同的题型,去思考大概的状态定义方向。 两个例子 关于状态定义,真的非常重要,以至于我将其列为动态规划的核心。因此我觉得有必要举几个例子来进行说明。我直接从力扣的动态规划专题中抽取前两道给大家讲讲。 第一道题:《5. 最长回文子串》难度中等 123456789101112131415161718192021222324252627给你一个字符串 s,找到 s 中最长的回文子串。 示例 1:输入:s = "babad"输出:"bab"解释:"aba" 同样是符合题意的答案。示例 2:输入:s = "cbbd"输出:"bb"示例 3:输入:s = "a"输出:"a"示例 4:输入:s = "ac"输出:"a" 提示:1 <= s.length <= 1000s 仅由数字和英文字母(大写和/或小写)组成 这道题入参是一个字符串,那我们要将其转化为规模更小的子问题,那无疑就是字符串变得更短的问题,临界条件也应该是空字符串或者一个字符这样。 因此: 一种定义状态的方式就是 f(s1),含义是字符串 s1 的最长回文子串,其中 s1 就是题目中的字符串 s 的子串,那么答案就是 f(s)。 由于规模更小指的是字符串变得更短,而描述字符串我们也可以用两个变量来描述,这样实际上还省去了开辟字符串的开销。两个变量可以是起点索引 + 子串长度,也可以是终点索引 + 子串长度,也可以是起点坐标 + 终点坐标。随你喜欢,这里我就用起点坐标 + 终点坐标。那么状态定义就是 f(start, end),含义是子串 s[start:end+1]的最长回文子串,那么答案就是 f(0, len(s) - 1) s[start:end+1] 指的是包含 s[start],而不包含 s[end+1] 的连续子串。 这无疑是一种定义状态的方式,但是一旦我们这样去定义就会发现:状态转移方程会变得难以确定(实际上很多动态规划都有这个问题,比如最长上升子序列问题)。那究竟如何定义状态呢?我会在稍后的状态转移方程继续完成这道题。我们先来看下一道题。 第二道题:《10. 正则表达式匹配》难度困难 12345678910111213141516171819202122232425262728293031323334353637383940给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。'.' 匹配任意单个字符'*' 匹配零个或多个前面的那一个元素所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。 示例 1:输入:s = "aa" p = "a"输出:false解释:"a" 无法匹配 "aa" 整个字符串。示例 2:输入:s = "aa" p = "a*"输出:true解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。示例 3:输入:s = "ab" p = ".*"输出:true解释:".*" 表示可匹配零个或多个('*')任意字符('.')。示例 4:输入:s = "aab" p = "c*a*b"输出:true解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。示例 5:输入:s = "mississippi" p = "mis*is*p*."输出:false 提示:0 <= s.length <= 200 <= p.length <= 30s 可能为空,且只包含从 a-z 的小写字母。p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。保证每次出现字符 * 时,前面都匹配到有效的字符 这道题入参有两个, 一个是 s,一个是 p。沿用上面的思路,我们有两种定义状态的方式。 一种定义状态的方式就是 f(s1, p1),含义是 p1 是否可匹配字符串 s1,其中 s1 就是题目中的字符串 s 的子串,p1 就是题目中的字符串 p 的子串,那么答案就是 f(s, p)。 另一种是 f(s_start, s_end, p_start, p_end),含义是子串 p1[p_start:p_end+1] 是否可以匹配字符串 s[s_start:s_end+1],那么答案就是 f(0, len(s) - 1, 0, len(p) - 1) 而这道题实际上我们也可采用更简单的状态定义方式,不过基本思路都是差不多的。我仍旧卖个关子,后面讲转移方程再揭晓。 搞定了状态定义,你会发现时间空间复杂度都变得很明显了。这也是为啥我反复强调状态定义是动态规划的核心。 时间空间复杂度怎么个明显法了呢? 首先空间复杂度,我刚才说了动态规划其实就是查表的暴力法,因此动态规划的空间复杂度打底就是表的大小。再直白一点就是上面的哈希表 memo 的大小。而 memo的大小基本就是状态的个数。状态个数是多少呢? 这不就取决你状态怎么定义了么?比如上面的 f(s1, p1) 。状态的多少是多少呢?很明显就是每个参数的取值范围大小的笛卡尔积。s1 的所有可能取值有 len(s) 种,p1 的所有可能有 len(p)种,那么总的状态大小就是 len(s) * len(p)。那空间复杂度是 $O(m * n)$,其中 m 和 n 分别为 s 和 p 的大小。 我说空间复杂度打底是状态个数, 这里暂时先不考虑状态压缩的情况。 其次是时间复杂度。时间复杂度就比较难说了。但是由于我们无论如何都要枚举所有状态,因此时间复杂度打底就是状态总数。以上面的状态定义方式,时间复杂度打底就是$O(m * n)$。 如果你枚举每一个状态都需要和 s 的每一个字符计算一下,那时间复杂度就是 $O(m^2 * n)$。 以上面的爬楼梯的例子来说,我们定义状态 f(n) 表示到达第 n 级台阶的方法数,那么状态总数就是 n,空间复杂度和时间复杂度打底就是 $n$ 了。(仍然不考虑滚动数组优化) 再举个例子:62. 不同路径 12345一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径? 这道题是和上面的爬楼梯很像,只不过从一维变成了二维,我把它叫做二维爬楼梯,类似的换皮题还很多,大家慢慢体会。 这道题我定义状态为 f(i, j) 表示机器人到达点 (i,j) 的总的路径数。那么状态总数就是 i 和 j 的取值的笛卡尔积,也就是 m * n 。 总的来说,动态规划的空间和时间复杂度打底就是状态的个数,而状态的个数通常是参数的笛卡尔积,这是由动态规划的无后向性决定的。 临界条件是比较最容易的 当你定义好了状态,剩下就三件事了: 临界条件 状态转移方程 枚举状态 在上面讲解的爬楼梯问题中,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么: 12f(1) 与 f(2) 就是【边界】f(n) = f(n-1) + f(n-2) 就是【状态转移公式】 我用动态规划的形式表示一下: 12dp[0] 与 dp[1] 就是【边界】dp[n] = dp[n - 1] + dp[n - 2] 就是【状态转移方程】 可以看出记忆化递归和动态规划是多么的相似。 实际上临界条件相对简单,大家只有多刷几道题,里面就有感觉。困难的是找到状态转移方程和枚举状态。这两个核心点的都建立在已经抽象好了状态的基础上。比如爬楼梯的问题,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么 f(1), f(2), … 就是各个独立的状态。 搞定了状态的定义,那么我们来看下状态转移方程。 状态转移方程动态规划中当前阶段的状态往往是上一阶段状态和上一阶段决策的结果。这里有两个关键字,分别是 : 上一阶段状态 上一阶段决策 也就是说,如果给定了第 k 阶段的状态 s[k] 以及决策 choice(s[k]),则第 k+1 阶段的状态 s[k+1] 也就完全确定,用公式表示就是:s[k] + choice(s[k]) -> s[k+1], 这就是状态转移方程。需要注意的是 choice 可能有多个,因此每个阶段的状态 s[k+1]也会有多个。 继续以上面的爬楼梯问题来说,爬楼梯问题由于上第 n 级台阶一定是从 n - 1 或者 n - 2 来的,因此 上第 n 级台阶的数目就是 上 n - 1 级台阶的数目加上 n - 2 级台阶的数目。 上面的这个理解是核心, 它就是我们的状态转移方程,用代码表示就是 f(n) = f(n - 1) + f(n - 2)。 实际操作的过程,有可能题目和爬楼梯一样直观,我们不难想到。也可能隐藏很深或者维度过高。 如果你实在想不到,可以尝试画图打开思路,这也是我刚学习动态规划时候的方法。当你做题量上去了,你的题感就会来,那个时候就可以不用画图了。 比如我们定义了状态方程,据此我们定义初始状态和目标状态。然后聚焦最优子结构,思考每一个状态究竟如何进行扩展使得离目标状态越来越近。 如下图所示: 理论差不多先这样,接下来来几个实战消化一下。 ok,接下来是解密环节。上面两道题我们都没有讲转移方程,我们在这里补上。 第一道题:《5. 最长回文子串》难度中等。上面我们的两种状态定义都不好,而我可以在上面的基础上稍微变动一点就可以使得转移方程变得非常好写。这个技巧在很多动态题目都有体现,比如最长上升子序列等,需要大家掌握。 以上面提到的 f(start, end) 来说,含义是子串 s[start:end+1]的最长回文子串。表示方式我们不变,只是将含义变成子串 s[start:end+1]的最长回文子串,且必须包含 start 和 end。经过这样的定义,实际上我们也没有必要定义 f(start, end)的返回值是长度了,而仅仅是布尔值就行了。如果返回 true, 则最长回文子串就是 end - start + 1,否则就是 0。 这样转移方程就可以写为: 1f(i,j)=f(i+1,j−1) and s[i] == s[j] 第二道题:《10. 正则表达式匹配》难度困难。 以我们分析的 f(s_start, s_end, p_start, p_end) 来说,含义是子串 p1[p_start:p_end+1] 是否可以匹配字符串 s[s_start:s_end+1]。 实际上,我们可以定义更简单的方式,那就是 f(s_end, p_end),含义是子串 p1[:p_end+1] 是否可以匹配字符串 s[:s_end+1]。也就是说固定起点为索引 0,这同样也是一个很常见的技巧,请务必掌握。 这样转移方程就可以写为: if p[j] 是小写字母,是否匹配取决于 s[i] 是否等于 p[j]: f(i,j)=\\left\\{ \\begin{aligned} f(i-1, j-1) & & s[i] == p[j] \\\\ false & & s[i] != p[j] \\\\ \\end{aligned} \\right. if p[j] == ‘.’,一定可匹配: 1f(i,j)=f(i-1,j−1) if p[j] == ‘*‘,表示 p 可以匹配 s 第 j−1 个字符匹配任意次: f(i,j)=\\left\\{ \\begin{aligned} f(i-1, j) & & 匹配1次以上 \\\\ f(i, j - 2) & & 匹配0次 \\\\ \\end{aligned} \\right.相信你能分析到这里,写出代码就不是难事了。具体代码可参考我的力扣题解仓库,咱就不在这里讲了。 注意到了么?所有的状态转移方程我都使用了上述的数学公式来描述。没错,所有的转移方程都可以这样描述。我建议大家做每一道动态规划题目都写出这样的公式,起初你可能觉得很烦麻烦。不过相信我,你坚持下去,会发现自己慢慢变强大。就好像我强烈建议你每一道题都分析好复杂度一样。动态规划不仅要搞懂转移方程,还要自己像我那样完整地用数学公式写出来。 是不是觉得状态转移方程写起来麻烦?这里我给大家介绍一个小技巧,那就是使用 latex,latex 语法可以方便地写出这样的公式。另外西法还贴心地写了一键生成动态规划转移方程公式的功能,帮助大家以最快速度生成公诉处。 插件地址:https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template 状态转移方程实在是没有什么灵丹妙药,不同的题目有不同的解法。状态转移方程同时也是解决动态规划问题中最最困难和关键的点,大家一定要多多练习,提高题感。接下来,我们来看下不那么困难,但是新手疑问比较多的问题 - 如何枚举状态。 当然状态转移方程可能不止一个, 不同的转移方程对应的效率也可能大相径庭,这个就是比较玄学的话题了,需要大家在做题的过程中领悟。 如何枚举状态前面说了如何枚举状态,才能不重不漏是枚举状态的关键所在。 如果是一维状态,那么我们使用一层循环可以搞定。 12for i in range(1, n + 1): pass 如果是两维状态,那么我们使用两层循环可以搞定。 123for i in range(1, m + 1): for j in range(1, n + 1): pass 。。。 但是实际操作的过程有很多细节比如: 一维状态我是先枚举左边的还是右边的?(从左到右遍历还是从右到左遍历) 二维状态我是先枚举左上边的还是右上的,还是左下的还是右下的? 里层循环和外层循环的位置关系(可以互换么) 。。。 其实这个东西和很多因素有关,很难总结出一个规律,而且我认为也完全没有必要去总结规律。 不过这里我还是总结了一个关键点,那就是: 如果你没有使用滚动数组的技巧,那么遍历顺序取决于状态转移方程。比如: 12for i in range(1, n + 1): dp[i] = dp[i - 1] + 1 那么我们就需要从左到右遍历,原因很简单,因为 dp[i] 依赖于 dp[i - 1],因此计算 dp[i] 的时候, dp[i - 1] 需要已经计算好了。 二维的也是一样的,大家可以试试。 如果你使用了滚动数组的技巧,则怎么遍历都可以,但是不同的遍历意义通常不不同的。比如我将二维的压缩到了一维: 123for i in range(1, n + 1): for j in range(1, n + 1): dp[j] = dp[j - 1] + 1; 这样是可以的。 dp[j - 1] 实际上指的是压缩前的 dp[i][j - 1] 而: 1234for i in range(1, n + 1): # 倒着遍历 for j in range(n, 0, -1): dp[j] = dp[j - 1] + 1; 这样也是可以的。 但是 dp[j - 1] 实际上指的是压缩前的 dp[i - 1][j - 1]。因此实际中采用怎么样的遍历手段取决于题目。我特意写了一个 【完全背包问题】套路题(1449. 数位成本和为目标值的最大数字 文章,通过一个具体的例子告诉大家不同的遍历有什么实际不同,强烈建议大家看看,并顺手给个三连。 关于里外循环的问题,其实和上面原理类似。 这个比较微妙,大家可以参考这篇文章理解一下 0518.coin-change-2。 小结关于如何确定临界条件通常是比较简单的,多做几个题就可以快速掌握。 关于如何确定状态转移方程,这个其实比较困难。 不过所幸的是,这些套路性比较强, 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 这样遇到新的题目可以往上套, 实在套不出那就先老实画图,不断观察,提高题感。 关于如何枚举状态,如果没有滚动数组, 那么根据转移方程决定如何枚举即可。 如果用了滚动数组,那么要注意压缩后和压缩前的 dp 对应关系即可。 动态规划 VS 记忆化递归上面我们用记忆化递归的问题巧妙地解决了爬楼梯问题。 那么动态规划是怎么解决这个问题呢? 答案也是“查表”,我们平常写的 dp table 就是表,其实这个 dp table 和上面的 memo 没啥差别。 而一般我们写的 dp table,数组的索引通常对应记忆化递归的函数参数,值对应递归函数的返回值。 看起来两者似乎没任何思想上的差异,区别的仅仅是写法?? 没错。不过这种写法上的差异还会带来一些别的相关差异,这点我们之后再讲。 如果上面的爬楼梯问题,使用动态规划,代码是怎么样的呢?我们来看下: 1234567891011function climbStairs(n) { if (n == 1) return 1; const dp = new Array(n); dp[0] = 1; dp[1] = 2; for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 大家现在不会也没关系,我们将前文的递归的代码稍微改造一下。其实就是将函数的名字改一下: 12345function dp(n) { if (n === 1) return 1; if (n === 2) return 2; return dp(n - 1) + dp(n - 2);} 经过这样的变化。我们将 dp[n] 和 dp(n) 对比看,这样是不是有点理解了呢? 其实他们的区别只不过是递归用调用栈枚举状态, 而动态规划使用迭代枚举状态。 如果需要多个维度枚举,那么记忆化递归内部也可以使用迭代进行枚举,比如最长上升子序列问题。 动态规划的查表过程如果画成图,就是这样的: 虚线代表的是查表过程 滚动数组优化爬楼梯我们并没有必要使用一维数组,而是借助两个变量来实现的,空间复杂度是 O(1)。代码: 12345678910111213141516function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; let a = 1; let b = 2; let temp; for (let i = 3; i <= n; i++) { temp = a + b; a = b; b = temp; } return temp;} 之所以能这么做,是因为爬楼梯问题的状态转移方程中当前状态只和前两个状态有关,因此只需要存储这两个即可。 动态规划问题有很多这种讨巧的方式,这个技巧叫做滚动数组。 这道题目是动态规划中最简单的问题了,因为仅涉及到单个因素的变化,如果涉及到多个因素,就比较复杂了,比如著名的背包问题,挖金矿问题等。 对于单个因素的,我们最多只需要一个一维数组即可,对于如背包问题我们需要二维数组等更高纬度。 回答上面的问题:记忆化递归和动态规划除了一个用递归一个用迭代,其他没差别。那两者有啥区别呢?我觉得最大的区别就是记忆化递归无法使用滚动数组优化(不信你用上面的爬楼梯试一下),记忆化调用栈的开销比较大(复杂度不变,你可以认为空间复杂度常数项更大),不过几乎不至于 TLE 或者 MLE。因此我的建议就是没空间优化需求直接就记忆化,否则用迭代 dp。 再次强调一下: 如果说递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。 记忆化递归和动态规划没有本质不同。都是枚举状态,并根据状态直接的联系逐步推导求解。 动态规划性能通常更好。 一方面是递归的栈开销,一方面是滚动数组的技巧。 动态规划的基本类型 背包 DP(这个我们专门开了一个专题讲) 区间 DP 区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。令状态 $f(i,j)$ 表示将下标位置 $i$ 到 $j$ 的所有元素合并能获得的价值的最大值,那么 $f(i,j)=\\max\\{f(i,k)+f(k+1,j)+cost\\}$,$cost$ 为将这两组元素合并起来的代价。 区间 DP 的特点: 合并:即将两个或多个部分进行整合,当然也可以反过来; 特征:能将问题分解为能两两合并的形式; 求解:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。 推荐两道题: 877. 石子游戏 312. 戳气球 状压 DP 关于状压 DP 可以参考下我之前写过的一篇文章: 状压 DP 是什么?这篇题解带你入门 数位 DP 数位 DP 通常是这:给定一个闭区间 ,让你求这个区间中满足某种条件的数的总数。 推荐一道题 Increasing-Digits 计数 DP 和 概率 DP 这两个我就不多说。因为没啥规律。 之所以列举计数 DP 是因为两个原因: 让大家知道确实有这个题型。 计数是动态规划的副产物。 概率 DP 比较特殊,概率 DP 的状态转移公式一般是说一个状态有多大的概率从某一个状态转移过来,更像是期望的计算,因此也叫期望 DP。 推荐两道题: 91. 解码方法 837. 新 21 点 更多题目类型以及推荐题目见刷题插件的学习路线。插件获取方式:公众号力扣加加回复插件。 什么时候用记忆化递归? 从数组两端同时进行遍历的时候使用记忆化递归方便,其实也就是区间 DP(range dp)。比如石子游戏,再比如这道题 https://binarysearch.com/problems/Make-a-Palindrome-by-Inserting-Characters 如果区间 dp 你的遍历方式大概需要这样: 12345678910class Solution: def solve(self, s): n = len(s) dp = [[0] * n for _ in range(n)] # 右边界倒序遍历 for i in range(n - 1, -1, -1): # 左边界正序遍历 for j in range(i + 1, n): # do something return dp[0][m-1] # 一般都是使用这个区间作为答案 如果使用记忆化递归则不需考虑遍历方式的问题。 代码: 12345678910111213class Solution: def solve(self, s): @lru_cache(None) def helper(l, r): if l >= r: return 0 if s[l] == s[r]: return helper(l + 1, r - 1) return 1 + min(helper(l + 1, r), helper(l, r - 1)) return helper(0, len(s) - 1) 选择 比较离散的时候,使用记忆化递归更好。比如马走棋盘。 那什么时候不用记忆化递归呢?答案是其他情况都不用。因为普通的 dp table 有一个重要的功能,这个功能记忆化递归是无法代替的,那就是滚动数组优化。如果你需要对空间进行优化,那一定要用 dp table。 热身开始理论知识已经差不多了,我们拿一道题来试试手。 我们以一个非常经典的背包问题来练一下手。 题目:322. 零钱兑换 1234567891011给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。 示例 1:输入:coins = [1, 2, 5], amount = 11输出:3解释:11 = 5 + 5 + 1 这道题的参数有两个,一个是 coins,一个是 amount。 我们可以定义状态为 f(i, j) 表示用 coins 的前 i 项找 j 元需要的最少硬币数。那么答案就是 f(len(coins) - 1, amount)。 由组合原理,coins 的所有选择状态是 $2^n$。状态总数就是 i 和 j 的取值的笛卡尔积,也就是 2^len(coins) * (amount + 1)。 减 1 是因为存在 0 元的情况。 明确了这些,我们需要考虑的就是状态如何转移,也就是如何从寻常转移到 f(len(coins) - 1, amount)。 如何确定状态转移方程?我们需要: 聚焦最优子结构 做选择,在选择中取最优解(如果是计数 dp 则进行计数) 对于这道题来说,我们的选择有两种: 选择 coins[i] 不选择 coins[i] 这无疑是完备的。只不过仅仅是对 coins 中的每一项进行选择与不选择,这样的状态数就已经是 $2^n$ 了,其中 n 为 coins 长度。 如果仅仅是这样枚举肯定会超时,因为状态数已经是指数级别了。 而这道题的核心在于 coins[i] 选择与否其实没有那么重要,重要的其实是选择的 coins 一共有多少钱。 因此我们可以定义 f(i, j) 表示选择了 coins 的前 i 项(怎么选的不关心),且组成 j 元需要的最少硬币数。 举个例子来说,比如 coins = [1,2,3] 。那么选择 [1,2] 和 选择 [3] 虽然是不一样的状态,但是我们压根不关心。因为这两者没有区别,我们还是谁对结果贡献大就 pick 谁。 以 coins = [1,2,3], amount = 6 来说,我们可以画出如下的递归树。 (图片来自https://leetcode.com/problems/coin-change/solution/) 因此转移方程就是 min(dp[i][j], dp[i-1][j - coins[j]] + 1),含义就是: min(不选择 coins[j], 选择 coins[j]) 所需最少的硬币数。 用公式表示就是: dp[i]=\\left\\{ \\begin{aligned} min(dp[i][j], dp[i-1][j - coins[j]] + 1) & & j >= coins[j] \\\\ amount + 1 & & j < coins[j] \\\\ \\end{aligned} \\right. amount 表示无解。因为硬币的面额都是正整数,不可能存在一种需要 amount + 1 枚硬币的方案。 代码 记忆化递归: 123456789101112class Solution: def coinChange(self, coins: List[int], amount: int) -> int: @lru_cache(None) def dfs(amount): if amount < 0: return float('inf') if amount == 0: return 0 ans = float('inf') for coin in coins: ans = min(ans, 1 + dfs(amount - coin)) return ans ans = dfs(amount) return -1 if ans == float('inf') else ans 二维 dp: 12345678910111213141516171819202122class Solution: def coinChange(self, coins: List[int], amount: int) -> int: if amount < 0: return - 1 dp = [[amount + 1 for _ in range(len(coins) + 1)] for _ in range(amount + 1)] # 初始化第一行为0,其他为最大值(也就是amount + 1) for j in range(len(coins) + 1): dp[0][j] = 0 for i in range(1, amount + 1): for j in range(1, len(coins) + 1): if i - coins[j - 1] >= 0: dp[i][j] = min( dp[i][j - 1], dp[i - coins[j - 1]][j] + 1) else: dp[i][j] = dp[i][j - 1] return -1 if dp[-1][-1] == amount + 1 else dp[-1][-1] dp[i][j] 依赖于dp[i][j - 1]和 dp[i - coins[j - 1]][j] + 1) 这是一个优化的信号,我们可以将其优化到一维。 一维 dp(滚动数组优化): 1234567891011class Solution: def coinChange(self, coins: List[int], amount: int) -> int: dp = [amount + 1] * (amount + 1) dp[0] = 0 for j in range(len(coins)): for i in range(1, amount + 1): if i >= coins[j]: dp[i] = min(dp[i], dp[i - coins[j]] + 1) return -1 if dp[-1] == amount + 1 else dp[-1] 推荐练习题目最后推荐几道题目给大家,建议大家分别使用记忆化递归和动态规划来解决。如果使用动态规划,则尽可能使用滚动数组优化空间。 0091.decode-ways 0139.word-break 0198.house-robber 0309.best-time-to-buy-and-sell-stock-with-cooldown 0322.coin-change 0416.partition-equal-subset-sum 0518.coin-change-2 总结本篇文章总结了算法中比较常用的两个方法 - 递归和动态规划。递归的话可以拿树的题目练手,动态规划的话则将我上面推荐的刷完,再考虑去刷力扣的动态规划标签即可。 大家前期学习动态规划的时候,可以先尝试使用记忆化递归解决。然后将其改造为动态规划,这样多练习几次就会有感觉。之后大家可以练习一下滚动数组,这个技巧很有用,并且相对来说比较简单。 动态规划的核心在于定义状态,定义好了状态其他都是水到渠成。 动态规划的难点在于枚举所有状态(不重不漏) 和 寻找状态转移方程。 参考 oi-wiki - dp 这个资料推荐大家学习,非常全面。只不过更适合有一定基础的人,大家可以配合本讲义食用哦。 另外,大家可以去 LeetCode 探索中的 递归 I 中进行互动式学习。","categories":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/categories/动态规划/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"为何我刷了很多,遇到新的题还是唯唯诺诺,无法重拳出击?","slug":"out-of-science","date":"2021-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.845Z","comments":true,"path":"2021/04/06/out-of-science/","link":"","permalink":"https://lucifer.ren/blog/2021/04/06/out-of-science/","excerpt":"为何我刷了很多题,遇到新的题还是唯唯诺诺,无法重拳出击?为何一看就会一些就废?这其中又隐藏着怎样的秘密?究竟是道德的沦丧还是人性的扭曲?欢迎收看本期的 走出科学 特别栏目。 今天给大家带来了三位重量级选手,一位体重 98 公斤,一位体重 100 公斤,还有一位体重 98 斤,今天 lucifer 就带大家用同样的招式 KO 他们三位。","text":"为何我刷了很多题,遇到新的题还是唯唯诺诺,无法重拳出击?为何一看就会一些就废?这其中又隐藏着怎样的秘密?究竟是道德的沦丧还是人性的扭曲?欢迎收看本期的 走出科学 特别栏目。 今天给大家带来了三位重量级选手,一位体重 98 公斤,一位体重 100 公斤,还有一位体重 98 斤,今天 lucifer 就带大家用同样的招式 KO 他们三位。 第一位选手 - 84. 柱状图中最大的矩形题目地址https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ 题目描述给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。\b 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。 1234示例:输入:[2,1,5,6,2,3]输出:10 公司 阿里 腾讯 百度 字节 前置知识 单调栈 暴力枚举 - 左右端点法(TLE)思路我们暴力尝试所有可能的矩形。由于矩阵是二维图形, 我我们可以使用左右两个端点来唯一确认一个矩阵。因此我们使用双层循环枚举所有的可能性即可。 而矩形的面积等于(右端点坐标 - 左端点坐标 + 1) * 最小的高度,最小的高度我们可以在遍历的时候顺便求出。 代码1234567891011class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, ans = len(heights), 0 if n != 0: ans = heights[0] for i in range(n): height = heights[i] for j in range(i, n): height = min(height, heights[j]) ans = max(ans, (j - i + 1) * height) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(1)$ 暴力枚举 - 中心扩展法(TLE)思路我们仍然暴力尝试所有可能的矩形。只不过我们这一次从中心向两边进行扩展。对于每一个 i,我们计算出其左边第一个高度小于它的索引 p,同样地,计算出右边第一个高度小于它的索引 q。那么以 i 为最低点能够构成的面积就是(q - p - 1) * heights[i]。 这种算法毫无疑问也是正确的。 我们证明一下,假设 f(i) 表示求以 i 为最低点的情况下,所能形成的最大矩阵面积。那么原问题转化为max(f(0), f(1), f(2), ..., f(n - 1))。 具体算法如下: 我们使用 l 和 r 数组。l[i] 表示 左边第一个高度小于它的索引,r[i] 表示 右边第一个高度小于它的索引。 我们从前往后求出 l,再从后往前计算出 r。 再次遍历求出所有的可能面积,并取出最大的。 代码1234567891011121314151617class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j -= 1 l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j += 1 r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(N)$ 优化中心扩展法(Accepted)思路实际上我们内层循环没必要一步一步移动,我们可以直接将j -= 1 改成 j = l[j], j += 1 改成 j = r[j]。 代码123456789101112131415161718class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j = l[j] l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j = r[j] r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 单调栈(Accepted)思路实际上,读完第二种方法的时候,你应该注意到了。我们的核心是求左边第一个比 i 小的和右边第一个比 i 小的。 如果你熟悉单调栈的话,那么应该会想到这是非常适合使用单调栈来处理的场景。 从左到右遍历柱子,对于每一个柱子,我们想找到第一个高度小于它的柱子,那么我们就可以使用一个单调递增栈来实现。 如果柱子大于栈顶的柱子,那么说明不是我们要找的柱子,我们把它塞进去继续遍历,如果比栈顶小,那么我们就找到了第一个小于的柱子。 对于栈顶元素,其右边第一个小于它的就是当前遍历到的柱子,左边第一个小于它的就是栈中下一个要被弹出的元素,因此以当前栈顶为最小柱子的面积为当前栈顶的柱子高度 * (当前遍历到的柱子索引 - 1 - (栈中下一个要被弹出的元素索引 + 1) + 1),化简一下就是 当前栈顶的柱子高度 * (当前遍历到的柱子索引 - 栈中下一个要被弹出的元素索引 - 1)。 这种方法只需要遍历一次,并用一个栈。由于每一个元素最多进栈出栈一次,因此时间和空间复杂度都是$O(N)$。 为了统一算法逻辑,减少边界处理,我在 heights 首尾添加了两个哨兵元素,这样我们可以保证所有的柱子都会出栈。 代码代码支持: Python,CPP Python Code: 12345678class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans CPP Code: 123456789101112131415161718class Solution {public: int largestRectangleArea(vector<int>& A) { A.push_back(0); int N = A.size(), ans = 0; stack<int> s; for (int i = 0; i < N; ++i) { while (s.size() && A[s.top()] >= A[i]) { int h = A[s.top()]; s.pop(); int j = s.size() ? s.top() : -1; ans = max(ans, h * (i - j - 1)); } s.push(i); } return ans; }}; 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 2020-05-30 更新: 有的观众反应看不懂为啥需要两个哨兵。 其实末尾的哨兵就是为了将栈清空,防止遍历完成栈中还有没参与运算的数据。 而前面的哨兵有什么用呢? 我这里把上面代码进行了拆解: 1234567891011class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights),[0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: a = heights[st[-1]] st.pop() # 如果没有前面的哨兵,这里的 st[-1] 可能会越界。 ans = max(ans, a * (i - 1 - st[-1])) st.append(i) return ans 相关题目 42.trapping-rain-water 第二位选手 - 85. 最大矩形题目地址https://leetcode-cn.com/problems/maximal-rectangle/ 题目描述给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 示例: 输入: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 输出:6 前置知识 单调栈 公司 阿里 腾讯 百度 字节 思路我在 【84. 柱状图中最大的矩形】多种方法(Python3) 使用了多种方法来解决。 然而在这道题,我们仍然可以使用完全一样的思路去完成。本题解是基于那道题的题解来进行的。 拿题目给的例子来说: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 我们逐行扫描得到 84. 柱状图中最大的矩形 中的 heights 数组: 这样我们就可以使用84. 柱状图中最大的矩形 中的解法来进行了,这里我们使用单调栈来解。 下面的代码直接将 84 题的代码封装成 API 调用了。 代码代码支持:Python Python Code: 1234567891011121314151617181920212223class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans def maximalRectangle(self, matrix: List[List[str]]) -> int: m = len(matrix) if m == 0: return 0 n = len(matrix[0]) heights = [0] * n ans = 0 for i in range(m): for j in range(n): if matrix[i][j] == \"0\": heights[j] = 0 else: heights[j] += 1 ans = max(ans, self.largestRectangleArea(heights)) return ans 复杂度分析 时间复杂度:$O(M * N)$ 空间复杂度:$O(N)$ 第三位选手 - 221. 最大正方形题目地址https://leetcode-cn.com/problems/maximal-square/ 题目描述123456789101112在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。示例:输入:1 0 1 0 01 0 1 1 11 1 1 1 11 0 0 1 0输出: 4 前置知识 动态规划 递归 公司 阿里 腾讯 百度 字节 思路看到题看起来和上面的题目类似,只不过把长方形限定为了正方形嘛。 符合直觉的做法是暴力求解处所有的正方形,逐一计算面积,然后记录最大的。这种时间复杂度很高。 我们考虑使用动态规划,我们使用 dp[i][j]表示以 matrix[i][j]为右下角的顶点的可以组成的最大正方形的边长。那么我们只需要计算所有的 i,j 组合,然后求出最大值即可。 我们来看下 dp[i][j] 怎么推导。 首先我们要看 matrix[i][j], 如果 matrix[i][j]等于 0,那么就不用看了,直接等于 0。如果 matrix[i][j]等于 1,那么我们将 matrix[[i][j]分别往上和往左进行延伸,直到碰到一个 0 为止。 如图 dp[3][3] 的计算。 matrix[3][3]等于 1,我们分别往上和往左进行延伸,直到碰到一个 0 为止,上面长度为 1,左边为 3。dp[2][2]等于 1(之前已经计算好了),那么其实这里的瓶颈在于三者的最小值, 即Min(1, 1, 3), 也就是1。 那么 dp[3][3] 就等于Min(1, 1, 3) + 1。 dp[i - 1][j - 1]我们直接拿到,关键是往上和往左进行延伸, 最直观的做法是我们内层加一个循环去做就好了。但是我们仔细观察一下,其实我们根本不需要这样算。 我们可以直接用 dp[i - 1][j]和 dp[i][j -1]。具体就是Min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1。 事实上,这道题还有空间复杂度 O(N)的解法,其中 N 指的是列数。大家可以去这个leetcode 讨论看一下。 关键点解析 DP 递归公式可以利用 dp[i - 1][j]和 dp[i][j -1]的计算结果,而不用重新计算 空间复杂度可以降低到 O(n), n 为列数 代码代码支持:Python,JavaScript: Python Code: 1234567891011121314class Solution: def maximalSquare(self, matrix: List[List[str]]) -> int: res = 0 m = len(matrix) if m == 0: return 0 n = len(matrix[0]) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1 if matrix[i - 1][j - 1] == \"1\" else 0 res = max(res, dp[i][j]) return res ** 2 JavaScript Code: 12345678910111213141516171819202122232425262728293031323334353637/* * @lc app=leetcode id=221 lang=javascript * * [221] Maximal Square *//** * @param {character[][]} matrix * @return {number} */var maximalSquare = function (matrix) { if (matrix.length === 0) return 0; const dp = []; const rows = matrix.length; const cols = matrix[0].length; let max = Number.MIN_VALUE; for (let i = 0; i < rows + 1; i++) { if (i === 0) { dp[i] = Array(cols + 1).fill(0); } else { dp[i] = [0]; } } for (let i = 1; i < rows + 1; i++) { for (let j = 1; j < cols + 1; j++) { if (matrix[i - 1][j - 1] === \"1\") { dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1; max = Math.max(max, dp[i][j]); } else { dp[i][j] = 0; } } } return max * max;}; 复杂度分析 时间复杂度:$O(M * N)$,其中 M 为行数,N 为列数。 空间复杂度:$O(M * N)$,其中 M 为行数,N 为列数。 如果我还想用上面的方法可以么?当然可以! lucifer 我就专门给大家改写了一下,直接将上面的代码复制过来,改了一行就 AC 了。 1234567891011121314151617181920212223242526class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: # 就只改了下面这一行 ans = max(ans, min(heights[st.pop()], (i - st[-1] - 1)) ** 2) st.append(i) return ans def maximalSquare(self, matrix: List[List[str]]) -> int: m = len(matrix) if m == 0: return 0 n = len(matrix[0]) heights = [0] * n ans = 0 for i in range(m): for j in range(n): if matrix[i][j] == \"0\": heights[j] = 0 else: heights[j] += 1 ans = max(ans, self.largestRectangleArea(heights)) return ans 总结上面三道题我都用的是 largestRectangleArea 的核心逻辑,基本上调用下稍微改改就完事了。对于 largestRectangleArea 的解法最明显的莫过于平方级别的暴力,可如果你仔细分析就发现我们可以通过指针不回溯达到降低复杂度的效果。而这里的指针不回溯可以是单调栈,也可以是我给的双指针数组方法。不管用的哪种,区别的只是战术,战略思想是一样的。大家做题的时候也要注重战略,训练战术。 你说这招是不是很好使?学习算法不可贪多,不然嚼不烂。当你细细咀嚼后会发现,其实算法的思想和套路还真不多,至少应付大部分的面试和 OJ 题目不成问题。","categories":[{"name":"走出科学","slug":"走出科学","permalink":"https://lucifer.ren/blog/categories/走出科学/"}],"tags":[{"name":"走出科学","slug":"走出科学","permalink":"https://lucifer.ren/blog/tags/走出科学/"}]},{"title":"lucifer 的面试之路","slug":"interview-road","date":"2021-04-02T16:00:00.000Z","updated":"2023-01-05T12:24:50.186Z","comments":true,"path":"2021/04/03/interview-road/","link":"","permalink":"https://lucifer.ren/blog/2021/04/03/interview-road/","excerpt":"","text":"为什么有这个栏目?关注 lucifer 的同学可能知道,我之前在组织模拟面试。这个活动是我作为面试官虐别人的。很多胖友想看我是如何面试被虐的。可是我想我也没被面试虐过啊(逃~),就算被虐过你们也看不到,所以我也不会承认的。 所以我就打算开这个系列,从网上找一些靠谱的面经。把我自己当成面试者去回答面经的问题,让大家看看我是如何被虐的。 为了更好的节目效果(不会承认是自己懒),我就不提前准备了,直接拿起来面经就开始干了,这样大家的参考价值更高。 本期猎物 虾皮 题目面经来源:https://www.1point3acres.com/bbs/forum.php?mod=viewthread&tid=723973&extra=page%3D1%26filter%3Dsortid%26sortid%3D327%26sortid%3D327 一面 JS 给一个 array [“1”, “2”, “3”]转化为 string “1, 2, 3”,[“1”, “2”, [“1”, “2”], “3”] 转化为 string “1,2,1,2,3”,follow up: 输入的 array 可能有哪些异常情况,该如何处理 其实就是考察 flatten。我的大前端面试宝典收录了这个题。大前端面试宝典地址在文末。 经典问题实现函数柯里化 curry(add()),背了无数遍的实现,秒了 我的大前端面试宝典收录了这个题。大前端面试宝典地址在文末。 基础知识问答: html 渲染流程,follow up: defer 和 async 标签的区别 defer 和 async 都不会组织后面文档的渲染和执行。区别是文档中的 defer 标签会按照在文档中的声明顺序执行,async 则不会。 https 和 http, follow up: 什么是中间人攻击,数字签名证书,RSA 加密过程 JavaScript 有几种数据类型 JavaScript 代码的执行顺序(宏任务,微任务经典背诵) 什么是闭包 一定先搞明白作用域,然后提到词法作用域才是闭包产生的原因。最后讲下闭包的原理和应用。 二面一些 BQ,问的 LZ 有点懵 基础知识问答: React 大致介绍一下 React 的 virtual dom, follow up: 渲染中 reflow 和 repaint 的区别 React 的 render return 的是什么 React native 用过吗(答:没有,但我会用 Java 写 Android)follow up: Recycler View 介绍一下(说好的前端呢) 跑偏了,Java 的垃圾处理(LZ 从 JVM 的内存结构开始讲起,什么新生代老年代元空间,什么标记清除法) 一个像素占用内存大小,答:不知道。follow up:猜一下,LZ 猜测 RGB 三个通道 0-255,256 是 2 的 8 次方,所以一个通道 1byte,三个加上 3 byte。面试官表示还有个透明通道,加上一共 4byte 1 周以后 HR 通知过了 视频解析bilibili 在线观看:https://www.bilibili.com/video/BV1qV411n7Qc 参考 大前端面试宝典 不要再问我头像如何变灰了,试试这几种滤镜吧!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"},{"name":"虾皮","slug":"虾皮","permalink":"https://lucifer.ren/blog/categories/虾皮/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"},{"name":"虾皮","slug":"虾皮","permalink":"https://lucifer.ren/blog/tags/虾皮/"}]},{"title":"春招冲冲冲(网易)","slug":"school-03","date":"2021-03-27T16:00:00.000Z","updated":"2023-01-05T12:24:49.777Z","comments":true,"path":"2021/03/28/school-03/","link":"","permalink":"https://lucifer.ren/blog/2021/03/28/school-03/","excerpt":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是网易。来看看这两家的算法题难度几何吧! ​","text":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是网易。来看看这两家的算法题难度几何吧! ​ 视频地址:https://www.bilibili.com/video/BV14V411e7MF/ 题目来源:https://www.nowcoder.com/discuss/625915 题目一一组数据,判断能组成三角形最多的数,如果有多个,都写下来。 力扣原题 611. 有效三角形的个数 题目描述123456789101112131415给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。示例 1:输入: [2,2,3,4]输出: 3解释:有效的组合是:2,3,4 (使用第一个 2)2,3,4 (使用第二个 2)2,2,3注意:数组长度不超过1000。数组里整数的范围为 [0, 1000]。 前置知识 排序 双指针 二分法 三角形边的关系 暴力法(超时)思路首先要有一个数学前提: 如果三条线段中任意两条的和都大于第三边,那么这三条线段可以组成一个三角形。即给定三个线段 a,b,c,如果满足 a + b > c and a + c > b and b + c > a,则线段 a,b,c 可以构成三角形,否则不可以。 力扣中有一些题目是需要一些数学前提的,不过这些数学前提都比较简单,一般不会超过高中数学知识,并且也不会特别复杂。一般都是小学初中知识即可。 如果你在面试中碰到不知道的数学前提,可以寻求面试官提示试试。 关键点解析 三角形边的关系 三层循环确定三个线段 代码代码支持: Python 1234567891011121314class Solution: def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False if a + b > c and a + c > b and b + c > a: return True return False def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 for i in range(n - 2): for j in range(i + 1, n - 1): for k in range(j + 1, n): if self.is_triangle(nums[i], nums[j], nums[k]): ans += 1 return ans 复杂度分析 时间复杂度:$O(N ^ 3)$,其中 N 为 数组长度。 空间复杂度:$O(1)$ 优化的暴力法思路暴力法的时间复杂度为 $O(N ^ 3)$, 其中 $N$ 最大为 1000。一般来说, $O(N ^ 3)$ 的算法在数据量 <= 500 是可以 AC 的。1000 的数量级则需要考虑 $O(N ^ 2)$ 或者更好的解法。 OK,到这里了。我给大家一个干货。 应该是其他博主不太会提的。原因可能是他们不知道, 也可能是他们觉得太小儿科不需要说。 由于前面我根据数据规模推测到到了解法的复杂度区间是 $N ^ 2$, $N ^ 2 * logN$,不可能是 $N$ (WHY?)。 降低时间复杂度的方法主要有: 空间换时间 和 排序换时间(我们一般都是使用基于比较的排序方法)。而排序换时间仅仅在总体复杂度大于 $O(NlogN)$ 才适用(原因不用多说了吧?)。 这里由于总体的时间复杂度是 $O(N ^ 3)$,因此我自然想到了排序换时间。当我们对 nums 进行一次排序之后,我发现: is_triangle 函数有一些判断是无效的 12345def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False # a + c > b 和 b + c > a 是无效的判断,因为恒成立 if a + b > c and a + c > b and b + c > a: return True return False 因此我们的目标变为找到a + b > c即可,因此第三层循环是可以提前退出的。 123456for i in range(n - 2): for j in range(i + 1, n - 1): k = j + 1 while k < n and num[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 这也仅仅是减枝而已,复杂度没有变化。通过进一步观察,发现 k 没有必要每次都从 j + 1 开始。而是从上次找到的 k 值开始就行。原因很简单, 当 nums[i] + nums[j] > nums[k] 时,我们想要找到下一个满足 nums[i] + nums[j] > nums[k] 的 新的 k 值,由于进行了排序,因此这个 k 肯定比之前的大(单调递增性),因此上一个 k 值之前的数都是无效的,可以跳过。 123456for i in range(n - 2): k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 由于 K 不会后退,因此最内层循环总共最多执行 N 次,因此总的时间复杂度为 $O(N ^ 2)$。 这个复杂度分析有点像单调栈,大家可以结合起来理解。 关键点分析 排序 代码12345678910111213class Solution: def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 nums.sort() for i in range(n - 2): if nums[i] == 0: continue k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:取决于排序算法 题目二给你一个二叉树,实现固定值和的路径,优先层数低的,排在左边的 思路DFS 找出所有的满足和为 target 的,遍历过程维护层数较低的并返回即可。 函数签名为:dfs(node, target, depth),其中 node 为当前的节点, target 为目标和,减到 0 就找到目标路径了,depth 是深度,用于维护层数较低。 也可以使用 BFS 从左到右将 (node, target) 入队即可。 如果还不懂, 建议参考我的树专题 题目三一组数据,找出能组成和被 6 整除的最大值对应的集合 题目描述123456789101112131415161718192021222324给你一个整数数组 nums,请你找出并返回能被六整除的元素最大和。示例 1:输入:nums = [3,6,5,1,8]输出:18解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 6 整除的最大和)。示例 2:输入:nums = [4]输出:0解释:4 不能被 6 整除,所以无法选出数字,返回 0。示例 3:输入:nums = [1,2,3,4,4]输出:12解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 6 整除的最大和)。 提示:1 <= nums.length <= 4 * 10^41 <= nums[i] <= 10^4 前置知识 数组 回溯法 排序 公司 字节 网易有道 暴力法思路力扣类似题 1262. 可被三整除的最大和 这道题是 6 的倍数,而上面的是 3 的倍数。 实际上, 6 的倍数就是在满足三的倍数的条件下,再加上是偶数的条件即可。 这里以 3 的倍数为例,讲一下这道题。 一种方式是找出所有的能够被 3 整除的子集,然后挑选出和最大的。由于我们选出了所有的子集,那么时间复杂度就是 $O(2^N)$ , 毫无疑问会超时。这里我们使用回溯法找子集,如果不清楚回溯法,可以参考我之前的题解,很多题目都用到了,比如78.subsets。 更多回溯题目,可以访问上方链接查看(可以使用一套模板搞定): 代码12345678910111213141516class Solution: def maxSumDivThree(self, nums: List[int]) -> int: self.res = 0 def backtrack(temp, start): total = sum(temp) if total % 3 == 0: self.res = max(self.res, total) for i in range(start, len(nums)): temp.append(nums[i]) backtrack(temp, i + 1) temp.pop(-1) backtrack([], 0) return self.res 减法 + 排序减法的核心思想是,我们求出总和。如果总和不满足题意,我们尝试减去最小的数,使之满足题意。 思路这种算法的思想,具体来说就是: 我们将所有的数字加起来,我们不妨设为 total total 除以 3,得到一个余数 mod, mod 可能值有 0,1,2. 同时我们建立两个数组,一个是余数为 1 的数组 one,一个是余数为 2 的数组 two 如果 mod 为 0,我们直接返回即可。 如果 mod 为 1,我们可以减去 one 数组中最小的一个(如果有的话),或者减去两个 two 数组中最小的(如果有的话),究竟减去谁取决谁更小。 如果 mod 为 2,我们可以减去 two 数组中最小的一个(如果有的话),或者减去两个 one 数组中最小的(如果有的话),究竟减去谁取决谁更小。 由于我们需要取 one 和 two 中最小的一个或者两个,因此对数组 one 和 two 进行排序是可行的,如果基于排序的话,时间复杂度大致为 $O(NlogN)$,这种算法可以通过。 以题目中的例 1 为例: 以题目中的例 2 为例: 代码12345678910111213141516171819202122232425class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [] two = [] total = 0 for num in nums: total += num if num % 3 == 1: one.append(num) if num % 3 == 2: two.append(num) one.sort() two.sort() if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 减法 + 非排序思路上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。 代码123456789101112131415161718192021222324252627282930313233class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [float('inf')] * 2 two = [float('inf')] * 2 total = 0 for num in nums: total += num if num % 3 == 1: if num < one[0]: t = one[0] one[0] = num one[1] = t elif num < one[1]: one[1] = num if num % 3 == 2: if num < two[0]: t = two[0] two[0] = num two[1] = t elif num < two[1]: two[1] = num if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 有限状态机思路我在数据结构与算法在前端领域的应用 - 第二篇 中讲到了有限状态机。 状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。 状态机使用非常广泛,比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。 拿本题中来说,我们从左到右扫描数组的过程,将会不断改变状态机的状态。 我们使用 state 数组来表示本题的状态: state[0] 表示 mod 为 0 的 最大和 state[1] 表示 mod 为 1 的 最大和 state[2] 表示 mod 为 1 的 最大和 我们的状态转移方程就会很容易。说到状态转移方程,你可能会想到动态规划。没错!这种思路可以直接翻译成动态规划,算法完全一样。如果你看过我上面提到的文章,那么状态转移方程对你来说就会很容易。如果你不清楚,那么请往下看: 我们从左往右不断读取数字,我们不妨设这个数字为 num。 如果 num % 3 为 0。 那么我们的 state[0], state[1], state[2] 可以直接加上 num(题目限定了 num 为非负), 因为任何数字加上 3 的倍数之后,mod 3 的值是不变的。 如果 num % 3 为 1。 我们知道 state[2] + num 会变成一个能被三整除的数,但是这个数字不一定比当前的 state[0]大。 代码表示就是max(state[2] + num, state[0])。同理 state[1] 和 state[2] 的转移逻辑类似。 同理 num % 3 为 2 也是类似的逻辑。 最后我们返回 state[0]即可。 代码123456789101112131415161718class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: if num % 3 == 0: state = [state[0] + num, state[1] + num, state[2] + num] if num % 3 == 1: a = max(state[2] + num, state[0]) b = max(state[0] + num, state[1]) c = max(state[1] + num, state[2]) state = [a, b, c] if num % 3 == 2: a = max(state[1] + num, state[0]) b = max(state[2] + num, state[1]) c = max(state[0] + num, state[2]) state = [a, b, c] return state[0] 当然这个代码还可以简化: 1234567891011class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: temp = [0] * 3 for i in range(3): temp[(i + num) % 3] = max(state[(i + num) % 3], state[i] + num) state = temp return state[0] 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 关键点解析 贪婪法 状态机 数学分析 扩展实际上,我们可以采取加法(贪婪策略),感兴趣的可以试一下。 另外如果题目改成了请你找出并返回能被x整除的元素最大和,你只需要将我的解法中的 3 改成 x 即可。 题目四题目描述编辑距离变种,定义了编辑距离和两组字符串长度的比值,参考 https://leetcode-cn.com/problems/edit-distance/ ,只不过增删距离为 1,改距离为 2 思路力扣原题变种 72. 编辑距离 这道题我太熟悉了,这道题用不同的语言我写了不下十次,提供了大概四五种写法(基本思路类似,写法不同)。然而笔试推荐大家记忆化递归来写,毕竟大多数笔试题解题速度比代码运行速度更重要。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122class Solution: @lru_cache(None) def helper(self, word1: str, s1: int, e1: int, word2: str, s2: int, e2: int) -> int: if s1 > e1: return e2 - s2 + 1 elif s2 > e2: return e1 - s1 + 1 c1 = word1[s1] c2 = word2[s2] if c1 == c2: return self.helper(word1, s1 + 1, e1, word2, s2 + 1, e2) else: return ( min( self.helper(word1, s1 + 1, e1, word2, s2, e2) + 1, # delete or add self.helper(word1, s1, e1, word2, s2 + 1, e2) + 1, # delete or add self.helper(word1, s1 + 1, e1, word2, s2 + 1, e2) + 2, # replace ) ) def minDistance(self, word1: str, word2: str) -> int: return self.helper(word1, 0, len(word1) - 1, word2, 0, len(word2) - 1) 复杂度分析 令 m 和 n 分别为两个字符串的长度。 时间复杂度:$O(m * n)$ 空间复杂度:$O(max(m, n))$","categories":[{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/categories/春招/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/tags/春招/"}]},{"title":"几乎刷完了力扣所有的二分题,我发现了这些东西。。。(下)","slug":"binary-search-2","date":"2021-03-22T16:00:00.000Z","updated":"2023-01-07T12:20:39.949Z","comments":true,"path":"2021/03/23/binary-search-2/","link":"","permalink":"https://lucifer.ren/blog/2021/03/23/binary-search-2/","excerpt":"前言大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下) 几乎刷完了力扣所有的二分题,我发现了这些东西。。。(上)","text":"前言大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下) 几乎刷完了力扣所有的二分题,我发现了这些东西。。。(上) 本专题预计分两部分两进行。上一节主要讲述基本概念 和 一个中心。这一节我们继续学习两种二分类型 和四大应用。没有看过上篇的建议先看一下上篇,地址在上面。 如果觉得文章有用,请点赞留言转发一下,让我有动力继续做下去。 上篇回顾上篇主要就是带大家了解几个概念,这些概念对做题极为重要,请务必掌握。接下来讲解了二分法的中心 - 折半,这个中心需要大家做任何二分都要放到脑子中。 二分法的精髓正如开篇提到的二分法是一种让未知世界无机可乘的算法。二分法无论如何我们都可以舍弃一半解,也就是无论如何都可以将解空间砍半。难点就是上面提到的两点:什么条件 和 舍弃哪部分。 接下来,我们继续下篇。下篇注主要内容是两种类型和四大应用。 其中两种类型主要解决的的是:这道题我的解空间以及明确出来了,如何用代码找出具体的值。而四大应用主要解决的是:如何构造解空间(更多的情况则是如何构建有序序列)以及一些变体。 这两部分都是实操性很强的内容。这里我提醒大家,在理解这两部分内容的同时,请大家务必牢记一个中心折半。 两种类型问题定义 这里的问题定义是一个狭义的问题。而如果你理解了这个问题之后,可以将这个具体的问题进行推广以适应更复杂的问题。关于推广,我们之后再谈。 给定一个由数字组成的有序数组 nums,并给你一个数字 target。问 nums 中是否存在 target。如果存在, 则返回其在 nums 中的索引。如果不存在,则返回 - 1。 这是二分查找中最简单的一种形式。当然二分查找也有很多的变形,这也是二分查找容易出错,难以掌握的原因。 常见变体有: 如果存在多个满足条件的元素,返回最左边满足条件的索引。 如果存在多个满足条件的元素,返回最右边满足条件的索引。 数组不是整体有序的。 比如先升序再降序,或者先降序再升序。 将一维数组变成二维数组。 。。。 接下来,我们逐个进行查看。 前提 数组是有序的(如果无序,我们也可以考虑排序,不过要注意排序的复杂度) 这个有序的数组可能是题目直接给的,也可能是你自己构造的。比如求数组的逆序数就可以在自己构造的有序序列上做二分。 术语为了后面描述问题方便,有必要引入一些约定和术语。 二分查找中使用的术语: target —— 要查找的值 index —— 当前位置 l 和 r —— 左右指针 mid —— 左右指针的中点,用来确定我们应该向左查找还是向右查找的索引(其实就是收缩解空间) 值得注意的是,除了 target 是固定不变的,其他都是动态变化的。其中 l 和 r 指的是解空间的上下界,mid 是上下界的中间值, index 是遍历指针,用于控制遍历过程。 查找一个数前面我们已经对问题进行了定义。接下来,我们需要对定义的问题进行分析和求解。 为了更好理解接下来的内容,我们解决最简单的类型 - 查找某一个具体值 。 算法描述: 先从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束; 如果目标元素大于中间元素,那么数组中小于中间元素的值都可以排除(由于数组有序,那么相当于是可以排除数组左侧的所有值),解空间可以收缩为 [mid+1, r]。 如果目标元素小于中间元素,那么数组中大于中间元素的值都可以排除(由于数组有序,那么相当于是可以排除数组右侧的所有值),解空间可以收缩为 [l, mid - 1]。 如果在某一步骤解空间为空,则代表找不到。 举一个具体的例子方便大家增加代入感。假设 nums 为 [1,3,4,6,7,8,10,13,14], target 为 4·。 刚开始数组中间的元素为 7 7 > 4 ,由于 7 右边的数字都大于 7 ,因此不可能是答案。我们将范围缩写到了 7 的左侧。 解空间变成了 [1,3,4,6],此时中间元素为 3。 3 < 4,由于 3 左边的数字都小于 3 ,因此不可能是答案。我们将范围缩写到了 3 的右侧。 解空间变成了 [4,6],此时中间元素为 4,正好是我们要找的,返回其索引 2 即可。 复杂度分析 由于这种搜索算法每一次比较都使搜索范围缩小一半,是典型的二分查找。 平均时间复杂度: $O(logN)$ 最坏时间复杂度: $O(logN)$ 空间复杂度 迭代: $O(1)$ 递归: $O(logN)$(无尾调用消除) 后面的复杂度也是类似的,不再赘述。 思维框架如何将上面的算法转换为容易理解的可执行代码呢? 大家不要小看这样的一个算法。就算是这样一个简简单单,朴实无华的二分查找, 不同的人写出来的差别也是很大的。 如果没有一个思维框架指导你,不同的时间你可能会写出差异很大的代码。这样的话,犯错的几率会大大增加。这里给大家介绍一个我经常使用的思维框架和代码模板。 首先定义解空间为 [left, right],注意是左右都闭合,之后会用到这个点 你可以定义别的解空间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的解空间。 由于定义的解空间为 [left, right],因此当 left <= right 的时候,解空间都不为空,此时我们都需要继续搜索。 也就是说终止搜索条件应该为 left <= right。 举个例子容易明白一点。 比如对于区间 [4,4],其包含了一个元素 4,因此解空间不为空,需要继续搜索(试想 4 恰好是我们要找的 target,如果不继续搜索, 会错过正确答案)。而当解空间为 [left, right) 的时候,同样对于 [4,4],这个时候解空间却是空的,因为这样的一个区间不存在任何数字·。 循环体内,我们不断计算 mid ,并将 nums[mid] 与 目标值比对。 如果 nums[mid] 等于目标值, 则提前返回 mid(只需要找到一个满足条件的即可) 如果 nums[mid] 小于目标值, 说明目标值在 mid 右侧,这个时候解空间可缩小为 [mid + 1, right] (mid 以及 mid 左侧的数字被我们排除在外) 如果 nums[mid] 大于目标值, 说明目标值在 mid 左侧,这个时候解空间可缩小为 [left, mid - 1] (mid 以及 mid 右侧的数字被我们排除在外) 循环结束都没有找到,则说明找不到,返回 -1 表示未找到。 代码模板Java123456789101112131415161718public int binarySearch(int[] nums, int target) { // 左右都闭合的区间 [l, r] int left = 0; int right = nums.length - 1; while(left <= right) { int mid = left + (right - left) / 2; if(nums[mid] == target) return mid; if (nums[mid] < target) // 解空间变为 [mid+1, right] left = mid + 1; if (nums[mid] > target) // 解空间变为 [left, mid - 1] right = mid - 1; } return -1;} Python1234567891011def binarySearch(nums, target): # 左右都闭合的区间 [l, r] l, r = 0, len(nums) - 1 while l <= r: mid = (left + right) >> 1 if nums[mid] == target: return mid # 解空间变为 [mid+1, right] if nums[mid] < target: l = mid + 1 # 解空间变为 [left, mid - 1] if nums[mid] > target: r = mid - 1 return -1 JavaScript123456789101112131415function binarySearch(nums, target) { let left = 0; let right = nums.length - 1; while (left <= right) { const mid = Math.floor(left + (right - left) / 2); if (nums[mid] == target) return mid; if (nums[mid] < target) // 解空间变为 [mid+1, right] left = mid + 1; if (nums[mid] > target) // 解空间变为 [left, mid - 1] right = mid - 1; } return -1;} C++1234567891011121314151617int binarySearch(vector<int>& nums, int target){ if(nums.size() == 0) return -1; int left = 0, right = nums.size() - 1; while(left <= right){ int mid = left + ((right - left) >> 1); if(nums[mid] == target){ return mid; } // 解空间变为 [mid+1, right] else if(nums[mid] < target) left = mid + 1; // 解空间变为 [left, mid - 1] else right = mid - 1; } return -1;} 寻找最左插入位置上面我们讲了寻找满足条件的值。如果找不到,就返回 -1。那如果不是返回 -1,而是返回应该插入的位置,使得插入之后列表仍然有序呢? 比如一个数组 nums: [1,3,4],target 是 2。我们应该将其插入(注意不是真的插入)的位置是索引 1 的位置,即 [1,2,3,4]。因此寻找最左插入位置应该返回 1,而寻找满足条件的位置 应该返回-1。 另外如果有多个满足条件的值,我们返回最左侧的。 比如一个数组 nums: [1,2,2,2,3,4],target 是 2,我们应该插入的位置是 1。 思维框架具体算法: 首先定义解空间为 [left, right],注意是左右都闭合,之后会用到这个点。 你可以定义别的解空间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的解空间。 由于我们定义的解空间为 [left, right],因此当 left <= right 的时候,解空间都不为空。 也就是说我们的终止搜索条件为 left <= right。 当 A[mid] >= x,说明找到一个备胎,我们令 r = mid - 1 将 mid 从解空间排除,继续看看有没有更好的备胎。 当 A[mid] < x,说明 mid 根本就不是答案,直接更新 l = mid + 1,从而将 mid 从解空间排除。 最后解空间的 l 就是最好的备胎,备胎转正。 代码模板Python12345678910def bisect_left(nums, x): # 内置 api bisect.bisect_left(nums, x) # 手写 l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 if A[mid] >= x: r = mid - 1 else: l = mid + 1 return l 寻找最右插入位置思维框架具体算法: 首先定义解空间为 [left, right],注意是左右都闭合,之后会用到这个点。 你可以定义别的解空间形式,不过后面的代码也相应要调整,感兴趣的可以试试别的解空间。 由于我们定义的解空间为 [left, right],因此当 left <= right 的时候,解空间都不为空。 也就是说我们的终止搜索条件为 left <= right。 当 A[mid] > x,说明找到一个备胎,我们令 r = mid - 1 将 mid 从解空间排除,继续看看有没有更好的备胎。 当 A[mid] <= x,说明 mid 根本就不是答案,直接更新 l = mid + 1,从而将 mid 从解空间排除。 最后解空间的 l 就是最好的备胎,备胎转正。 代码模板Python1234567891011def bisect_right(nums, x): # 内置 api bisect.bisect_right(nums, x) # 手写 l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 if A[mid] <= x: l = mid + 1 else: r = mid - 1 return l 以上就是两种二分的基本形式了。而在实际的写代码过程中,我不会使用寻找满足条件的值模板,而是直接使用最左 或者 最右 插入模板。为什么呢?因为后者包含了前者,并还有前者实现不了的功能。比如我要实现寻找满足条件的值,就可直接使用最左插入模板找到插入索引 i,只不过最后判断一下 nums[i] 是否等于 target 即可,如果不等于则返回 -1,否则返回 i。这也是为什么我将二分分为两种类型,而不是三种甚至四种的原因。 另外最左插入和最右插入可以结合使用从而求出有序序列中和 target 相等的数的个数,这在有些时候会是一个考点。代码表示: 1234nums = [1,2,2,2,3,4]i = bisect.bisect_left(nums, 2) # get 1j = bisect.bisect_right(nums, 2) # get 4# j - i 就是 nums 中 2 的个数 为了描述方便,以后所有的最左插入二分我都会简称最左二分,代码上直接用 bisect.bisect_left 表示,而最右插入二分我都会简称最右二分,代码上用 bisect.bisect_right 或者 bisect.bisect 表示。 小结对于二分题目首先要明确解空间,然后根据一定条件(通常是和中间值比较),舍弃其中一半的解。大家可以先从查找满足条件的值的二分入手,进而学习最左和最右二分。同时大家只需要掌握最左和最右二分即可,因为后者功能大于前者。 对于最左和最右二分,简单用两句话总结一下: 最左二分不断收缩右边界,最终返回左边界 最右二分不断收缩左边界,最终返回右边界 四大应用基础知识铺垫了差不多了。接下来,我们开始干货技巧。 接下来要讲的: 能力检测和计数二分本质差不多,都是普通二分 的泛化。 前缀和二分和插入排序二分,本质都是在构建有序序列。 那让我们开始吧。 能力检测二分能力检测二分一般是:定义函数 possible, 参数是 mid,返回值是布尔值。外层根据返回值调整”解空间”。 示例代码(以最左二分为例): 12345678910def ability_test_bs(nums): def possible(mid): pass l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 # 只有这里和最左二分不一样 if possible(mid): l = mid + 1 else: r = mid - 1 return l 和最左最右二分这两种最最基本的类型相比,能力检测二分只是将 while 内部的 if 语句调整为了一个函数罢了。因此能力检测二分也分最左和最右两种基本类型。 基本上大家都可以用这个模式来套。明确了解题的框架,我们最后来看下能力检测二分可以解决哪些问题。这里通过三道题目带大家感受一下,类似的题目还有很多,大家课后自行体会。 875. 爱吃香蕉的珂珂(中等)题目地址https://leetcode-cn.com/problems/koko-eating-bananas/description/ 题目描述1234567891011121314151617181920212223242526272829珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。 珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。 示例 1:输入: piles = [3,6,7,11], H = 8输出: 4示例 2:输入: piles = [30,11,23,4,20], H = 5输出: 30示例 3:输入: piles = [30,11,23,4,20], H = 6输出: 23 提示:1 <= piles.length <= 10^4piles.length <= H <= 10^91 <= piles[i] <= 10^9 前置知识 二分查找 公司 字节 思路题目是让我们求H 小时内吃掉所有香蕉的最小速度。 符合直觉的做法是枚举所有可能的速度,找出所有的可以吃完香蕉的速度,接下来选择最小的速度即可。由于需要返回最小的速度,因此选择从小到大枚举会比较好,因为可以提前退出。 这种解法的时间复杂度比较高,为 $O(N * M)$,其中 N 为 piles 长度, M 为 Piles 中最大的数(也就是解空间的最大值)。 观察到需要检测的解空间是个有序序列,应该想到可能能够使用二分来解决,而不是线性枚举。可以使用二分解决的关键和前面我们简化的二分问题并无二致,关键点在于如果速度 k 吃不完所有香蕉,那么所有小于等于 k 的解都可以被排除。 二分解决的关键在于: 明确解空间。 对于这道题来说, 解空间就是 [1,max(piles)]。 如何收缩解空间。关键点在于如果速度 k 吃不完所有香蕉,那么所有小于等于 k 的解都可以被排除。 综上,我们可以使用最左二分,即不断收缩右边界。 香蕉堆的香蕉个数上限是 10^9, 珂珂这也太能吃了吧? 关键点解析 二分查找模板 代码代码支持:Python,JavaScript Python Code: 1234567891011121314151617class Solution: def solve(self, piles, k): def possible(mid): t = 0 for pile in piles: t += (pile + mid - 1) // mid return t <= k l, r = 1, max(piles) while l <= r: mid = (l + r) // 2 if possible(mid): r = mid - 1 else: l = mid + 1 return l JavaScript Code: 12345678910111213141516171819202122232425262728function canEatAllBananas(piles, H, mid) { let h = 0; for (let pile of piles) { h += Math.ceil(pile / mid); } return h <= H;}/** * @param {number[]} piles * @param {number} H * @return {number} */var minEatingSpeed = function (piles, H) { let lo = 1, hi = Math.max(...piles); // [l, r) , 左闭右开的好处是如果能找到,那么返回 l 和 r 都是一样的,因为最终 l 等于 r。 while (lo <= hi) { let mid = lo + ((hi - lo) >> 1); if (canEatAllBananas(piles, H, mid)) { hi = mid - 1; } else { lo = mid + 1; } } return lo; // 不能选择hi}; 复杂度分析 时间复杂度:$O(max(N, N * logM))$,其中 N 为 piles 长度, M 为 Piles 中最大的数。 空间复杂度:$O(1)$ 最小灯半径(困难)题目描述123456789101112You are given a list of integers nums representing coordinates of houses on a 1-dimensional line. You have 3 street lights that you can put anywhere on the coordinate line and a light at coordinate x lights up houses in [x - r, x + r], inclusive. Return the smallest r required such that we can place the 3 lights and all the houses are lit up.Constraintsn ≤ 100,000 where n is the length of numsExample 1Inputnums = [3, 4, 5, 6]Output0.5ExplanationIf we place the lamps on 3.5, 4.5 and 5.5 then with r = 0.5 we can light up all 4 houses. 前置知识 排序 二分法 二分法思路本题和力扣 475. 供暖器 类似。 这道题的意思是给你一个数组 nums,让你在 [min(nums),max(nums)] 范围内放置 3 个灯,每个灯覆盖半径都是 r,让你求最小的 r。 之所以不选择小于 min(nums) 的位置和大于 max(nums) 的位置是因为没有必要。比如选取了小于 min(nums) 的位置 pos,那么选取 pos 一定不比选择 min(nums) 位置结果更优。 这道题的核心点还是一样的思维模型,即: 确定解空间。这里的解空间其实就是 r。不难看出 r 的下界是 0, 上界是 max(nums) - min(nums)。 没必要十分精准,只要不错过正确解即可,这个我们在前面讲过,这里再次强调一下。 对于上下界之间的所有可能 x 进行枚举(不妨从小到大枚举),检查半径为 x 是否可以覆盖所有,返回第一个可以覆盖所有的 x 即可。 注意到我们是在一个有序序列进行枚举,因此使用二分就应该想到。可使用二分的核心点在于:如果 x 不行,那么小于 x 的所有半径都必然不行。 接下来的问题就是给定一个半径 x,判断其是否可覆盖所有的房子。 判断其是否可覆盖就是所谓的能力检测,我定义的函数 possible 就是能力检测。 首先对 nums 进行排序,这在后面会用到。 然后从左开始模拟放置灯。先在 nums[0] + r 处放置一个灯,其可以覆盖 [0, 2 r]。由于 nums 已经排好序了,那么这个等可以覆盖到的房间其实就是 nums 中坐标小于等于 2 \\ r 所有房间,使用二分查找即可。对于 nums 右侧的所有的房间我们需要继续放置灯,采用同样的方式即可。 能力检测核心代码: 12345678910def possible(diameter): start = nums[0] end = start + diameter for i in range(LIGHTS): idx = bisect_right(nums, end) if idx >= N: return True start = nums[idx] end = start + diameter return False 由于我们想要找到满足条件的最小值,因此可直接套用最左二分模板。 代码代码支持:Python3 Python3 Code: 123456789101112131415161718192021222324252627class Solution: def solve(self, nums): nums.sort() N = len(nums) if N <= 3: return 0 LIGHTS = 3 # 这里使用的是直径,因此最终返回需要除以 2 def possible(diameter): start = nums[0] end = start + diameter for i in range(LIGHTS): idx = bisect_right(nums, end) if idx >= N: return True start = nums[idx] end = start + diameter return False l, r = 0, nums[-1] - nums[0] while l <= r: mid = (l + r) // 2 if possible(mid): r = mid - 1 else: l = mid + 1 return l / 2 复杂度分析 令 n 为数组长度。 时间复杂度:由于进行了排序, 因此时间复杂度大约是 $O(nlogn)$ 空间复杂度:取决于排序的空间消耗 778. 水位上升的泳池中游泳(困难)题目地址https://leetcode-cn.com/problems/swim-in-rising-water 题目描述123456789101112131415161718192021222324252627282930313233在一个 N x N 的坐标方格 grid 中,每一个方格的值 grid[i][j] 表示在位置 (i,j) 的平台高度。现在开始下雨了。当时间为 t 时,此时雨水导致水池中任意位置的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。你从坐标方格的左上平台 (0,0) 出发。最少耗时多久你才能到达坐标方格的右下平台 (N-1, N-1)?示例 1:输入: [[0,2],[1,3]]输出: 3解释:时间为 0 时,你位于坐标方格的位置为 (0, 0)。此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置示例 2:输入: [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]输出: 16解释:0 1 2 3 424 23 22 21 512 13 14 15 1611 17 18 19 2010 9 8 7 6最终的路线用加粗进行了标记。我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的提示:2 <= N <= 50.grid[i][j] 位于区间 [0, ..., N*N - 1] 内。 前置知识 DFS 二分 思路首先明确一下解空间。不难得出,解空间是[0, max(grid)],其中 max(grid) 表示 grid 中的最大值。 因此一个简单的思路是一个个试。 试试 a 可以不 试试 a+1 可以不 。。。 试试 x 是否可行就是能力检测。 实际上,如果 x 不可以,那么小于 x 的所有值都是不可以的,这正是本题的突破口。基于此,我们同样可使用讲义中的最左二分模板解决。 伪代码: 123456789def test(x): passwhile l <= r: mid = (l + r) // 2 if test(mid, 0, 0): r = mid - 1 else: l = mid + 1return l 这个模板会在很多二分中使用。比如典型的计数型二分,典型的就是计算小于等于 x 的有多少,然后根据答案更新解空间。 明确了这点,剩下要做的就是完成能力检测部分 (test 函数) 了。其实这个就是一个普通的二维网格 dfs,我们从 (0,0) 开始在一个二维网格中搜索,直到无法继续或达到 (N-1,N-1),如果可以达到 (N-1,N-1),我们返回 true,否则返回 False 即可。对二维网格的 DFS 不熟悉的同学可以看下我之前写的小岛专题 代码1234567891011121314151617181920212223242526class Solution: def swimInWater(self, grid: List[List[int]]) -> int: l, r = 0, max([max(vec) for vec in grid]) seen = set() def test(mid, x, y): if x > len(grid) - 1 or x < 0 or y > len(grid[0]) - 1 or y < 0: return False if grid[x][y] > mid: return False if (x, y) == (len(grid) - 1, len(grid[0]) - 1): return True if (x, y) in seen: return False seen.add((x, y)) ans = test(mid, x + 1, y) or test(mid, x - 1, y) or test(mid, x, y + 1) or test(mid, x, y - 1) return ans while l <= r: mid = (l + r) // 2 if test(mid, 0, 0): r = mid - 1 else: l = mid + 1 seen = set() return l 复杂度分析 时间复杂度:$O(NlogM)$,其中 M 为 grid 中的最大值, N 为 grid 的总大小。 空间复杂度:$O(N)$,其中 N 为 grid 的总大小。 计数二分计数二分和上面的思路已经代码都基本一致。 直接看代码会清楚一点: 12345678910def count_bs(nums, k): def count_not_greater(mid): pass l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 # 只有这里和最左二分不一样 if count_not_greater(mid) > k: r = mid - 1 else: l = mid + 1 return l 可以看出只是将 possible 变成了 count_not_greater,返回值变成了数字而已。 实际上,我们可以将上面的代码稍微改造一下,使得两者更像: 12345678910def count_bs(nums, k): def possible(mid, k): # xxx return cnt > k l, r = 0, len(A) - 1 while l <= r: mid = (l + r) // 2 if possible(mid, k): r = mid - 1 else: l = mid + 1 return l 是不是基本一致了? 由于和上面基本一致, 因此这里直接推荐一个题目,大家用我的思路练习一下,看看我的技巧灵不灵。 第 k 小的距离对 前缀和二分前面说了:如果数组全是正的,那么其前缀和就是一个严格递增的数组,基于这个特性,我们可以在其之上做二分。类似的有单调栈/队列。这种题目类型很多,为了节省篇幅就不举例说明了。提出前缀和二分的核心的点在于让大家保持对有序序列的敏感度。 插入排序二分除了上面的前缀和之外,我们还可以自行维护有序序列。一般有两种方式: 直接对序列排序。 代码表示: 123nums.sort()bisect.bisect_left(nums, x) # 最左二分bisect.bisect_right(nums, x) # 最右二分 遍历过程维护一个新的有序序列,有序序列的内容为已经遍历过的值的集合。 比如无序数组 [3,2,10,5],遍历到索引为 2 的项(也就是值为 10 的项)时,我们构建的有序序列为 [2,3,10]。 注意我描述的是有序序列,并不是指数组,链表等具体的数据结构。而实际上,这个有序序列很多情况下是平衡二叉树。后面题目会体现这一点。 代码表示: 123d = SortedList()for a in A: d.add(a) # 将 a 添加到 d,并维持 d 中数据有序 上面代码的 d 就是有序序列。 理论知识到此为止,接下来通过一个例子来说明。 327. 区间和的个数(困难)题目地址https://leetcode-cn.com/problems/count-of-range-sum 题目描述1234567891011121314151617181920212223242526272829给定一个整数数组 nums 。区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。请你以下标 i (0 <= i <= nums.length )为起点,元素个数逐次递增,计算子数组内的元素和。当元素和落在范围 [lower, upper] (包含 lower 和 upper)之内时,记录子数组当前最末元素下标 j ,记作 有效 区间和 S(i, j) 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 有效 区间和的个数。 注意:最直观的算法复杂度是 O(n2) ,请在此基础上优化你的算法。 示例:输入:nums = [-2,5,-1], lower = -2, upper = 2,输出:3解释:下标 i = 0 时,子数组 [-2]、[-2,5]、[-2,5,-1],对应元素和分别为 -2、3、2 ;其中 -2 和 2 落在范围 [lower = -2, upper = 2] 之间,因此记录有效区间和 S(0,0),S(0,2) 。下标 i = 1 时,子数组 [5]、[5,-1] ,元素和 5、4 ;没有满足题意的有效区间和。下标 i = 2 时,子数组 [-1] ,元素和 -1 ;记录有效区间和 S(2,2) 。故,共有 3 个有效区间和。 提示:0 <= nums.length <= 10^4 思路题目很好理解。 由前缀和的性质知道:区间 i 到 j(包含)的和 sum(i,j) = pre[j] - pre[i-1],其中 pre[i] 为数组前 i 项的和 0 <= i < n。 但是题目中的数字可能是负数,前缀和不一定是单调的啊?这如何是好呢?答案是手动维护前缀和的有序性。 比如 [-2,5,-1] 的前缀和 为 [-2,3,2],但是我们可以将求手动维护为 [-2,2,3],这样就有序了。但是这丧失了索引信息,因此这个技巧仅适用于无需考虑索引,也就是不需要求具体的子序列,只需要知道有这么一个子序列就行了,具体是哪个,我们不关心。 比如当前的前缀和是 cur,那么前缀和小于等于 cur - lower 有多少个,就说明以当前结尾的区间和大于等于 lower 的有多少个。类似地,前缀和小于等于 cur - upper 有多少个,就说明以当前结尾的区间和大于等于 upper 的有多少个。 基于这个想法,我们可使用二分在 $logn$ 的时间快速求出这两个数字,使用平衡二叉树代替数组可使得插入的时间复杂度降低到 $O(logn)$。Python 可使用 SortedList 来实现, Java 可用 TreeMap 代替。 代码123456789from sortedcontainers import SortedListclass Solution: def countRangeSum(self, A: List[int], lower: int, upper: int) -> int: ans, pre, cur = 0, [0], 0 for a in A: cur += a ans += pre.bisect_right(cur - lower) - pre.bisect_left(cur - upper) pre.add(cur) return ans 复杂度分析 令 n 为数组长度。 时间复杂度:$O(nlogn)$ 空间复杂度:$O(nlogn)$ 493. 翻转对(困难)题目地址https://leetcode-cn.com/problems/reverse-pairs/ 题目描述1234567891011121314151617181920给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。你需要返回给定数组中的重要翻转对的数量。示例 1:输入: [1,3,2,3,1]输出: 2示例 2:输入: [2,4,3,5,1]输出: 3注意:给定数组的长度不会超过50000。输入数组中的所有数字都在32位整数的表示范围内。 前置知识 二分 公司 暂无 思路我们可以一边遍历一边维护一个有序序列 d,其中 d 为已经遍历过的值的集合。对于每一个位置 0 <= i < n,我们统计 d 中大于 2 * A[i] 的个数,这个个数就是题目要求的翻转对。这里的关键在于 d 中的值是比当前索引小的全部值。 我们当然可以线性遍历 d,求出个数。一个更好的方法是在遍历的同时维持 d 是有序的,这样我们就可以用二分了。和上面题目一样,使用平衡二叉树代替数组可使得插入的时间复杂度降低到 $O(logn)$。 关键点 插入排序二分 代码 语言支持:Python3 Python3 Code: 123456789from sortedcontainers import SortedListclass Solution: def reversePairs(self, A: List[int]) -> int: d = SortedList() ans = 0 for a in A: ans += len(d) - d.bisect_right(2*a) d.add(a) return ans 复杂度分析 令 n 为数组长度。 时间复杂度:$O(nlogn)$ 空间复杂度:$O(n)$ 小结四个应用讲了两种构造有序序列的方式,分别是前缀和,插入排序,插入排序的部分其实也可以看下我之前写的最长上升子序列系列,那里面的贪心解法就是自己构造有序序列再二分的。 另外理论上单调栈/队列也是有序的,也可是用来做二分,但是相关题目太少了,因此大家只要保持对有序序列的敏感度即可。 能力检测二分很常见,不过其仅仅是将普通二分的 if 部分改造成了函数而已。而对于计数二分,其实就是能力检测二分的特例,只不过其太常见了,就将其单独提取出来了。 另外,有时候有序序列也会给你稍微变化一种形式。比如二叉搜索树,大家都知道可以在 $logn$ 的时间完成查找,这个查找过程本质也是二分。二叉查找树有有序序列么?有的!二叉查找树的中序遍历恰好就是一个有序序列。因此如果一个数比当前节点值小,一定在左子树(也就是有序序列的左侧),如果一个数比当前节点值大,一定在右子树(也就是有序序列的右侧)。 总结本文主要讲了两种二分类型:最左和最右,模板已经给大家了,大家只需要根据题目调整解空间和判断条件即可。关于四种应用更多的还是让大家理解二分的核心折半。表面上来看,二分就是对有序序列的查找。其实不然,只不过有序序列很容易做二分罢了。因此战术上大家保持对有序序列的敏感度,战略上要明确二分的本质是折半,核心在于什么时候将哪一半折半。 一个问题能否用二分解决的关键在于检测一个值的时候是否可以排除解空间中的一半元素。比如我前面反复提到的如果 x 不行,那么解空间中所有小于等于 x 的值都不行。 对于简单题目,通常就是给你一个有序序列,让你在上面找满足条件的位置。顶多变化一点,比如数组局部有序,一维变成二维等。对于这部分可以看下我写的91 算法 - 二分查找讲义 中等题目可能需要让你自己构造有序序列。 困难题则可能是二分和其他专题的结合,比如上面的 778. 水位上升的泳池中游泳(困难),就是二分和搜索(我用的是 DFS)的结合。 以上就是本文的全部内容了, 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。我是 lucifer,维护西湖区最好的算法题解,Github 超 40K star 。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 另外我整理的 1000 多页的电子书已限时免费下载,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/categories/二分/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/tags/二分/"}]},{"title":"力扣刷题插件近期更新盘点","slug":"leetcode-cheat-update-1","date":"2021-03-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.826Z","comments":true,"path":"2021/03/16/leetcode-cheat-update-1/","link":"","permalink":"https://lucifer.ren/blog/2021/03/16/leetcode-cheat-update-1/","excerpt":"刷题插件更新日志。","text":"刷题插件更新日志。 手撕算法系列插件增加了手撕算法系列。那么作为第一篇手撕算法上线的就是我们的排序算法。 排序算法目前我提供了五种排序算法,它们分别是: 归并排序(推荐!其他排序方法都不推荐在竞赛中使用) 快速排序 插入排序 选择排序 冒泡排序 每一种排序都对数组和链表两种数据结构进行了支持。 同时,为了对新手更加优化, 对于归并排序和快速排序都提供了乞丐版。乞丐版的代码更容易理解,对于你看清算法的主脉络至关重要。 之后,力扣加加还会为大家带来更多的手撕算法,敬请期待吧!如果你有什么想看的内容,也可以直接和我讲,我会尽量满足大家的。 力扣官方题解配色刷题插件更新新功能啦。本期新增“力扣官方题解”主题。效果图: 学习路线增加二分二分的两种类型,以及四种技巧都包含在内了哦~ 动态规划接下来会进一步完善,比如增加区间二分等。 修复无法复制测试用例 bug力扣官网更新之后和我使用的 antd 的 message 组件冲突,导致代码无法正常运行,部分功能直接失效。目前紧急修复,直接去掉了 antd 的 message 组件。 目前复制测试用例 后并不会弹层提示了(因为 message 组件被我删除了 ^_^)。 插件获取方式公众号力扣加加后台回复插件。 如果你已经了在线版插件只需要等待更新即可,如果按照了离线版,那么可以去仓库下载最新的 crx(仓库地址也是公众号回复插件获取)。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"},{"name":"刷题","slug":"刷题","permalink":"https://lucifer.ren/blog/tags/刷题/"}]},{"title":"春招冲冲冲(钉钉+腾讯)","slug":"school-02","date":"2021-03-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.796Z","comments":true,"path":"2021/03/11/school-02/","link":"","permalink":"https://lucifer.ren/blog/2021/03/11/school-02/","excerpt":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是钉钉和腾讯。来看看这两家的算法题难度几何吧! ​","text":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是钉钉和腾讯。来看看这两家的算法题难度几何吧! ​ 视频地址:https://www.bilibili.com/video/BV1Qy4y187Em/ 钉钉 比较版本号(力扣题号 165.比较版本号) 一次遍历即可,唯一需要注意的是补全再比较(逻辑补全即可,并不一定需要物理上真的去补全),时间复杂度 $O(m + n)$,其中 m 和 n 分别为两个版本号的长度 随机字符串生成(随机生成一个长度为 8 的字符串,要求只能是小写字母和数字,字母和数字可重复,但是生成的随机字符串不能重复) 随机生成一个长度为 8 的字符并将其存到哈希表中,下次生成后判断是否已经在哈希表中了。如果存在,说明之前生成过了,继续生成。注意这种算法存在一直拒绝的可能,代码会无限循环。 日志上报(实现效果:没有数据不上报,有数据每 100ms 批量上报一次) 维护一个窗口,当窗口内有数据才触发请求批量上传,一个窗口的长度为 100m(可能大于) 内的所有请求 腾讯 字符串反转 头尾双指针,不断交换两个指针的字符即可。 链表的倒数第 k 个数 快慢双指针典型题目。 设计求一个数的 n 次开方 典型的二分题目,不会的建议看下我的二分讲义。我的刷题仓库或者公众号搜 二分 就行。 LRU 算法 稍微有点难度了,这个题很常见,难度不小,建议刷。我之前写过题解了,直接甩给大家吧 146. LRU 缓存机制 我给了 JS, Go, PHP, Python3 四种语言,有你的菜么? 手撕一下,就是一个小车给定坐标位置,和当前面朝方向(NSWE),再输入前进转向情况和前进步数,输出小车的坐标位置和面朝方向。 没啥难度,直接模拟。 链表相加 链表和数组本质没有不同,只是具体操作不一样。因此掌握链表基本操作就行了。链表基本操作有哪些?需要注意什么?我的链表专题都给大家总结好了,建议阅读。 leetcode 1567 乘积为正数的最长子数组长度。 题目是:给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。请你返回乘积为正数的最长子数组长度。 这道题是求连续的乘积为正数的最长子数组长度。这里需要一个小学知识,两个符号相同的相乘为正数,两个符号不同的数相乘为负数(先不考虑 0)。 于是直接使用一维 DP + 一层循环即可。 定义状态 positive[i] 为 nums[i] 结尾的乘积为正数的最长子数组长度,negative[i] 为 nums[i] 结尾的乘积为负数的最长子数组长度,于是答案就是 max(positive)。 接下来,遍历 nums,对 nums[i] 的不同取值分类讨论即可: nums[i] > 0 positive[i]=positive[i−1]+1 negative[i]=\\left\\{ \\begin{aligned} negative[i-1] + 1 & & negative[i-1] > 0 \\\\ 0 & & negative[i-1] = 0 \\\\ \\end{aligned} \\right. nums[i] < 0 negative[i]=positive[i−1]+1 positive[i]=\\left\\{ \\begin{aligned} negative[i-1] + 1 & & negative[i-1] > 0 \\\\ 0 & & negative[i-1] = 0 \\\\ \\end{aligned} \\right. nums[i] == 0 negative[i]=positive[i] = 0状态定义一般两种套路: 使用一个二维数组,定义 dp[i][0] 和 定义 dp[i][1] 使用两个一维数组,定义 dp1[i] 和 dp2[i] 两种方式思路一样,只是实操不一样而已。我个人倾向于第二种。比如股票题,我就喜欢定义一个 buy 和 一个 sell 数组。再比如摆动数组,我就喜欢定义一个 up 和 一个 down 数组。 另外如果题目没有限定连续,则需要两层循环和一维 DP(滚动数组优化)。 总结我个人觉得算法题难度是中等,都非常常规,没有什么难以读懂的题目或者冷门知识。 另外我组建了春招群,大家面试遇到不会的题都可以问哦。想进群的可在公众号力扣加加后台回复春招获取小秘书的微信,通过之后再次回复春招入群。 最后祝大家 offer 多多。","categories":[{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/categories/春招/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/tags/春招/"}]},{"title":"几乎刷完了力扣所有的二分题,我发现了这些东西。。。(上)","slug":"binary-search-1","date":"2021-03-07T16:00:00.000Z","updated":"2023-01-05T12:24:49.621Z","comments":true,"path":"2021/03/08/binary-search-1/","link":"","permalink":"https://lucifer.ren/blog/2021/03/08/binary-search-1/","excerpt":"前言 大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下)","text":"前言 大家好,我是 lucifer。今天给大家带来的是《二分》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(上) 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(下) 本专题预计分两部分两进行。第一部分主要讲述基本概念 和 一个中心。有了这些基础知识之后,第二部分我们继续学习两种二分类型 和四大应用。 本文内容已经同步到我的刷题插件的 RoadMap 中,结合刷题插件食用味道更佳哦~ 插件的获取方式可以在我的公众号力扣加加中回复插件查看。 如果觉得文章有用,请点赞留言转发一下,让我有动力继续做下去。 前言为了准备这个专题,我不仅肝完了力扣的所有二分题目,还肝完了另外一个 OJ 网站 - Binary Search 的所有二分题目,一共100 多道。大家看完如果觉得有用,可以通过点赞转发的方式告诉我,如果喜欢的人多,我继续尽快出下篇哦~ 二分查找又称折半搜索算法。 狭义地来讲,二分查找是一种在有序数组查找某一特定元素的搜索算法。这同时也是大多数人所知道的一种说法。实际上, 广义的二分查找是将问题的规模缩小到原有的一半。类似的,三分法就是将问题规模缩小为原来的 1/3。 本文给大家带来的内容则是狭义地二分查找,如果想了解其他广义上的二分查找可以查看我之前写的一篇博文 从老鼠试毒问题来看二分法 尽管二分查找的基本思想相对简单,但细节可以令人难以招架 … — 高德纳 当乔恩·本特利将二分搜索问题布置给专业编程课的学生时,百分之 90 的学生在花费数小时后还是无法给出正确的解答,主要因为这些错误程序在面对边界值的时候无法运行,或返回错误结果。1988 年开展的一项研究显示,20 本教科书里只有 5 本正确实现了二分搜索。不仅如此,本特利自己 1986 年出版的《编程珠玑》一书中的二分搜索算法存在整数溢出的问题,二十多年来无人发现。Java 语言的库所实现的二分搜索算法中同样的溢出问题存在了九年多才被修复。 可见二分查找并不简单, 本文就试图带你走近 ta,明白 ta 的底层逻辑,并提供模板帮助大家写书 bug free 的二分查找代码。看完讲义后建议大家结合 LeetCode Book 二分查找 练习一下。 基本概念首先我们要知道几个基本概念。这些概念对学习二分有着很重要的作用,之后遇到这些概念就不再讲述了,默认大家已经掌握。 解空间解空间指的是题目所有可能的解构成的集合。比如一个题目所有解的可能是 1,2,3,4,5,但具体在某一种情况只能是其中某一个数(即可能是 1,2,3,4,5 中的一个数)。那么这里的解空间就是 1,2,3,4,5 构成的集合,在某一个具体的情况下可能是其中任意一个值,我们的目标就是在某个具体的情况判断其具体是哪个。如果线性枚举所有的可能,就枚举这部分来说时间复杂度就是 $O(n)$。 举了例子: 如果让你在一个数组 nums 中查找 target,如果存在则返回对应索引,如果不存在则返回 -1。那么对于这道题来说其解空间是什么? 很明显解空间是区间 [-1, n-1],其中 n 为 nums 的长度。 需要注意的是上面题目的解空间只可能是区间 [-1,n-1] 之间的整数。而诸如 1.2 这样的小数是不可能存在的。这其实也是大多数二分的情况。 但也有少部分题目解空间包括小数的。如果解空间包括小数,就可能会涉及到精度问题,这一点大家需要注意。 比如让你求一个数 x 的平方根,答案误差在 $10^-6$ 次方都认为正确。这里容易知道其解空间大小可定义为 [1,x](当然可以定义地更精确,之后我们再讨论这个问题),其中解空间应该包括所有区间的实数,不仅仅是整数而已。这个时候解题思路和代码都没有太大变化,唯二需要变化的是: 更新答案的步长。 比如之前的更新是 l = mid + 1,现在可能就不行了,因此这样可能会错过正确解,比如正确解恰好就在区间 [mid,mid+1] 内的某一个小数。 判断条件时候需要考虑误差。由于精度的问题,判断的结束条件可能就要变成 与答案的误差在某一个范围内。 对于搜索类题目,解空间一定是有限的,不然问题不可解。对于搜索类问题,第一步就是需要明确解空间,这样你才能够在解空间内进行搜索。这个技巧不仅适用于二分法,只要是搜索问题都可以使用,比如 DFS,BFS 以及回溯等。只不过对于二分法来说,明确解空间显得更为重要。如果现在还不理解这句话也没关系,看完本文或许你就理解了。 定义解空间的时候的一个原则是: 可以大但不可以小。因为如果解空间偏大(只要不是无限大)无非就是多做几次运算,而如果解空间过小则可能错失正确解,导致结果错误。比如前面我提到的求 x 的平方根,我们当然可以将解空间定义的更小,比如定义为 [1,x/2],这样可以减少运算的次数。但如果设置地太小,则可能会错过正确解。这是新手容易犯错的点之一。 有的同学可能会说我看不出来怎么办呀。我觉得如果你实在拿不准也完全没有关系,比如求 x 的平方根,就可以甚至为 [1,x],就让它多做几次运算嘛。我建议你给上下界设置一个宽泛的范围。等你对二分逐步了解之后可以卡地更死一点。 序列有序我这里说的是序列,并不是数组,链表等。也就是说二分法通常要求的序列有序,不一定是数组,链表,也有可能是其他数据结构。另外有的序列有序题目直接讲出来了,会比较容易。而有些则隐藏在题目信息之中。乍一看,题目并没有有序关键字,而有序其实就隐藏在字里行间。比如题目给了数组 nums,并且没有限定 nums 有序,但限定了 nums 为非负。这样如果给 nums 做前缀和或者前缀或(位运算或),就可以得到一个有序的序列啦。 更多技巧在四个应用部分展开哦。 虽然二分法不意味着需要序列有序,但大多数二分题目都有有序这个显著特征。只不过: 有的是题目直接限定了有序。这种题目通常难度不高,也容易让人想到用二分。 有的是需要你自己构造有序序列。这种类型的题目通常难度不低,需要大家有一定的观察能力。 比如Triple Inversion。题目描述如下: 12345678910Given a list of integers nums, return the number of pairs i < j such that nums[i] > nums[j] * 3.Constraints: n ≤ 100,000 where n is the length of numsExample 1Input:nums = [7, 1, 2]Output:2Explanation:We have the pairs (7, 1) and (7, 2) 这道题并没有限定数组 nums 是有序的,但是我们可以构造一个有序序列 d,进而在 d 上做二分。代码: 12345678910class Solution: def solve(self, A): d = [] ans = 0 for a in A: i = bisect.bisect_right(d, a * 3) ans += len(d) - i bisect.insort(d, a) return ans 如果暂时不理解代码也没关系,大家先留个印象,知道有这么一种类型题即可,大家可以看完本章的所有内容(上下两篇)之后再回头做这道题。 极值类似我在堆专题 提到的极值。只不过这里的极值是静态的,而不是动态的。这里的极值通常指的是求第 k 大(或者第 k 小)的数。 堆的一种很重要的用法是求第 k 大的数,而二分法也可以求第 k 大的数,只不过二者的思路完全不同。使用堆求第 k 大的思路我已经在前面提到的堆专题里详细解释了。那么二分呢?这里我们通过一个例子来感受一下:这道题是 Kth Pair Distance,题目描述如下: 123456789101112131415161718192021Given a list of integers nums and an integer k, return the k-th (0-indexed) smallest abs(x - y) for every pair of elements (x, y) in nums. Note that (x, y) and (y, x) are considered the same pair.Constraints:n ≤ 100,000 where n is the length of numsExample 1Input:nums = [1, 5, 3, 2]k = 3Output:2Explanation:Here are all the pair distances:abs(1 - 5) = 4abs(1 - 3) = 2abs(1 - 2) = 1abs(5 - 3) = 2abs(5 - 2) = 3abs(3 - 2) = 1Sorted in ascending order we have [1, 1, 2, 2, 3, 4]. 简单来说,题目就是给的一个数组 nums,让你求 nums 第 k 大的任意两个数的差的绝对值。当然,我们可以使用堆来做,只不过使用堆的时间复杂度会很高,导致无法通过所有的测试用例。这道题我们可以使用二分法来降维打击。 对于这道题来说,解空间就是从 0 到数组 nums 中最大最小值的差,用区间表示就是 [0, max(nums) - min(nums)]。明确了解空间之后,我们就需要对解空间进行二分。对于这道题来说,可以选当前解空间的中间值 mid ,然后计算小于等于这个中间值的任意两个数的差的绝对值有几个,我们不妨令这个数字为 x。 如果 x 大于 k,那么解空间中大于等于 mid 的数都不可能是答案,可以将其舍弃。 如果 x 小于 k,那么解空间中小于等于 mid 的数都不可能是答案,可以将其舍弃。 如果 x 等于 k,那么 mid 就是答案。 基于此,我们可使用二分来解决。这种题型,我总结为计数二分。我会在后面的四大应用部分重点讲解。 代码: 1234567891011121314151617181920class Solution: def solve(self, A, k): A.sort() def count_not_greater(diff): i = ans = 0 for j in range(1, len(A)): while A[j] - A[i] > diff: i += 1 ans += j - i return ans l, r = 0, A[-1] - A[0] while l <= r: mid = (l + r) // 2 if count_not_greater(mid) > k: r = mid - 1 else: l = mid + 1 return l 如果暂时不理解代码也没关系,大家先留个印象,知道有这么一种类型题即可,大家可以看完本章的所有内容(上下两篇)之后再回头做这道题。 一个中心二分法的一个中心大家一定牢牢记住。其他(比如序列有序,左右双指针)都是二分法的手和脚,都是表象,并不是本质,而折半才是二分法的灵魂。 前面已经给大家明确了解空间的概念。而这里的折半其实就是解空间的折半。 比如刚开始解空间是 [1, n](n 为一个大于 n 的整数)。通过某种方式,我们确定 [1, m] 区间都不可能是答案。那么解空间就变成了 (m,n],持续此过程知道解空间变成平凡(直接可解)。 注意区间 (m,n] 左侧是开放的,表示 m 不可能取到。 显然折半的难点是根据什么条件舍弃哪一步部分。这里有两个关键字: 什么条件 舍弃哪部分 几乎所有的二分的难点都在这两个点上。如果明确了这两点,几乎所有的二分问题都可以迎刃而解。幸运的是,关于这两个问题的答案通常都是有限的,题目考察的往往就是那几种。这其实就是所谓的做题套路。关于这些套路,我会在之后的四个应用部分给大家做详细介绍。 二分法上篇小结上篇主要就是带大家了解几个概念,这些概念对做题极为重要,请务必掌握。接下来讲解了二分法的中心 - 折半,这个中心需要大家做任何二分都要放到脑子中。 如果让我用一句话总结二分法,我会说二分法是一种让未知世界无机可乘的算法。即二分法无论如何我们都可以舍弃一半解,也就是无论如何都可以将解空间砍半。难点就是上面提到的两点:什么条件 和 舍弃哪部分。这是二分法核心要解决的问题。 以上就是《二分专题(上篇)》的所有内容。如果觉得文章有用,请点赞留言转发一下,让我有动力继续出下集。 下集预告上集介绍的是基本概念。下一集我们介绍两种二分的类型以及四种二分的应用。 下集目录: 两种类型 最左插入 最右插入 四大应用 能力检测二分 前缀和二分 插入排序二分(不是你理解的插入排序哦) 计数二分 其中两种类型(最左和最右插入)主要解决的的是:解空间已经明确出来了,如何用代码找出具体的解。 而四大应用主要解决的是:如何构造解空间。更多的情况则是如何构建有序序列。 这两部分都是实操性很强的内容,在理解这两部分内容的同时,请大家务必牢记一个中心折半。那我们下篇见喽~","categories":[{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/categories/二分/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"二分","slug":"二分","permalink":"https://lucifer.ren/blog/tags/二分/"}]},{"title":"春招冲冲冲","slug":"school-01","date":"2021-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.894Z","comments":true,"path":"2021/03/04/school-01/","link":"","permalink":"https://lucifer.ren/blog/2021/03/04/school-01/","excerpt":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是虾皮。来看看虾皮的算法题难度几何吧! ​","text":"春招已经开始了。 你是不是已经开始准备了呢?为了帮助大家获得更好的 offer,lucifer 开辟了春招冲冲冲栏目。 今天我们的猎物是虾皮。来看看虾皮的算法题难度几何吧! ​ 视频地址:https://www.bilibili.com/video/BV1bp4y1H7KT/ 视频中涉及到的知识有: dfs 回溯 滑动窗口 排序 平衡二叉树 二分 我个人觉得算法题难度是中等,都非常常规,没有什么难以读懂的题目或者冷门知识。 另外我组建了春招群,大家面试遇到不会的题都可以问哦。 如果显示满了或者过期可在公众号后台回复春招获取小秘书的微信,通过之后再次回复春招入群。 最后祝大家 offer 多多。","categories":[{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/categories/春招/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"春招","slug":"春招","permalink":"https://lucifer.ren/blog/tags/春招/"}]},{"title":"91 天学算法第三期视频会议总结","slug":"91meeting-season-3-1","date":"2021-02-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.888Z","comments":true,"path":"2021/03/01/91meeting-season-3-1/","link":"","permalink":"https://lucifer.ren/blog/2021/03/01/91meeting-season-3-1/","excerpt":"这是 91 天学算法第三期视频会议的一个文字版总结。想要参加第三期的小伙伴可在公众号后台回复 91 查看参与方式。 大家好,我是 lucifer。前几天给 91 的学员开了一场视频会议,关于目前学习的情况,西法给大家的学习建议以及下期的规划做了简单的讨论。 ​","text":"这是 91 天学算法第三期视频会议的一个文字版总结。想要参加第三期的小伙伴可在公众号后台回复 91 查看参与方式。 大家好,我是 lucifer。前几天给 91 的学员开了一场视频会议,关于目前学习的情况,西法给大家的学习建议以及下期的规划做了简单的讨论。 ​ 时间 2021-02-28 10:00 参与人员 91 第三期的部分学员 在线观看 https://www.bilibili.com/video/BV1qK4y1J7DD/ 会议主题基础篇回顾基础篇内容如下: 【91 算法-基础篇】01.数组,栈,队列 【91 算法-基础篇】02.链表 【91 算法-基础篇】03.树 【91 算法-基础篇】04.哈希表 【91 算法-基础篇】05.双指针 【91 算法-基础篇】06.图(加餐) 【待定】模拟和枚举(加餐) 除了加餐,其他都会给大家每日一题的练习时间。对于加餐来说,目前还没有完结,等到完结之后会第一时间在群里通知。 对于基础篇的内容需要大家重点掌握的是队列和栈。理由如下: 数组没啥好讲的,大家用的太多了,唯一需要注意的是各种操作的复杂度。 链表和树我写的两篇专题已经非常清楚了,几乎涉及了所有考点。大家如果对这两部分内容没信息,建议看看我公众号的几乎刷完了系列。 哈希表从战略上考察点基本就是空间换时间,战术上就是统计频率,计算最近或者最远,分桶等几个操作而已,具体可看讲义。 双指针我们会在专题篇分两个小专题讲解,分别是 二分法 和 滑动窗口,因此基础篇没太搞懂也不要紧。 通过几个题目,讲解更有如何有效率的解题以下是视频中提到的题目,每个题目都有一定代表性。 https://binarysearch.com/problems/Split-List-to-Minimize-Largest-Sum 第一题主要告诉大家做题的时候一定要结合讲义,我们出的题通常都是和讲义中的一个或者多个知识点有关。出题的目的就是强化理解讲义中的内容。因此如果看完题目,你完全不知道讲义中有挂内容,很有可能是你自己对讲义的理解不到位。这个时候需要再次阅读讲义或者求助群友和群主。否则你的进步会很慢,这也是一些同学向我反馈跟了很久却没有效果的最主要原因。 https://binarysearch.com/problems/Kth-Largest-Pair-Product/editorials/3567227 这道题其实是想告诉大家: 2.1. 如果一道题没思路。可以通过最最暴力的解法入手,然后找算法瓶颈,根据瓶颈使用适当的数据结构和算法进行优化2.2. 讲义中提到的点,一定要牢记。比如我们讲义中提到了固定小顶堆求第 k 大的数,这道题就是求第 k 大的数,你就应该想到。作为新手我对你们的期望是要想到,不管最终能不能用,想到是必须的。 https://leetcode-cn.com/problems/widest-vertical-area-between-two-points-containing-no-points/ 这道题主要告诉大家题目如果看不懂, 那练习再多都没用,建议大家通过几个文字很多的题目或者带图的题目练练手,提供阅读理解能力。 算法面试题(非编程题)epoll 以一个具体的例子: 《epoll 是如何优化我们的程序的?》 为例,讲解算法面试中问答题。如果你不想八股文式面试, 那么就需要大家对基本的数据结构与算法有很深的理解。而这恰好是基础篇的内容。 下期(专题篇)预告专题篇目录: 二分专题 滑动窗口专题 位运算专题 搜索(BFS、DFS、回溯)专题 背包 动态规划 分治 贪心 这部分的重点是:搜索 和 动态规划(含背包)。为什么这两个重要? 搜索篇重要的原因是其范围太广了,涉及到的知识很多。 动态规划不仅容易考,而且大家普遍认为比较难。(我承认它要比搜索篇难) 而: 二分法,滑动窗口相信你看了我的讲义,不会有太大问题。 位运算则考察频率不那么高,大家可以将优先级适当放低。 分治和贪心算法下限很低,基本看了我的讲义跟着做做题入门是可以的。但是要想精通可是相当难的,但是如果你掌握了搜索篇以及动态规划,那么对你掌握这两个专题会有极大的帮助,这也是为啥我特别强调要掌握搜索篇和动态规划的原因。 插件有什么用? 插件源码:https://github.com/leetcode-pp/leetcode-cheat 插件功能介绍: https://lucifer.ren/blog/2020/08/16/leetcode-cheat/ 插件获取方式:公众号《力扣加加》回复插件。 很多人不会用我的插件功能,尤其是复制测试用例。他们好奇为啥官方有了运行内置用例功能,我还弄个复制所有测试用例的功能。这是因为官方的是运行内置用例,并不会自动复制到自定义用例的输入框中。 这有什么问题呢?比如我执行了内置的用例报错了,报错的用例是 [1,2,3,4],而这个用例不在内置用例中。我肯定要调整代码,调整完毕后继续运行。而我这个时候期望的是运行所有的测试用例 和 [1,2,3,4]这个上次出错的用例。因为我可能为了改这个而导致其他本来可以过的用例现在过不了。 如果这个时候又挂在 [3,2,1] 这个用例。我肯定希望运行所有内置的用例和 [1,2,3,4] 以及 [3,2,1]。而这是官方的功能不具备的。如果想实现这个效果,你需要先点击执行内置用例,再点击执行自定义用例。而我的复制所有内置用例可以很好的解决这个问题。 答疑回答了几个比较具有代表性的问题。有一个问题是:我想往 java 后端发展,求推荐学习路线。 答:关于 java,其实我并不权威,尽管我是 java 入行,但毕竟几年不碰了。但是任何计算机相关岗位的学习我都建议先学基础,比如网络,算法。然后学操作系统和汇编(操作系统和汇编用到了大量数据结构与算法)。接下来可以学习编程语言的语法,语法熟悉就是语言内置的库,然后是三方库,然后是运行时,比如 java 就是 jvm。最后从大的方向将知识串起来。比如你可以从一个经典的问题浏览器输入 url 发生了什么入手来验证自己知识的掌握程度。 其他问题可看视频。 以上就是本文的全部内容了, 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。我是 lucifer,维护西湖区最好的算法题解,Github 超 40K star 。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"如何自动同步博客到 Github 主页?","slug":"github-blog-auto","date":"2021-02-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.568Z","comments":true,"path":"2021/02/27/github-blog-auto/","link":"","permalink":"https://lucifer.ren/blog/2021/02/27/github-blog-auto/","excerpt":"前言Github 支持通过创建同名仓库的形式自定义主页。比如我的 Github 用户名是 azl397985856,那么新建一个名为 azl397985856 的仓库即可。接下来你可以通过修改此仓库的 README 文件来自定义 Github 主页。也就是说,你想要自定义主页就新建一个同名仓库并修改 README 就行了。 修改 README 能玩出什么花样呢?请接着往下看。","text":"前言Github 支持通过创建同名仓库的形式自定义主页。比如我的 Github 用户名是 azl397985856,那么新建一个名为 azl397985856 的仓库即可。接下来你可以通过修改此仓库的 README 文件来自定义 Github 主页。也就是说,你想要自定义主页就新建一个同名仓库并修改 README 就行了。 修改 README 能玩出什么花样呢?请接着往下看。 装修效果先上一下我的装修效果: 开始动手添加数据统计上图的那几个 Github 数据统计以及奖杯使用的是一个外部服务。想要显示哪个就添加相应代码即可: 数据统计: 1<img src=\"https://github-readme-stats.vercel.app/api?username=azl397985856&show_icons=true\" alt=\"logo\" height=\"160\" align=\"right\" style=\"margin: 5px; margin-bottom: 20px;\" /> 注意将 username 改成自己的用户名哦(下面也是一样,不再赘述),不然就显示的 lucifer 我的信息啦。 奖杯: 1<img src=\"https://github-profile-trophy.vercel.app/?username=azl397985856&theme=flat&column=7\" alt=\"logo\" height=\"160\" align=\"center\" style=\"margin: auto; margin-bottom: 20px;\" /> 自动更新博客如上图我的装修主页,其中博客的文章列表不是写死的,而是每隔一个小时定时读取我的博客 内容,并提取前 5 篇文章。 如果你也想要这个功能,就在 README 中添加如下代码即可: 1234## 📕 Latest Blog Posts<!-- BLOG-POST-LIST:START --><!-- BLOG-POST-LIST:END --> 之后读取的博客列表会填充在两个注释之间,也就是说你可以通过改变注释的位置,将其放到页面任意位置。 为了实现每个小时定时更新的功能,我们可以使用 Github Action 的定时任务来实现。 具体操作步骤如下: 接下来将如下内容复制粘贴进去: 123456789101112131415161718name: Blog Postson: # Run workflow automatically schedule: # Runs every hour, on the hour - cron: \"0 * * * *\"jobs: update-readme-with-blog: name: Update this repo's README with latest blog posts runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: gautamkrishnar/blog-post-workflow@master with: # comma-separated list of RSS feed urls feed_list: \"https://lucifer.ren/blog/atom.xml\" 注意:这里的 cron: "0 * * * *" 的意思是每个小时进行一次,并且是每个小时的 0 分。 因为你需要等到下一个整点才能看到效果,有时候 Github 会有延时,晚几分钟也正常,大家不要着急,耐心等待即可。 请将 feed_list 替换为你自己的 RSS 订阅地址。如果有多个订阅地址,则用英文半角逗号分割。 如果你的博客没有 RSS 或者你不知道自己的 RSS 地址就无法使用了哦。我的博客是用 hexo 生成的,因此添加 RSS 就很容易了,如果你的博客是挂到第三方的,也会提供 RSS 地址。比如 CSDN 就提供了 RSS 地址: 由于大家的博客可能都不相同,因此具体大家可以自行搜索。 完整源代码本文所有的代码都可以在如下的代码仓库中找到。 仓库地址:https://github.com/azl397985856/azl397985856 如果在使用过程中碰到其他问题,也欢迎私信我哦~ 最后祝大家都有一个高大上的 Github 主页。","categories":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/categories/Github/"}],"tags":[{"name":"Github","slug":"Github","permalink":"https://lucifer.ren/blog/tags/Github/"},{"name":"持续集成","slug":"持续集成","permalink":"https://lucifer.ren/blog/tags/持续集成/"}]},{"title":"我是如何用「最大公约数」秒杀算法题的","slug":"gcd","date":"2021-02-23T16:00:00.000Z","updated":"2023-01-07T13:54:06.085Z","comments":true,"path":"2021/02/24/gcd/","link":"","permalink":"https://lucifer.ren/blog/2021/02/24/gcd/","excerpt":"关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。但是却有一些间接需要你求解最大公约数的题目。 比如: 914. 卡牌分组 365. 水壶问题 1071. 字符串的最大公因子 因此如何求解最大公约数就显得重要了。","text":"关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。但是却有一些间接需要你求解最大公约数的题目。 比如: 914. 卡牌分组 365. 水壶问题 1071. 字符串的最大公因子 因此如何求解最大公约数就显得重要了。 如何求最大公约数?定义法123456def GCD(a: int, b: int) -> int: smaller = min(a, b) while smaller: if a % smaller == 0 and b % smaller == 0: return smaller smaller -= 1 复杂度分析 时间复杂度:最好的情况是执行一次循环体,最坏的情况是循环到 smaller 为 1,因此总的时间复杂度为 $O(N)$,其中 N 为 a 和 b 中较小的数。 空间复杂度:$O(1)$。 辗转相除法如果我们需要计算 a 和 b 的最大公约数,运用辗转相除法的话。首先,我们先计算出 a 除以 b 的余数 c,把问题转化成求出 b 和 c 的最大公约数;然后计算出 b 除以 c 的余数 d,把问题转化成求出 c 和 d 的最大公约数;再然后计算出 c 除以 d 的余数 e,把问题转化成求出 d 和 e 的最大公约数。….. 以此类推,逐渐把两个较大整数之间的运算转化为两个较小整数之间的运算,直到两个数可以整除为止。 12def GCD(a: int, b: int) -> int: return a if b == 0 else GCD(b, a % b) 复杂度分析 时间复杂度:$O(log(max(a, b)))$ 空间复杂度:空间复杂度取决于递归的深度,因此空间复杂度为 $O(log(max(a, b)))$ 更相减损术辗转相除法如果 a 和 b 都很大的时候,a % b 性能会较低。在中国,《九章算术》中提到了一种类似辗转相减法的 更相减损术。它的原理是:两个正整数 a 和 b(a>b),它们的最大公约数等于 a-b 的差值 c 和较小数 b 的最大公约数。。 123456def GCD(a: int, b: int) -> int: if a == b: return a if a < b: return GCD(b - a, a) return GCD(a - b, b) 上面的代码会报栈溢出。原因在于如果 a 和 b 相差比较大的话,递归次数会明显增加,要比辗转相除法递归深度增加很多,最坏时间复杂度为 O(max(a, b)))。这个时候我们可以将辗转相除法和更相减损术做一个结合,从而在各种情况都可以获得较好的性能。 形象化解释下面我们对上面的过程进行一个表形象地讲解,实际上这也是教材里面的讲解方式,我只是照搬过来,增加一下自己的理解罢了。我们来通过一个例子来讲解: 假如我们有一块 1680 米 * 640 米 的土地,我们希望讲起分成若干正方形的土地,且我们想让正方形土地的边长尽可能大,我们应该如何设计算法呢? 实际上这正是一个最大公约数的应用场景,我们的目标就是求解 1680 和 640 的最大公约数。 将 1680 米 * 640 米 的土地分割,相当于对将 400 米 * 640 米 的土地进行分割。 为什么呢? 假如 400 米 * 640 米分割的正方形边长为 x,那么有 640 % x == 0,那么肯定也满足剩下的两块 640 米 * 640 米的。 我们不断进行上面的分割: 直到边长为 80,没有必要进行下去了。 实例解析题目描述1234567891011给你三个数字 a,b,c,你需要找到第 n 个(n 从 0 开始)有序序列的值,这个有序序列是由 a,b,c 的整数倍构成的。比如:n = 8a = 2b = 5c = 7由于 2,5,7 构成的整数倍构成的有序序列为 [1, 2, 4, 5, 6, 7, 8, 10, 12, ...],因此我们需要返回 12。注意:我们约定,有序序列的第一个永远是 1。 思路大家可以通过 这个网站 在线验证。 一个简单的思路是使用堆来做,唯一需要注意的是去重,我们可以使用一个哈希表来记录出现过的数字,以达到去重的目的。 代码: 1234567891011121314ss Solution: def solve(self, n, a, b, c): seen = set() h = [(a, a, 1), (b, b, 1), (c, c, 1)] heapq.heapify(h) while True: cur, base, times = heapq.heappop(h) if cur not in seen: n -= 1 seen.add(cur) if n == 0: return cur heapq.heappush(h, (base * (times + 1), base, times + 1)) 对于此解法不理解的可先看下我之前写的 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第二弹) 然而这种做法时间复杂度太高,有没有更好的做法呢? 实际上,我们可对搜索空间进行二分。首先思考一个问题,如果给定一个数字 x,那么有序序列中小于等于 x 的值有几个。 答案是 x // a + x // b + x // c 吗? // 是地板除 可惜不是的。比如 a = 2, b = 4, n = 4,答案显然不是 4 // 2 + 4 // 4 = 3,而是 2。这里出错的原因在于 4 被计算了两次,一次是 $2 2 = 4$,另一次是 $4 1 = 4$。 为了解决这个问题,我们可以通过集合论的知识。 一点点集合知识: 如果把有序序列中小于等于 x 的可以被 x 整除,且是 a 的倍数的值构成的集合为 SA,集合大小为 A 如果把有序序列中小于等于 x 的可以被 x 整除,且是 b 的倍数的值构成的集合为 SB,集合大小为 B 如果把有序序列中小于等于 x 的可以被 x 整除,且是 c 的倍数的值构成的集合为 SC,集合大小为 C 那么最终的答案就是 SA ,SB,SC 构成的大的集合(需要去重)的中的数字的个数,也就是: A + B + C - sizeof(SA \\cap SB) - sizeof(SB \\cap SC) - sizeof(SA \\cap SC) + sizeof(SA \\cap SB \\cap SC)问题转化为 A 和 B 集合交集的个数如何求? A 和 B,B 和 C, A 和 C ,甚至是 A,B,C 的交集求法都是一样的。 实际上, SA 和 SB 的交集个数就是 x // lcm(A, B),其中 lcm 为 A 和 B 的最小公倍数。而最小公倍数则可以通过最大公约数计算出来: 12def lcm(x, y): return x * y // gcd(x, y) 接下来就是二分套路了,二分部分看不懂的建议看下我的二分专题。 代码(Python3)123456789101112131415161718192021class Solution: def solve(self, n, a, b, c): def gcd(x, y): if y == 0: return x return gcd(y, x % y) def lcm(x, y): return x * y // gcd(x, y) def possible(mid): return (mid // a + mid // b + mid // c - mid // lcm(a, b) - mid // lcm(b, c) - mid // lcm(a, c) + mid // lcm(a, lcm(b, c))) >= n l, r = 1, n * max(a, b, c) while l <= r: mid = (l + r) // 2 if possible(mid): r = mid - 1 else: l = mid + 1 return l 复杂度分析 时间复杂度:$logn$。 空间复杂度:gcd 和 lcm 的递归树深度,基本可忽略不计。 总结通过这篇文章,我们不仅明白了最大公约数的概念以及求法。也形象化地感知到了最大公约数计算的原理。最大公约数和最小公倍数是两个相似的概念, 关于最大公约数和最小公倍数的题目在力扣中不算少,大家可以通过数学标签找到这些题。更多关于算法中的数学知识,可以参考这篇文章刷算法题必备的数学考点汇总 这篇文章的第二篇也马上要发布了。","categories":[{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/categories/数学/"},{"name":"算法","slug":"数学/算法","permalink":"https://lucifer.ren/blog/categories/数学/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"最大公约数","slug":"最大公约数","permalink":"https://lucifer.ren/blog/tags/最大公约数/"}]},{"title":"如何求二维数组的前缀和?","slug":"2d-pre","date":"2021-02-19T16:00:00.000Z","updated":"2023-01-07T12:17:01.143Z","comments":true,"path":"2021/02/20/2d-pre/","link":"","permalink":"https://lucifer.ren/blog/2021/02/20/2d-pre/","excerpt":"一维前缀和很容易求,那二维前缀和你会吗?","text":"一维前缀和很容易求,那二维前缀和你会吗? 什么是前缀和?前缀和是一种重要的预处理,能大大降低查询的时间复杂度。我们可以简单理解为“数列的前 n 项的和”。这个概念其实很容易理解,即一个数组中,第 n 位存储的是数组前 n 个数字的和。 通过一个例子来进行说明会更清晰。题目描述:有一个长度为 N 的整数数组 A,要求返回一个新的数组 B,其中 B 的第 i 个数 B[i]是原数组 A 前 i 项和。 这道题实际就是让你求数组 A 的前缀和。对 [1,2,3,4,5,6] 来说,其前缀和可以是 pre=[1,3,6,10,15,21]。我们可以使用公式 pre[𝑖]=pre[𝑖−1]+nums[𝑖]得到每一位前缀和的值,从而通过前缀和进行相应的计算和解题。其实前缀和的概念很简单,但困难的是如何在题目中使用前缀和以及如何使用前缀和的关系来进行解题。实际的题目更多不是直接让你求前缀和,而是你需要自己使用前缀和来优化算法的某一个性能瓶颈。 而如果数组是正数的话,前缀和数组会是一个单调不递减序列,因此前缀和 + 二分也会是一个考点,不过这种题目难度一般是力扣的困难难度。关于这个知识点,我会在之后的二分专题方做更多介绍。 简单的二维前缀和上面提到的例子是一维数组的前缀和,简称一维前缀和。那么二维前缀和实际上就是二维数组上的前缀和了。一维数组的前缀和也是一个一维数组,同样地,二维数组的前缀和也是一个二维的数组。 比如对于如下的一个二维矩阵: 121 2 3 45 6 7 8 定义二维前缀和矩阵 $pres$,$pres{x,y} = \\sum\\limits_{i=1}^x \\sum\\limits_{j=1}^y a_{i,j}$。经过这样的处理,上面矩阵的二维前缀和就变成了: 121 3 6 106 14 24 36 那么如何用代码计算二维数组的前缀和呢?简单的二维前缀和的求解方法是基于容斥原理的。 比如我们想求如图中灰色部分的和。 一种方式就是用下图中两个绿色部分的矩阵加起来(之所以用绿色部分相加是因为这两部分已经通过上面预处理计算好了,可以在 $O(1)$ 的时间得到),这样我们就会多加一块区域,这块区域就是如图黄色部分,我们再减去黄色部分就好了,最后再加上当前位置本身就行了。 比如我们想要求 $sum_{i,j}$,则可以通过 $sum_{i - 1,j} + sum_{i,j - 1} - sum_{i - 1,j - 1} + a_{i,j}$ 的方式来实现。这样我就可以通过 $O(m n)$ 的预处理计算二维前缀和矩阵(m 和 n 分别为矩阵的长和宽),再通过 $O(1)$ 的时间计算出*任意小矩阵的和。其底层原理就是上面提到的容斥原理,大家可以通过画图的方式来感受一下。 如何将二维前缀和转化为一维前缀和然而实际上,我们也可不构建一个前缀和数组,而是直接原地修改。 一维前缀和同样可以采用这一技巧。 比如我们可以先不考虑行之间的关联,而是预先计算出每一行的前缀和。对于计算每一行的前缀和就是一维前缀和啦。接下来通过固定两个列的端点的方式计算每一行的区域和。代码上,我们可以通过三层循环来实现, 其中两层循环用来固定列端点,另一层用于枚举所有行。 其实也可以反过来。即固定行的左右端点并枚举列,下面的题目会提到这一点。 代码表示: 1234# 预先构建行的前缀和for row in matrix: for i in range(n - 1): row[i + 1] += row[i] 比如矩阵: 121 2 3 45 6 7 8 则会变为: 121 3 6 105 11 18 26 接下来: 12345678910# 固定列的两个端点,即枚举所有列的组合for i in range(n): for j in range(i, n): pres = [0] pre = 0 # 枚举所有行 for k in range(m): # matrix[k] 其实已经是上一步预处理的每一行的前缀和了。因此 matrix[k][j] - (matrix[k][i - 1] 就是每一行 [i, j] 的区域和。 pre += matrix[k][j] - (matrix[k][i - 1] if i > 0 else 0) pres.append(pre) 上面代码做的事情形象来看,就是先在水平方向计算前缀和,然后在竖直方向计算前缀和,而不是同时在两个方向计算。 如果把 [i, j] 的区域和看出是一个数的话,问题就和一维前缀和一样了。代码: 12345678910for i in range(n): for j in range(i, n): pres = [0] pre = 0 # 枚举所有行 for k in range(m): # 其中 a 为[i, j] 的区域和 pre += a pres.append(pre) 题目推荐有了上面的知识,我们就可以来解决下面两道题。虽然下面两道题的难度都是 hard,不过总体难度并不高。这两道题之所以是 hard, 是因为其考察了不止一个知识点。这也是 hard 题目的一种类型,即同时考察多个知识点。 363. 矩形区域不超过 K 的最大数值和题目地址https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/ 题目描述12345678910111213给定一个非空二维矩阵 matrix 和一个整数 k,找到这个矩阵内部不大于 k 的最大矩形和。示例:输入: matrix = [[1,0,1],[0,-2,3]], k = 2输出: 2解释: 矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。说明:矩阵内的矩形区域面积必须大于 0。如果行数远大于列数,你将如何解答呢? 前置知识 二维前缀和 二分法 思路前面提到了由于非负数数组的二维前缀和是一个非递减的数组,因此常常和二分结合考察。实际上即使数组不是非负的,我们仍然有可能构建一个有序的前缀和,从而使用二分,这道题就是一个例子。 首先我们可以用上面提到的技巧计算二维数组的前缀和,这样我们就可以计算快速地任意子矩阵的和了。注意到上面我们计算的 pres 数组是一个一维数组,但矩阵其实可能为负数,因此不满足单调性。这里我们可以手动维护 pres 单调递增,这样就可以使用二分法在 $logN$ 的时间求出以当前项 i 结尾的不大于 k 的最大矩形和,那么答案就是所有的以任意索引 x 结尾的不大于 k 的最大矩形和的最大值。 之所以可以手动维护 pres 数组单调增也可得到正确结果的原因是题目只需要求子矩阵和,而不是求具体的子矩阵。 代码上,当计算出 pres 后,我们其实需要寻找大于等于 pre - k 的最小数 x。这样矩阵和 pre - x 才能满足 pre - x <= k,使用最左插入二分模板即可解决。 关键点 典型的二维前缀和 + 二分题目 代码 语言支持:Python3 Python3 Code: 1234567891011121314151617181920212223242526from sortedcontainers import SortedListclass Solution: def maxSumSubmatrix(self, matrix: List[List[int]], K: int) -> int: m, n = len(matrix), len(matrix[0]) for i in range(m): for j in range(1, n): matrix[i][j] += matrix[i][j - 1] ans = float(\"-inf\") for i in range(n): for j in range(i, n): pres = SortedList([0]) pre = 0 for k in range(m): pre += matrix[k][j] - (0 if i == 0 else matrix[k][i - 1]) # 寻找小于等于 pre - k 的最大数。 # 为了达到这个目的,可以使用 bisect_left 来完成。(使用 bisect_right 不包含等号) idx = pres.bisect_left(pre - K) # 如果 i == len(pre) 表示无解 if idx < len(pres): ans = max(ans, pre - pres[idx]) pres.add(pre) return ans 复杂度分析 令 n 为数组长度。 时间复杂度:$O(m*n ^ 2logm)$ 空间复杂度:$O(m)$ 题目给了一个 follow up:如果行数远大于列数,你将如何解答呢? 实际上,如果行数远大于列数,由复杂度分析可知空间复杂度会很高。我们可以将行列兑换,这样空间复杂度是 $O(n)$。换句话说,我们可以通过行列的调换做到空间复杂度为 $O(min(m, n))$。 1074. 元素和为目标值的子矩阵数量题目地址https://leetcode-cn.com/problems/number-of-submatrices-that-sum-to-target/ 题目描述123456789101112131415161718192021222324252627282930给出矩阵 matrix 和目标值 target,返回元素总和等于目标值的非空子矩阵的数量。子矩阵 x1, y1, x2, y2 是满足 x1 <= x <= x2 且 y1 <= y <= y2 的所有单元 matrix[x][y] 的集合。如果 (x1, y1, x2, y2) 和 (x1', y1', x2', y2') 两个子矩阵中部分坐标不同(如:x1 != x1'),那么这两个子矩阵也不同。 示例 1:输入:matrix = [[0,1,0],[1,1,1],[0,1,0]], target = 0输出:4解释:四个只含 0 的 1x1 子矩阵。示例 2:输入:matrix = [[1,-1],[-1,1]], target = 0输出:5解释:两个 1x2 子矩阵,加上两个 2x1 子矩阵,再加上一个 2x2 子矩阵。 提示:1 <= matrix.length <= 3001 <= matrix[0].length <= 300-1000 <= matrix[i] <= 1000-10^8 <= target <= 10^8 前置知识 二维前缀和 思路和上面题目类似。不过这道题是求子矩阵和刚好等于某个目标值的数目。 我们不妨先对问题进行简化。比如题目要求的是一维数组中,子数组(连续)的和等于目标值 target 的数目。我们该如何做? 这很容易,我们只需要: 边遍历边计算前缀和。 比如当前的前缀和是 cur,那么我们要找的前缀和 x 应该满足 cur - x = target,因为这样当前位置和 x 的之间的子数组和才是 target。即我们需要找前缀和为 cur - target 的数目。 这提示我们使用哈希表记录每一种前缀和出现的次数。 由于仅仅是求数目,不涉及到求具体的子矩阵信息,因此使用类似上面的解法求出二维前缀和。接下来,使用和一维前缀和同样的方法即可求出答案。 关键点 主要考察一维前缀和到二维前缀和的过渡是否掌握 代码 语言支持:Python3 Python3 Code: 1234567891011121314151617class Solution: def numSubmatrixSumTarget(self, matrix, target): m, n = len(matrix), len(matrix[0]) for row in matrix: for i in range(n - 1): row[i + 1] += row[i] ans = 0 for i in range(n): for j in range(i, n): c = collections.defaultdict(int) cur, c[0] = 0, 1 for k in range(m): cur += matrix[k][j] - (matrix[k][i - 1] if i > 0 else 0) ans += c[cur - target] c[cur] += 1 return ans 复杂度分析 时间复杂度:$O(m * n ^ 2)$ 空间复杂度:$O(m)$ 和上面一样,我们可以将行列对换,这样空间复杂度是 $O(n)$。换句话说,我们可以通过行列的调换做到空间复杂度为 $O(min(m, n))$。 更多题目 面试题 17.24. 最大子矩阵 (用西法的套路一下子就做出来了) 这道题就是二维前缀和 + 一维最大子序和的知识就可以 AC。而这两个西法我都写过文章了,不懂的建议看看。 参考代码: 1234567891011121314151617181920212223class Solution: def getMaxMatrix(self, matrix: List[List[int]]) -> List[int]: max_area = float(\"-inf\") ans = [] m, n = len(matrix), len(matrix[0]) for i in range(m): for j in range(1, n): matrix[i][j] += matrix[i][j - 1] for i in range(n): for j in range(i, n): pre = min_pre = min_pre_idx = 0 for k in range(m): if pre < min_pre: min_pre = pre min_pre_idx = k pre += matrix[k][j] - (matrix[k][i - 1] if i > 0 else 0) if pre - min_pre > max_area: max_area = pre - min_pre ans = [min_pre_idx, i, k, j] return ans 力扣的小伙伴可以关注我,这样就会第一时间收到我的动态啦~ 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/categories/前缀和/"},{"name":"二维前缀和","slug":"前缀和/二维前缀和","permalink":"https://lucifer.ren/blog/categories/前缀和/二维前缀和/"}],"tags":[{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"}]},{"title":"一个让你的 YouTube 丝滑般柔顺的插件","slug":"youtube-extesion","date":"2021-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.758Z","comments":true,"path":"2021/02/20/youtube-extesion/","link":"","permalink":"https://lucifer.ren/blog/2021/02/20/youtube-extesion/","excerpt":"Youtube 和 B 站是很多人浏览视频的两个地方。 一开始我还挺喜欢 Youtube 的,不仅是因为可以看到一些其他人看不到的东西,也因为体验比较棒。而现在,我已经彻底失望了。每次看一个十分钟的视频被跳出来四次广告的痛苦你了解么?如果你在看一个需要集中精力思考的视频,那么这种打断真的是非常让人恼怒。如果不是实在无法忍受这种行为,我也不会使用一些广告屏蔽的插件,毕竟看广告其实也是对 up 主的另外一种支持。 另外 YouTube 竟然没有小窗播放。比如我下滑到评论区,视频并不会小窗悬浮播放,差评!","text":"Youtube 和 B 站是很多人浏览视频的两个地方。 一开始我还挺喜欢 Youtube 的,不仅是因为可以看到一些其他人看不到的东西,也因为体验比较棒。而现在,我已经彻底失望了。每次看一个十分钟的视频被跳出来四次广告的痛苦你了解么?如果你在看一个需要集中精力思考的视频,那么这种打断真的是非常让人恼怒。如果不是实在无法忍受这种行为,我也不会使用一些广告屏蔽的插件,毕竟看广告其实也是对 up 主的另外一种支持。 另外 YouTube 竟然没有小窗播放。比如我下滑到评论区,视频并不会小窗悬浮播放,差评! 废话不多说了。今天给大家推荐的这个插件名字叫 Enhancer for YouTube。安装地址:https://chrome.google.com/webstore/detail/enhancer-for-youtube/ponfpcnoihfmfllpaingbgckeeldkhle 这个插件有 400,000+用户,超过 9000 人做出了评价,评分是五分(满分五分)。 它提供了很多功能以及众多的配置项,从外观的配置到功能的配置应有尽有。 还可以配置主题,我把主题换成了暗黑模式。 主要功能有: 使用鼠标滚轮控制音量和播放速度 视频去广告(自动或手动) 频道白名单(不自动去广告) 屏蔽注解(自动或手动) 自动切换视频清晰度(可以设定为 4K、HD 或任意清晰度) 循环播放视频(完整循环或部分循环) 使用自定义主题样式 自动拉伸播放器 查看评论时将播放器固定在右下角 执行自定义脚本 。。。 更多功能等待大家的探索。","categories":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"},{"name":"浏览器插件","slug":"插件/浏览器插件","permalink":"https://lucifer.ren/blog/categories/插件/浏览器插件/"}],"tags":[{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"}]},{"title":"一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~","slug":"删除问题","date":"2021-02-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.714Z","comments":true,"path":"2021/02/20/删除问题/","link":"","permalink":"https://lucifer.ren/blog/2021/02/20/删除问题/","excerpt":"我花了几天时间,从力扣中精选了四道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 这就是接下来要给大家讲的四个题,其中 1081 和 316 题只是换了说法而已。 316. 去除重复字母(困难) 321. 拼接最大数(困难) 402. 移掉 K 位数字(中等) 1081. 不同字符的最小子序列(中等)","text":"我花了几天时间,从力扣中精选了四道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 这就是接下来要给大家讲的四个题,其中 1081 和 316 题只是换了说法而已。 316. 去除重复字母(困难) 321. 拼接最大数(困难) 402. 移掉 K 位数字(中等) 1081. 不同字符的最小子序列(中等) 402. 移掉 K 位数字(中等)我们从一个简单的问题入手,识别一下这种题的基本形式和套路,为之后的三道题打基础。 题目描述1234567891011121314151617181920212223给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。注意:num 的长度小于 10002 且 ≥ k。num 不会包含任何前导零。示例 1 :输入: num = "1432219", k = 3输出: "1219"解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。示例 2 :输入: num = "10200", k = 1输出: "200"解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。示例 3 :输入: num = "10", k = 2输出: "0"解释: 从原数字移除所有的数字,剩余为空就是 0。 前置知识 数学 思路这道题让我们从一个字符串数字中删除 k 个数字,使得剩下的数最小。也就说,我们要保持原来的数字的相对位置不变。 以题目中的 num = 1432219, k = 3 为例,我们需要返回一个长度为 4 的字符串,问题在于: 我们怎么才能求出这四个位置依次是什么呢? (图 1) 暴力法的话,我们需要枚举C_n^(n - k) 种序列(其中 n 为数字长度),并逐个比较最大。这个时间复杂度是指数级别的,必须进行优化。 一个思路是: 从左到右遍历 对于每一个遍历到的元素,我们决定是丢弃还是保留 问题的关键是:我们怎么知道,一个元素是应该保留还是丢弃呢? 这里有一个前置知识:对于两个数 123a456 和 123b456,如果 a > b, 那么数字 123a456 大于 数字 123b456,否则数字 123a456 小于等于数字 123b456。也就说,两个相同位数的数字大小关系取决于第一个不同的数的大小。 因此我们的思路就是: 从左到右遍历 对于遍历到的元素,我们选择保留。 但是我们可以选择性丢弃前面相邻的元素。 丢弃与否的依据如上面的前置知识中阐述中的方法。 以题目中的 num = 1432219, k = 3 为例的图解过程如下: (图 2) 由于没有左侧相邻元素,因此没办法丢弃。 (图 3) 由于 4 比左侧相邻的 1 大。如果选择丢弃左侧的 1,那么会使得剩下的数字更大(开头的数从 1 变成了 4)。因此我们仍然选择不丢弃。 (图 4) 由于 3 比左侧相邻的 4 小。 如果选择丢弃左侧的 4,那么会使得剩下的数字更小(开头的数从 4 变成了 3)。因此我们选择丢弃。 。。。 后面的思路类似,我就不继续分析啦。 然而需要注意的是,如果给定的数字是一个单调递增的数字,那么我们的算法会永远选择不丢弃。这个题目中要求的,我们要永远确保丢弃 k 个矛盾。 一个简单的思路就是: 每次丢弃一次,k 减去 1。当 k 减到 0 ,我们可以提前终止遍历。 而当遍历完成,如果 k 仍然大于 0。不妨假设最终还剩下 x 个需要丢弃,那么我们需要选择删除末尾 x 个元素。 上面的思路可行,但是稍显复杂。 (图 5) 我们需要把思路逆转过来。刚才我的关注点一直是丢弃,题目要求我们丢弃 k 个。反过来说,不就是让我们保留 $n - k$ 个元素么?其中 n 为数字长度。 那么我们只需要按照上面的方法遍历完成之后,再截取前n - k个元素即可。 按照上面的思路,我们来选择数据结构。由于我们需要保留和丢弃相邻的元素,因此使用栈这种在一端进行添加和删除的数据结构是再合适不过了,我们来看下代码实现。 代码(Python)12345678910class Solution(object): def removeKdigits(self, num, k): stack = [] remain = len(num) - k for digit in num: while k and stack and stack[-1] > digit: stack.pop() k -= 1 stack.append(digit) return ''.join(stack[:remain]).lstrip('0') or '0' _复杂度分析_ 时间复杂度:虽然内层还有一个 while 循环,但是由于每个数字最多仅会入栈出栈一次,因此时间复杂度仍然为 $O(N)$,其中 $N$ 为数字长度。 空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 $O(N)$,其中 $N$ 为数字长度。 提示: 如果题目改成求删除 k 个字符之后的最大数,我们只需要将 stack[-1] > digit 中的大于号改成小于号即可。 316. 去除重复字母(困难)题目描述12345678910给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。示例 1:输入: "bcabc"输出: "abc"示例 2:输入: "cbacdcbc"输出: "acdb" 前置知识 字典序 数学 思路与上面题目不同,这道题没有一个全局的删除次数 k。而是对于每一个在字符串 s 中出现的字母 c 都有一个 k 值。这个 k 是 c 出现次数 - 1。 沿用上面的知识的话,我们首先要做的就是计算每一个字符的 k,可以用一个字典来描述这种关系,其中 key 为 字符 c,value 为其出现的次数。 具体算法: 建立一个字典。其中 key 为 字符 c,value 为其出现的剩余次数。 从左往右遍历字符串,每次遍历到一个字符,其剩余出现次数 - 1. 对于每一个字符,如果其对应的剩余出现次数大于 1,我们可以选择丢弃(也可以选择不丢弃),否则不可以丢弃。 是否丢弃的标准和上面题目类似。如果栈中相邻的元素字典序更大,那么我们选择丢弃相邻的栈中的元素。 还记得上面题目的边界条件么?如果栈中剩下的元素大于 $n - k$,我们选择截取前 $n - k$ 个数字。然而本题中的 k 是分散在各个字符中的,因此这种思路不可行的。 不过不必担心。由于题目是要求只出现一次。我们可以在遍历的时候简单地判断其是否在栈上即可。 代码: 123456789101112class Solution: def removeDuplicateLetters(self, s) -> int: stack = [] remain_counter = collections.Counter(s) for c in s: if c not in stack: while stack and c < stack[-1] and remain_counter[stack[-1]] > 0: stack.pop() stack.append(c) remain_counter[c] -= 1 return ''.join(stack) _复杂度分析_ 时间复杂度:由于判断当前字符是否在栈上存在需要 $O(N)$ 的时间,因此总的时间复杂度就是 $O(N ^ 2)$,其中 $N$ 为字符串长度。 空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 $O(N)$,其中 $N$ 为字符串长度。 查询给定字符是否在一个序列中存在的方法。根本上来说,有两种可能: 有序序列: 可以二分法,时间复杂度大致是 $O(N)$。 无序序列: 可以使用遍历的方式,最坏的情况下时间复杂度为 $O(N)$。我们也可以使用空间换时间的方式,使用 $N$的空间 换取 $O(1)$的时间复杂度。 由于本题中的 stack 并不是有序的,因此我们的优化点考虑空间换时间。而由于每种字符仅可以出现一次,这里使用 hashset 即可。 代码(Python)1234567891011121314class Solution: def removeDuplicateLetters(self, s) -> int: stack = [] seen = set() remain_counter = collections.Counter(s) for c in s: if c not in seen: while stack and c < stack[-1] and remain_counter[stack[-1]] > 0: seen.discard(stack.pop()) seen.add(c) stack.append(c) remain_counter[c] -= 1 return ''.join(stack) _复杂度分析_ 时间复杂度:$O(N)$,其中 $N$ 为字符串长度。 空间复杂度:我们使用了额外的栈和 hashset,因此空间复杂度为 $O(N)$,其中 $N$ 为字符串长度。 LeetCode 《1081. 不同字符的最小子序列》 和本题一样,不再赘述。 321. 拼接最大数(困难)题目描述123456789101112131415161718192021222324252627282930给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。说明: 请尽可能地优化你算法的时间和空间复杂度。示例 1:输入:nums1 = [3, 4, 6, 5]nums2 = [9, 1, 2, 5, 8, 3]k = 5输出:[9, 8, 6, 5, 3]示例 2:输入:nums1 = [6, 7]nums2 = [6, 0, 4]k = 5输出:[6, 7, 6, 0, 4]示例 3:输入:nums1 = [3, 9]nums2 = [8, 9]k = 3输出:[9, 8, 9] 前置知识 分治 数学 思路和第一道题类似,只不不过这一次是两个数组,而不是一个,并且是求最大数。 最大最小是无关紧要的,关键在于是两个数组,并且要求从两个数组选取的元素个数加起来一共是 k。 然而在一个数组中取 k 个数字,并保持其最小(或者最大),我们已经会了。但是如果问题扩展到两个,会有什么变化呢? 实际上,问题本质并没有发生变化。 假设我们从 nums1 中取了 k1 个,从 num2 中取了 k2 个,其中 k1 + k2 = k。而 k1 和 k2 这 两个子问题我们是会解决的。由于这两个子问题是相互独立的,因此我们只需要分别求解,然后将结果合并即可。 假如 k1 和 k2 个数字,已经取出来了。那么剩下要做的就是将这个长度分别为 k1 和 k2 的数字,合并成一个长度为 k 的数组合并成一个最大的数组。 以题目的 nums1 = [3, 4, 6, 5] nums2 = [9, 1, 2, 5, 8, 3] k = 5 为例。 假如我们从 num1 中取出 1 个数字,那么就要从 nums2 中取出 4 个数字。 运用第一题的方法,我们计算出应该取 nums1 的 [6],并取 nums2 的 [9,5,8,3]。 如何将 [6] 和 [9,5,8,3],使得数字尽可能大,并且保持相对位置不变呢? 实际上这个过程有点类似归并排序中的治,而上面我们分别计算 num1 和 num2 的最大数的过程类似归并排序中的分。 (图 6) 代码: 我们将从 num1 中挑选的 k1 个数组成的数组称之为 A,将从 num2 中挑选的 k2 个数组成的数组称之为 B, 1234567def merge(A, B): ans = [] while A or B: bigger = A if A > B else B ans.append(bigger[0]) bigger.pop(0) return ans 这里需要说明一下。 在很多编程语言中:如果 A 和 B 是两个数组,当前仅当 A 的首个元素字典序大于 B 的首个元素,A > B 返回 true,否则返回 false。 比如: 1234567A = [1,2]B = [2]A < B # TrueA = [1,2]B = [1,2,3]A < B # False 以合并 [6] 和 [9,5,8,3] 为例,图解过程如下: (图 7) 具体算法: 从 nums1 中 取 $min(i, len(nums1))$ 个数形成新的数组 A(取的逻辑同第一题),其中 i 等于 0,1,2, … k。 从 nums2 中 对应取 $min(j, len(nums2))$ 个数形成新的数组 B(取的逻辑同第一题),其中 j 等于 k - i。 将 A 和 B 按照上面的 merge 方法合并 上面我们暴力了 k 种组合情况,我们只需要将 k 种情况取出最大值即可。 代码(Python)12345678910111213141516171819202122class Solution: def maxNumber(self, nums1, nums2, k): def pick_max(nums, k): stack = [] drop = len(nums) - k for num in nums: while drop and stack and stack[-1] < num: stack.pop() drop -= 1 stack.append(num) return stack[:k] def merge(A, B): ans = [] while A or B: bigger = A if A > B else B ans.append(bigger[0]) bigger.pop(0) return ans return max(merge(pick_max(nums1, i), pick_max(nums2, k-i)) for i in range(k+1) if i <= len(nums1) and k-i <= len(nums2)) _复杂度分析_ 时间复杂度:pick_max 的时间复杂度为 $O(M + N)$ ,其中 $M$ 为 nums1 的长度,$N$ 为 nums2 的长度。 merge 的时间复杂度为 $O(k)$,再加上外层遍历所有的 k 中可能性。因此总的时间复杂度为 $O(k^2 * (M + N))$。 空间复杂度:我们使用了额外的 stack 和 ans 数组,因此空间复杂度为 $O(max(M, N, k))$,其中 $M$ 为 nums1 的长度,$N$ 为 nums2 的长度。 总结这四道题都是删除或者保留若干个字符,使得剩下的数字最小(或最大)或者字典序最小(或最大)。而解决问题的前提是要有一定数学前提。而基于这个数学前提,我们贪心地删除栈中相邻的字符。如果你会了这个套路,那么这四个题目应该都可以轻松解决。 316. 去除重复字母(困难),我们使用 hashmap 代替了数组的遍历查找,属于典型的空间换时间方式,可以认识到数据结构的灵活使用是多么的重要。背后的思路是怎么样的?为什么想到空间换时间的方式,我在文中也进行了详细的说明,这都是值得大家思考的问题。然而实际上,这些题目中使用的栈也都是空间换时间的思想。大家下次碰到需要空间换取时间的场景,是否能够想到本文给大家介绍的栈和哈希表呢? 321. 拼接最大数(困难)则需要我们能够对问题进行分解,这绝对不是一件简单的事情。但是对难以解决的问题进行分解是一种很重要的技能,希望大家能够通过这道题加深这种分治思想的理解。 大家可以结合我之前写过的几个题解练习一下,它们分别是: 【简单易懂】归并排序(Python) 一文看懂《最大子序列和问题》 最后推荐类似的题目供大家练习: 1673. 找出最具竞争力的子序列 2030. 含特定字母的最小子序列 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/tags/经验分享/"},{"name":"困难","slug":"困难","permalink":"https://lucifer.ren/blog/tags/困难/"},{"name":"中等","slug":"中等","permalink":"https://lucifer.ren/blog/tags/中等/"},{"name":"删除 k 个字符","slug":"删除-k-个字符","permalink":"https://lucifer.ren/blog/tags/删除-k-个字符/"}]},{"title":"如何将 github 上的代码一键部署到服务器?","slug":"deploy-button","date":"2021-02-13T16:00:00.000Z","updated":"2023-01-07T13:54:06.068Z","comments":true,"path":"2021/02/14/deploy-button/","link":"","permalink":"https://lucifer.ren/blog/2021/02/14/deploy-button/","excerpt":"在 Github 上看到一些不错的仓库,想要贡献代码怎么办? 在 Github 上看到一些有用的网站,想部署到自己的服务器怎么办? 。。。 我想很多人都碰到过这个问题。 如果要贡献代码,之前我的做法通常是将代码克隆到本地,然后在本地的编辑器中修改并提交 pr。 如果想部署到自己的服务器,之前我的做法通常是克隆到本地,然后本地修改一下部署的配置,最后部署到自己的服务器或者第三方的云服务器(比如 Github Pages)。 而现在随着云技术的普及,我们没有必要将代码克隆到本地进行操作,而是直接在云端编辑器中完成修改,开发,并直接部署到云服务器。今天就给大家推荐一个工具,一键将代码部署到云服务器。 ​","text":"在 Github 上看到一些不错的仓库,想要贡献代码怎么办? 在 Github 上看到一些有用的网站,想部署到自己的服务器怎么办? 。。。 我想很多人都碰到过这个问题。 如果要贡献代码,之前我的做法通常是将代码克隆到本地,然后在本地的编辑器中修改并提交 pr。 如果想部署到自己的服务器,之前我的做法通常是克隆到本地,然后本地修改一下部署的配置,最后部署到自己的服务器或者第三方的云服务器(比如 Github Pages)。 而现在随着云技术的普及,我们没有必要将代码克隆到本地进行操作,而是直接在云端编辑器中完成修改,开发,并直接部署到云服务器。今天就给大家推荐一个工具,一键将代码部署到云服务器。 ​ 什么是一键部署?今天给大家介绍的就是一键部署。那什么是一键部署呢?顾名思义,就是有一个按钮,点击一下就能完成部署工作。 如下是一个拥有一键部署按钮的项目: 点击之后进入如下页面,你可以对一些默认配置进行修改(也可以直接使用默认配置): 修改后点击Deploy app 即可。部署成功之后就可以通过类似如下的地址访问啦~ 图中演示地址是:https://leetcode-cheat.herokuapp.com/ 大家可以直接进我的仓库 https://github.com/leetcode-pp/leetcode-cheat,点击部署按钮试试哦。 它是如何实现的呢?我是一个喜欢探究事物原理的人,当然对它们的原理了如指掌才行。其实它的原理很容易,我们从头开始说。 1. 如何在 Github 中显示发布按钮。上面的部署按钮就是如下的一个 Markdown 内容渲染的: 1[![Deploy](https://p.ipic.vip/hefqvb.jpg)](https://p.ipic.vip/v0aqts.jpg) 上面内容会被渲染成如下的 DOM: 1234567<a href=\"https://heroku.com/deploy\" rel=\"nofollow\" ><img src=\"https://camo.githubusercontent.com/6979881d5a96b7b18a057083bb8aeb87ba35fc279452e29034c1e1c49ade0636/68747470733a2f2f7777772e6865726f6b7563646e2e636f6d2f6465706c6f792f627574746f6e2e737667\" alt=\"Deploy\" data-canonical-src=\"https://www.herokucdn.com/deploy/button.svg\" style=\"max-width:100%;\"/></a> 也就是说其实就是一个被 a 标签包裹的 svg 图片,点击之后会完成 url 跳转。 2. 云服务厂商如何获取默认配置?这里以 heroku 为例,其他厂商(比如腾讯)原理都差不多。 由于上面的原因,实际上我们传递给第三方云厂商的方式只可能是 url。因此我们可以直接将配置通过 ur 的方式传输。比如 https://heroku.com/deploy?a=1&b=2&c=3 。 这种方式对于少量数据是足够的,那如何数据量很大呢?我们知道浏览器 url 的长度是有限的,而且不同的浏览器限制也不尽相同。 那怎么解决呢?现在比较流行的思路是约定。以 heroku 来说,就约定根目录的 app.json 文件中存配置,这种约定的方式我个人强烈推荐。 比如我的仓库的 app.json 就是: 12345678910111213141516171819202122{ \"name\": \"LeetCode Cheatsheet\", \"description\": \"力扣加加,或许是西湖区最好的算法题解\", \"repository\": \"https://github.com/leetcode-pp/leetcode-cheat\", \"logo\": \"https://tva1.sinaimg.cn/large/008eGmZEly1gnm68epc0kj30u00tsaav.jpg\", \"keywords\": [\"github\", \"leetcode\", \"cheatsheet\", \"91algo\", \"algorithm\"], \"env\": { \"REACT_APP_BUILD_TARGET\": { \"description\": \"枚举值:extension 和 web\", \"value\": null }, \"PUBLIC_URL\": { \"description\": \"静态资源存放位置(可使用 cdn 加速)\", \"value\": \"https://cdn.jsdelivr.net/gh/leetcode-pp/leetcode-cheat@gh-pages/\" } }, \"buildpacks\": [ { \"url\": \"https://buildpack-registry.s3.amazonaws.com/buildpacks/mars/create-react-app.tgz\" } ]} 可以看出,除了配置仓库,logo,描述这些常规信息,我还配置了环境变量和 buidpacks。buildpacks 简单来说就是构建应用的方式, 关于 buildpacks 的更多信息可以参考 heroku 官方文档 大家可能还有疑问,为啥上面的链接是 https://heroku.com/deploy。可以看出 url 中也没有任何参数信息,那为什么它就知道从哪来的呢?我觉得 ta 应该利用的是浏览器的 referer,用它可以判断从哪里过来的,进而搜索对应项目根目录的 app.json 文件。你可以通过右键在新的无痕模式中打开来验证。你会发现右键在新的无痕模式中打开是无法正常部署的。 这有什么用呢?一键部署意味着部署的门槛更低,不仅是技巧上的,而且是成本上的。比如 heroku 就允许你直接免费一键部署若干个应用,直接生成网站,域名可以直接访问。如果你觉得域名不喜欢也可以自定义。如果你想修改源码重新构建也是可以的。 比如我看到别人的博客很漂亮。如果 ta 提供了一键部署,那么就可以直接部署到自己的云服务器,生成自己的 url。关联自己的 git 之后,推送还能自动部署(CD)。而且这一切都可以是免费的,至少我现在用的是免费的。 而如果 ta 没有提供一键部署,就需要你自己手动完成了。如果你对这些熟悉还好,无非就是多花点时间。而如果你是技术小白,我可能仅仅是想部署一下,用自己的域名访问之类,没有一键部署就很不友好啦。 相关技术gitpod 是我一直在用的一个工具,它可以帮助我直接在云端编辑一些内容。或者有一些环境问题,需要虚拟主机的,也可以用它来解决。 它不仅仅提供了在线 IDE 的所有功能,还集成了 CI 和 CD,用起来也是非常方便。 同样地,你也可以在你的仓库中增加在 Gitpod 一键打开的功能。 小技巧一些开源项目你不知道怎么贡献。其实可以另辟蹊径,比如给他们贡献一个 logo,再比如贡献一键部署功能。这或许是你迈向开源事业的第一步。 更多资料 heroku-button cloudbase 一键部署","categories":[{"name":"CD","slug":"CD","permalink":"https://lucifer.ren/blog/categories/CD/"}],"tags":[{"name":"CD","slug":"CD","permalink":"https://lucifer.ren/blog/tags/CD/"},{"name":"GitHub","slug":"GitHub","permalink":"https://lucifer.ren/blog/tags/GitHub/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第三期)","slug":"91-algo-3","date":"2021-01-18T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2021/01/19/91-algo-3/","link":"","permalink":"https://lucifer.ren/blog/2021/01/19/91-algo-3/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。 刚好碰上寒假,大家难道不想在寒假中给自己充下电么? 活动时间2021-02-01 至 2021-05-02 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少参与一次打卡 违反上述条件的人员会被强制清退 课程大纲 往期公开讲义: 【91 算法-基础篇】双指针 【91 算法-专题篇】动态规划(部分内容) 【91 算法-专题篇】二分法 三期会对题目和讲义进行再次加工,质量会更高, 敬请期待~ 自习 数据结构与算法概述 如何衡量算法的性能 如何更有效率刷题 1(视频) 如何更有效率刷题 2(视频) 基础篇(30 天) 数组,队列,栈 链表 树与递归 哈希表 双指针 图(加餐) 专题篇(31 天) 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 进阶篇(30 天) 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 由于可能会随着项目进行调整内容,因此章节顺序和内容可能会有变动,但变动不会很大。 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定私有仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 第二天会对前一天的题目进行讲解。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书,科学上网兑换码等 连续打卡七天可以获得补签卡一张哦 冲鸭采用 微信群的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 具体收费标准: 前 50 人免费 51 - 100 收费 10 元 101 - 500 收费 30 元 现在已经超过 100 名同学报名参加啦,因此需要发 30 元红包给我,拉你进群,我的微信号:DevelopeEngineer 熟悉 91 的小伙伴可能发现第三期涨价了。 其实涨价的目的是提供更好的服务,包括但不限于发放奖品,完善讲义,购买服务器(后期考虑),还望大家理解。 如果你经济实在困难可以参加下面的返现活动哦。 分享返现如果你没有抢到免费的学习机会也不要气馁。我们贴心地为大家搞了分享返现活动,手慢照样可以免费参加哦~ 活动规则: 发送宣传海报到你的朋友圈不屏蔽好友保留三天,三天之后加 lucifer 微信好友(微信号:DevelopeEngineer)进行验证,验证通过全额返现。 不到三天就没必要联系我验证了,必须不屏蔽好友满三天才可以验证。 朋友圈文案统一为: 91 天,遇见更好的自己。发送本海报到朋友圈,不屏蔽好友保留三天即可免费学习(文案需保留)。快扫描下方二维码报名吧! 海报: 朋友圈分享海报示例: FAQ Q:第三期和前两期内容一样吗? A:我们会不断进行迭代,比如第二期我们就制作了电子书给大家,方便大家阅读。此外,每一期讲义和题解都会不断更新,当然我们也会根据大家的反馈进行调整。 Q:零基础人群可以学习吗? A:只要掌握一门编程语言就可以学习。 Q:课程是用什么语言教学的? A:Java, Python,JS 都可能,不过算法涉及到的语言都比较基础,即使不了解,也完全可以学习。另外算法重要的是思想, 语言不重要,思路理解了比什么都重要。 Q:讲义和题解能够观看多久? A:为了有效督促学习,如果大家被违反规则被清退(具体见上方的规则部分),则不可以继续观看,否则可以长期观看。 Q:我该怎么学习? A:每一个小节开始之前都会提前把讲义公布到仓库,大家可以关注一下,提前预习。每天都会有一道题,第二天会公布前一天的题解,所有题解和讲义都在仓库中查看。另外我还介绍了一些学习方法, 具体参考上方的视频。 Q:我该怎么打卡? A:打卡只需要在对应讲义新建的 issue 下留言即可,注意格式要求。格式模板在先导篇哦~ Q: 只能当天打卡吗? 如果一周补打卡算吗? A: 是的。必须当天才能打卡,比如第七天的题, 那么只有那一天打卡才算打卡成功。如果你连续打卡七天可以获取一张补签卡,补签卡是虚拟计算用的(不会实际发放),每月结束我们会统计当月满勤的同学,如果你不满勤,但是使用补签卡后满勤也是可以的。也就是说必须当天打卡,需要补卡的必须有补签卡,补签卡的获得方式是连续打卡七天。 Q:微信群的作用是什么? A:重要信息都在群公告和仓库,大家注意这两个信息渠道即可。微信群用来交流一下简单的,容易回答的问题。一些复杂的问题大家可以提 issue。 Q:虽然你这么说,但是我还是不想错过微信群的重要信息怎么办? A:重要信息在仓库和群公告。如果大家还是怕错过重要群信息,可以按如下操作,仅看群主即可。 首先点击微信群右上角的按钮进入群设置,并翻到最下方。 点击“查找聊天内容”,然后进入“按群成员查找”。 找到需要查找聊天记录的人,比如 lucifer。 Q:Github 收到很多邮件,怎么取消? A:参考 https://www.bpteach.com/knowledge-base/1047564/ Q:仓库在哪里?怎么进? A:进群之后会在活动开始之前(2021-02-01),通过群公告的形式通知大家,大家耐心等待即可。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第二弹)","slug":"heap-2","date":"2021-01-18T16:00:00.000Z","updated":"2023-01-05T12:24:49.460Z","comments":true,"path":"2021/01/19/heap-2/","link":"","permalink":"https://lucifer.ren/blog/2021/01/19/heap-2/","excerpt":"一点题外话上次在我的公众号给大家做了一个小调查《投出你想要的题解编程语言吧~》。以下是调查的结果: 而关于其他,则大多数是 Go 语言。 由于 Java 和 Python 所占比例已经超过了 60%,这次我尝试一下 Java 和 Python 双语言来写,感谢 @CaptainZ 提供的 Java 代码。同时为了不让文章又臭又长,我将 Java 本文所有代码(Java 和 Python)都放到了力扣加加官网上,网站地址:https://leetcode-solution.cn/solution-code 如果不科学上网的话,可能打开会很慢。 正文 大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第一弹)","text":"一点题外话上次在我的公众号给大家做了一个小调查《投出你想要的题解编程语言吧~》。以下是调查的结果: 而关于其他,则大多数是 Go 语言。 由于 Java 和 Python 所占比例已经超过了 60%,这次我尝试一下 Java 和 Python 双语言来写,感谢 @CaptainZ 提供的 Java 代码。同时为了不让文章又臭又长,我将 Java 本文所有代码(Java 和 Python)都放到了力扣加加官网上,网站地址:https://leetcode-solution.cn/solution-code 如果不科学上网的话,可能打开会很慢。 正文 大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第一弹) 本次是下篇,没有看过上篇的同学强烈建议先阅读上篇几乎刷完了力扣所有的堆题,我发现了这些东西。。。(第一弹) 这是第二部分,后面的内容更加干货,分别是三个技巧和四大应用。这两个主题是专门教你怎么解题的。掌握了它,力扣中的大多数堆的题目都不在话下(当然我指的仅仅是题目中涉及到堆的部分)。 警告: 本章的题目基本都是力扣 hard 难度,这是因为堆的题目很多标记难度都不小,关于这点在前面也介绍过了。 一点说明在上主菜之前,先给大家来个开胃菜。 这里给大家介绍两个概念,分别是元组和模拟大顶堆 。之所以进行这些说明就是防止大家后面看不懂。 元组使用堆不仅仅可以存储单一值,比如 [1,2,3,4] 的 1,2,3,4 分别都是单一值。除了单一值,也可以存储复合值,比如对象或者元组等。 这里我们介绍一种存储元组的方式,这个技巧会在后面被广泛使用,请务必掌握。比如 [(1,2,3), (4,5,6), (2,1,3),(4,2,8)]。 1234567h = [(1,2,3), (4,5,6), (2,1,3),(4,2,8)]heapq.heappify(h) # 堆化(小顶堆)heapq.heappop() # 弹出 (1,2,3)heapq.heappop() # 弹出 (2,1,3)heapq.heappop() # 弹出 (4,2,8)heapq.heappop() # 弹出 (4,5,6) 用图来表示堆结构就是下面这样: 简单解释一下上面代码的执行结果。 使用元组的方式,默认将元组第一个值当做键来比较。如果第一个相同,继续比较第二个。比如上面的 (4,5,6) 和 (4,2,8),由于第一个值相同,因此继续比较后一个,又由于 5 比 2 大,因此 (4,2,8)先出堆。 使用这个技巧有两个作用: 携带一些额外的信息。 比如我想求二维矩阵中第 k 小数,当然是以值作为键。但是处理过程又需要用到其行和列信息,那么使用元组就很合适,比如 (val, row, col)这样的形式。 想根据两个键进行排序,一个主键一个副键。这里面又有两种典型的用法, 2.1 一种是两个都是同样的顺序,比如都是顺序或者都是逆序。 2.2 另一种是两个不同顺序排序,即一个是逆序一个是顺序。 由于篇幅原因,具体就不再这里展开了,大家在平时做题过程中留意可以一下,有机会我会单独开一篇文章讲解。 如果你所使用的编程语言没有堆或者堆的实现不支持元组,那么也可以通过简单的改造使其支持,主要就是自定义比较逻辑即可。 模拟大顶堆由于 Python 没有大顶堆。因此我这里使用了小顶堆进行模拟实现。即将原有的数全部取相反数,比如原数字是 5,就将 -5 入堆。经过这样的处理,小顶堆就可以当成大顶堆用了。不过需要注意的是,当你 pop 出来的时候, 记得也要取反,将其还原回来哦。 代码示例: 123456789h = []A = [1,2,3,4,5]for a in A: heapq.heappush(h, -a)-1 * heapq.heappop(h) # 5-1 * heapq.heappop(h) # 4-1 * heapq.heappop(h) # 3-1 * heapq.heappop(h) # 2-1 * heapq.heappop(h) # 1 用图来表示就是下面这样: 铺垫就到这里,接下来进入正题。 三个技巧技巧一 - 固定堆这个技巧指的是固定堆的大小 k 不变,代码上可通过每 pop 出去一个就 push 进来一个来实现。而由于初始堆可能是 0,我们刚开始需要一个一个 push 进堆以达到堆的大小为 k,因此严格来说应该是维持堆的大小不大于 k。 固定堆一个典型的应用就是求第 k 小的数。其实求第 k 小的数最简单的思路是建立小顶堆,将所有的数先全部入堆,然后逐个出堆,一共出堆 k 次。最后一次出堆的就是第 k 小的数。 然而,我们也可不先全部入堆,而是建立大顶堆(注意不是上面的小顶堆),并维持堆的大小为 k 个。如果新的数入堆之后堆的大小大于 k,则需要将堆顶的数和新的数进行比较,并将较大的移除。这样可以保证堆中的数是全体数字中最小的 k 个,而这最小的 k 个中最大的(即堆顶)不就是第 k 小的么?这也就是选择建立大顶堆,而不是小顶堆的原因。 简单一句话总结就是固定一个大小为 k 的大顶堆可以快速求第 k 小的数,反之固定一个大小为 k 的小顶堆可以快速求第 k 大的数。比如力扣 2020-02-24 的周赛第三题5663. 找出第 K 大的异或坐标值就可以用固定小顶堆技巧来实现(这道题让你求第 k 大的数)。 这么说可能你的感受并不强烈,接下来我给大家举两个例子来帮助大家加深印象。 295. 数据流的中位数题目描述1234567891011121314151617181920212223中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。例如,[2,3,4] 的中位数是 3[2,3] 的中位数是 (2 + 3) / 2 = 2.5设计一个支持以下两种操作的数据结构:void addNum(int num) - 从数据流中添加一个整数到数据结构中。double findMedian() - 返回目前所有元素的中位数。示例:addNum(1)addNum(2)findMedian() -> 1.5addNum(3)findMedian() -> 2进阶:如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法? 思路这道题实际上可看出是求第 k 小的数的特例了。 如果列表长度是奇数,那么 k 就是 (n + 1) / 2,中位数就是第 k 个数,。比如 n 是 5, k 就是 (5 + 1)/ 2 = 3。 如果列表长度是偶数,那么 k 就是 (n + 1) / 2 和 (n + 1) / 2 + 1,中位数则是这两个数的平均值。比如 n 是 6, k 就是 (6 + 1)/ 2 = 3 和 (6 + 1) / 2 + 1 = 4。 因此我们的可以维护两个固定堆,固定堆的大小为 $(n + 1) \\div 2$ 和 $n - (n + 1)\\div2$,也就是两个堆的大小最多相差 1,更具体的就是 $ 0 <= (n + 1) \\div 2 - (n - (n + 1) \\div 2) <= 1$。 基于上面提到的知识,我们可以: 建立一个大顶堆,并存放最小的 $(n + 1) \\div 2$ 个数,这样堆顶的数就是第 $(n + 1) \\div 2$ 小的数,也就是奇数情况的中位数。 建立一个小顶堆,并存放最大的 n - $(n + 1) \\div 2$ 个数,这样堆顶的数就是第 n - $(n + 1) \\div 2$ 大的数,结合上面的大顶堆,可求出偶数情况的中位数。 有了这样一个知识,剩下的只是如何维护两个堆的大小了。 如果大顶堆的个数比小顶堆少,那么就将小顶堆中最小的转移到大顶堆。而由于小顶堆维护的是最大的 k 个数,大顶堆维护的是最小的 k 个数,因此小顶堆堆顶一定大于等于大顶堆堆顶,并且这两个堆顶是此时的中位数。 如果大顶堆的个数比小顶堆的个数多 2,那么就将大顶堆中最大的转移到小顶堆,理由同上。 至此,可能你已经明白了为什么分别建立两个堆,并且需要一个大顶堆一个小顶堆。这其中的原因正如上面所描述的那样。 固定堆的应用常见还不止于此,我们继续看一道题。 代码12345678910111213141516class MedianFinder: def __init__(self): self.min_heap = [] self.max_heap = [] def addNum(self, num: int) -> None: if not self.max_heap or num < -self.max_heap[0]: heapq.heappush(self.max_heap, -num) else: heapq.heappush(self.min_heap, num) if len(self.max_heap) > len(self.min_heap) + 1: heappush(self.min_heap, -heappop(self.max_heap)) elif len(self.min_heap) > len(self.max_heap): heappush(self.max_heap, -heappop(self.min_heap)) def findMedian(self) -> float: if len(self.min_heap) == len(self.max_heap): return (self.min_heap[0] - self.max_heap[0]) / 2 return -self.max_heap[0] (代码 1.3.1) 857. 雇佣 K 名工人的最低成本题目描述12345678910111213141516171819202122232425262728有 N 名工人。 第 i 名工人的工作质量为 quality[i] ,其最低期望工资为 wage[i] 。现在我们想雇佣 K 名工人组成一个工资组。在雇佣 一组 K 名工人时,我们必须按照下述规则向他们支付工资:对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。工资组中的每名工人至少应当得到他们的最低期望工资。返回组成一个满足上述条件的工资组至少需要多少钱。 示例 1:输入: quality = [10,20,5], wage = [70,50,30], K = 2输出: 105.00000解释: 我们向 0 号工人支付 70,向 2 号工人支付 35。示例 2:输入: quality = [3,1,10,10,1], wage = [4,8,2,2,7], K = 3输出: 30.66667解释: 我们向 0 号工人支付 4,向 2 号和 3 号分别支付 13.33333。 提示:1 <= K <= N <= 10000,其中 N = quality.length = wage.length1 <= quality[i] <= 100001 <= wage[i] <= 10000与正确答案误差在 10^-5 之内的答案将被视为正确的。 思路题目要求我们选择 k 个人,按其工作质量与同组其他工人的工作质量的比例来支付工资,并且工资组中的每名工人至少应当得到他们的最低期望工资。 换句话说,同一组的 k 个人他们的工作质量和工资比是一个固定值才能使支付的工资最少。请先理解这句话,后面的内容都是基于这个前提产生的。 我们不妨定一个指标工作效率,其值等于 q / w。前面说了这 k 个人的 q / w 是相同的才能保证工资最少,并且这个 q / w 一定是这 k 个人最低的(短板),否则一定会有人得不到最低期望工资。 于是我们可以写出下面的代码: 123456789101112131415161718class Solution: def mincostToHireWorkers(self, quality: List[int], wage: List[int], K: int) -> float: eff = [(q / w, q, w) for a, b in zip(quality, wage)] eff.sort(key=lambda a: -a[0]) ans = float('inf') for i in range(K-1, len(eff)): h = [] k = K - 1 rate, _, total = eff[i] # 找出工作效率比它高的 k 个人,这 k 个人的工资尽可能低。 # 由于已经工作效率倒序排了,因此前面的都是比它高的,然后使用堆就可得到 k 个工资最低的。 for j in range(i): heapq.heappush(h, eff[j][1] / rate) while k > 0: total += heapq.heappop(h) k -= 1 ans = min(ans, total) return ans (代码 1.3.2) 这种做法每次都 push 很多数,并 pop k 次,并没有很好地利用堆的动态特性,而只利用了其求极值的特性。 一个更好的做法是使用固定堆技巧。 这道题可以换个角度思考。其实这道题不就是让我们选 k 个人,工作效率比取他们中最低的,并按照这个最低的工作效率计算总工资,找出最低的总工资么? 因此这道题可以固定一个大小为 k 的大顶堆,通过一定操作保证堆顶的就是第 k 小的(操作和前面的题类似)。 并且前面的解法中堆使用了三元组 (q / w, q, w),实际上这也没有必要。因为已知其中两个,可推导出另外一个,因此存储两个就行了,而又由于我们需要根据工作效率比做堆的键,因此任意选一个 q 或者 w 即可,这里我选择了 q,即存 (q/2, q) 二元组。 具体来说就是:以 rate 为最低工作效率比的 k 个人的总工资 = $\\displaystyle \\sum_{n=1}^{k}{q}_{n}/rate$,这里的 rate 就是当前的 q / w,同时也是 k 个人的 q / w 的最小值。 代码123456789101112131415class Solution: def mincostToHireWorkers(self, quality: List[int], wage: List[int], K: int) -> float: effs = [(q / w, q) for q, w in zip(quality, wage)] effs.sort(key=lambda a: -a[0]) ans = float('inf') h = [] total = 0 for rate, q in effs: heapq.heappush(h, -q) total += q if len(h) > K: total += heapq.heappop(h) if len(h) == K: ans = min(ans, total / rate) return ans (代码 1.3.3) 技巧二 - 多路归并这个技巧其实在前面讲超级丑数的时候已经提到了,只是没有给这种类型的题目一个名字。 其实这个技巧,叫做多指针优化可能会更合适,只不过这个名字实在太过朴素且容易和双指针什么的混淆,因此我给 ta 起了个别致的名字 - 多路归并。 多路体现在:有多条候选路线。代码上,我们可使用多指针来表示。 归并体现在:结果可能是多个候选路线中最长的或者最短,也可能是第 k 个 等。因此我们需要对多条路线的结果进行比较,并根据题目描述舍弃或者选取某一个或多个路线。 这样描述比较抽象,接下来通过几个例子来加深一下大家的理解。 这里我给大家精心准备了四道难度为 hard 的题目。 掌握了这个套路就可以去快乐地 AC 这四道题啦。 1439. 有序矩阵中的第 k 个最小数组和题目描述123456789101112131415161718192021222324252627282930313233343536给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。你可以从每一行中选出 1 个元素形成一个数组。返回所有可能数组中的第 k 个 最小 数组和。 示例 1:输入:mat = [[1,3,11],[2,4,6]], k = 5输出:7解释:从每一行中选出一个元素,前 k 个和最小的数组分别是:[1,2], [1,4], [3,2], [3,4], [1,6]。其中第 5 个的和是 7 。示例 2:输入:mat = [[1,3,11],[2,4,6]], k = 9输出:17示例 3:输入:mat = [[1,10,10],[1,4,5],[2,3,6]], k = 7输出:9解释:从每一行中选出一个元素,前 k 个和最小的数组分别是:[1,1,2], [1,1,3], [1,4,2], [1,4,3], [1,1,6], [1,5,2], [1,5,3]。其中第 7 个的和是 9 。示例 4:输入:mat = [[1,1,10],[2,2,9]], k = 7输出:12 提示:m == mat.lengthn == mat.length[i]1 <= m, n <= 401 <= k <= min(200, n ^ m)1 <= mat[i][j] <= 5000mat[i] 是一个非递减数组 思路其实这道题就是给你 m 个长度均相同的一维数组,让我们从这 m 个数组中分别选出一个数,即一共选取 m 个数,求这 m 个数的和是所有选取可能性中和第 k 小的。 一个朴素的想法是使用多指针来解。对于这道题来说就是使用 m 个指针,分别指向 m 个一维数组,指针的位置表示当前选取的是该一维数组中第几个。 以题目中的 mat = [[1,3,11],[2,4,6]], k = 5 为例。 先初始化两个指针 p1,p2,分别指向两个一维数组的开头,代码表示就是全部初始化为 0。 此时两个指针指向的数字和为 1 + 2 = 3,这就是第 1 小的和。 接下来,我们移动其中一个指针。此时我们可以移动 p1,也可以移动 p2。 那么第 2 小的一定是移动 p1 和 移动 p2 这两种情况的较小值。而这里移动 p1 和 p2 实际上都会得到 5,也就是说第 2 和第 3 小的和都是 5。 到这里已经分叉了,出现了两种情况(注意看粗体的位置,粗体表示的是指针的位置): [1,3,11],[2,4,6] 和为 5 [1,3,11],[2,4,6] 和为 5 接下来,这两种情况应该齐头并进,共同进行下去。 对于情况 1 来说,接下来移动又有两种情况。 [1,3,11],[2,4,6] 和为 13 [1,3,11],[2,4,6] 和为 7 对于情况 2 来说,接下来移动也有两种情况。 [1,3,11],[2,4,6] 和为 7 [1,3,11],[2,4,6] 和为 7 我们通过比较这四种情况,得出结论: 第 4,5,6 小的数都是 7。但第 7 小的数并不一定是 13。原因和上面类似,可能第 7 小的就隐藏在前面的 7 分裂之后的新情况中,实际上确实如此。因此我们需要继续执行上述逻辑。 进一步,我们可以将上面的思路拓展到一般情况。 上面提到了题目需要求的其实是第 k 小的和,而最小的我们是容易知道的,即所有的一维数组首项和。我们又发现,根据最小的,我们可以推导出第 2 小,推导的方式就是移动其中一个指针,这就一共分裂出了 n 种情况了,其中 n 为一维数组长度,第 2 小的就在这分裂中的 n 种情况中,而筛选的方式是这 n 种情况和最小的,后面的情况也是类似。不难看出每次分裂之后极值也发生了变化,因此这是一个明显的求动态求极值的信号,使用堆是一个不错的选择。 那代码该如何书写呢? 上面说了,我们先要初始化 m 个指针,并赋值为 0。对应伪代码: 12345678# 初始化堆h = []# sum(vec[0] for vec in mat) 是 m 个一维数组的首项和# [0] * m 就是初始化了一个长度为 m 且全部填充为 0 的数组。# 我们将上面的两个信息组装成元祖 cur 方便使用cur = (sum(vec[0] for vec in mat), [0] * m)# 将其入堆heapq.heappush(h, cur) 接下来,我们每次都移动一个指针,从而形成分叉出一条新的分支。每次从堆中弹出一个最小的,弹出 k 次就是第 k 小的了。伪代码: 123456789101112for 1 to K: # acc 当前的和, pointers 是指针情况。 acc, pointers = heapq.heappop(h) # 每次都粗暴地移动指针数组中的一个指针。每移动一个指针就分叉一次, 一共可能移动的情况是 n,其中 n 为一维数组的长度。 for i, pointer in enumerate(pointers): # 如果 pointer == len(mat[0]) - 1 说明到头了,不能移动了 if pointer != len(mat[0]) - 1: # 下面两句话的含义是修改 pointers[i] 的指针 为 pointers[i] + 1 new_pointers = pointers.copy() new_pointers[i] += 1 # 将更新后的 acc 和指针数组重新入堆 heapq.heappush(h, (acc + mat[i][pointer + 1] - mat[i][pointer], new_pointers)) 这是多路归并问题的核心代码,请务必记住。 代码看起来很多,其实去掉注释一共才七行而已。 上面的伪代码有一个问题。比如有两个一维数组,指针都初始化为 0。第一次移动第一个一维数组的指针,第二次移动第二个数组的指针,此时指针数组为 [1, 1],即全部指针均指向下标为 1 的元素。而如果第一次移动第二个一维数组的指针,第二次移动第一个数组的指针,此时指针数组仍然为 [1, 1]。这实际上是一种情况,如果不加控制会被计算两次导致出错。 一个可能的解决方案是使用 hashset 记录所有的指针情况,这样就避免了同样的指针被计算多次的问题。为了做到这一点,我们需要对指针数组的使用做一些微调,即使用元组代替数组。原因在于数组是无法直接哈希化的。具体内容请参考代码区。 多路归并的题目,思路和代码都比较类似。为了后面的题目能够更高地理解,请务必搞定这道题,后面我们将不会这么详细地进行分析。 代码123456789101112131415161718class Solution: def kthSmallest(self, mat, k: int) -> int: h = [] cur = (sum(vec[0] for vec in mat), tuple([0] * len(mat))) heapq.heappush(h, cur) seen = set(cur) for _ in range(k): acc, pointers = heapq.heappop(h) for i, pointer in enumerate(pointers): if pointer != len(mat[0]) - 1: t = list(pointers) t[i] = pointer + 1 tt = tuple(t) if tt not in seen: seen.add(tt) heapq.heappush(h, (acc + mat[i][pointer + 1] - mat[i][pointer], tt)) return acc (代码 1.3.4) 719. 找出第 k 小的距离对题目描述12345678910111213141516171819给定一个整数数组,返回所有数对之间的第 k 个最小距离。一对 (A, B) 的距离被定义为 A 和 B 之间的绝对差值。示例 1:输入:nums = [1,3,1]k = 1输出:0解释:所有数对如下:(1,3) -> 2(1,1) -> 0(3,1) -> 2因此第 1 个最小距离的数对是 (1,1),它们之间的距离为 0。提示:2 <= len(nums) <= 10000.0 <= nums[i] < 1000000.1 <= k <= len(nums) * (len(nums) - 1) / 2. 思路不难看出所有的数对可能共 $C_n^2$ 个,也就是 $n\\times(n-1)\\div2$。 因此我们可以使用两次循环找出所有的数对,并升序排序,之后取第 k 个。 实际上,我们可使用固定堆技巧,维护一个大小为 k 的大顶堆,这样堆顶的元素就是第 k 小的,这在前面的固定堆中已经讲过,不再赘述。 12345678910111213class Solution: def smallestDistancePair(self, nums: List[int], k: int) -> int: h = [] for i in range(len(nums)): for j in range(i + 1, len(nums)): a, b = nums[i], nums[j] # 维持堆大小不超过 k if len(h) == k and -abs(a - b) > h[0]: heapq.heappop(h) if len(h) < k: heapq.heappush(h, -abs(a - b)) return -h[0] (代码 1.3.5) 不过这种优化意义不大,因为算法的瓶颈在于 $N^2$ 部分的枚举,我们应当设法优化这一点。 如果我们将数对进行排序,那么最小的数对距离一定在 nums[i] - nums[i - 1] 中,其中 i 为从 1 到 n 的整数,究竟是哪个取决于谁更小。接下来就可以使用上面多路归并的思路来解决了。 如果 nums[i] - nums[i - 1] 的差是最小的,那么第 2 小的一定是剩下的 n - 1 种情况和 nums[i] - nums[i - 1] 分裂的新情况。关于如何分裂,和上面类似,我们只需要移动其中 i 的指针为 i + 1 即可。这里的指针数组长度固定为 2,而不是上面题目中的 m。这里我将两个指针分别命名为 fr 和 to,分别代表 from 和 to。 代码12345678910111213class Solution(object): def smallestDistancePair(self, nums, k): nums.sort() # n 种候选答案 h = [(nums[i+1] - nums[i], i, i+1) for i in range(len(nums) - 1)] heapq.heapify(h) for _ in range(k): diff, fr, to = heapq.heappop(h) if to + 1 < len(nums): heapq.heappush((nums[to + 1] - nums[fr], fr, to + 1)) return diff (代码 1.3.6) 由于时间复杂度和 k 有关,而 k 最多可能达到 $N^2$ 的量级,因此此方法实际上也会超时。不过这证明了这种思路的正确性,如果题目稍加改变说不定就能用上。 这道题可通过二分法来解决,由于和堆主题有偏差,因此这里简单讲一下。 求第 k 小的数比较容易想到的就是堆和二分法。二分的原因在于求第 k 小,本质就是求不大于其本身的有 k - 1 个的那个数。而这个问题很多时候满足单调性,因此就可使用二分来解决。 以这道题来说,最大的数对差就是数组的最大值 - 最小值,不妨记为 max_diff。我们可以这样发问: 数对差小于 max_diff 的有几个? 数对差小于 max_diff - 1 的有几个? 数对差小于 max_diff - 2 的有几个? 数对差小于 max_diff - 3 的有几个? 数对差小于 max_diff - 4 的有几个? 。。。 而我们知道,发问的答案也是不严格递减的,因此使用二分就应该被想到。我们不断发问直到问到小于 x 的有 k - 1 个即可。然而这样的发问也有问题。原因有两个: 小于 x 的有 k - 1 个的数可能不止一个 我们无法确定小于 x 的有 k - 1 个的数一定存在。 比如数对差分别为 [1,1,1,1,2],让你求第 3 大的,那么小于 x 有两个的数根本就不存在。 我们的思路可调整为求小于等于 x 有 k 个的,接下来我们使用二分法的最左模板即可解决。关于最左模板可参考我的二分查找专题 代码: 123456789101112131415161718192021class Solution: def smallestDistancePair(self, A: List[int], K: int) -> int: A.sort() l, r = 0, A[-1] - A[0] def count_ngt(mid): slow = 0 ans = 0 for fast in range(len(A)): while A[fast] - A[slow] > mid: slow += 1 ans += fast - slow return ans while l <= r: mid = (l + r) // 2 if count_ngt(mid) >= K: r = mid - 1 else: l = mid + 1 return l (代码 1.3.7) 632. 最小区间题目描述123456789101112131415161718192021222324252627282930313233343536373839你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。 示例 1:输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]输出:[20,24]解释:列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。示例 2:输入:nums = [[1,2,3],[1,2,3],[1,2,3]]输出:[1,1]示例 3:输入:nums = [[10,10],[11,11]]输出:[10,11]示例 4:输入:nums = [[10],[11]]输出:[10,11]示例 5:输入:nums = [[1],[2],[3],[4],[5],[6],[7]]输出:[1,7] 提示:nums.length == k1 <= k <= 35001 <= nums[i].length <= 50-105 <= nums[i][j] <= 105nums[i] 按非递减顺序排列 思路这道题本质上就是在 m 个一维数组中各取出一个数字,重新组成新的数组 A,使得新的数组 A 中最大值和最小值的差值(diff)最小。 这道题和上面的题目有点类似,又略有不同。这道题是一个矩阵,上面一道题是一维数组。不过我们可以将二维矩阵看出一维数组,这样我们就可以沿用上面的思路了。 上面的思路 diff 最小的一定产生于排序之后相邻的元素之间。而这道题我们无法直接对二维数组进行排序,而且即使进行排序,也不好确定排序的原则。 我们其实可以继续使用前面两道题的思路。具体来说就是使用小顶堆获取堆中最小值,进而通过一个变量记录堆中的最大值,这样就知道了 diff,每次更新指针都会产生一个新的 diff,不断重复这个过程并维护全局最小 diff 即可。 这种算法的成立的前提是 k 个列表都是升序排列的,这里需要数组升序原理和上面题目是一样的,有序之后就可以对每个列表维护一个指针,进而使用上面的思路解决。 以题目中的 nums = [[1,2,3],[1,2,3],[1,2,3]] 为例: [1,2,3] [1,2,3] [1,2,3] 我们先选取所有行的最小值,也就是 [1,1,1],这时的 diff 为 0,全局最大值为 1,最小值也为 1。接下来,继续寻找备胎,看有没有更好的备胎供我们选择。 接下来的备胎可能产生于情况 1: [1,2,3] [1,2,3] [1,2,3] 移动了这行的指针,将其从原来的 0 移动一个单位到达 1。 或者情况 2: [1,2,3] [1,2,3]移动了这行的指针,将其从原来的 0 移动一个单位到达 1。 [1,2,3] 。。。 这几种情况又继续分裂更多的情况,这个就和上面的题目一样了,不再赘述。 代码123456789101112131415161718class Solution: def smallestRange(self, martrix: List[List[int]]) -> List[int]: l, r = -10**9, 10**9 # 将每一行最小的都放到堆中,同时记录其所在的行号和列号,一共 n 个齐头并进 h = [(row[0], i, 0) for i, row in enumerate(martrix)] heapq.heapify(h) # 维护最大值 max_v = max(row[0] for row in martrix) while True: min_v, row, col = heapq.heappop(h) # max_v - min_v 是当前的最大最小差值, r - l 为全局的最大最小差值。因为如果当前的更小,我们就更新全局结果 if max_v - min_v < r - l: l, r = min_v, max_v if col == len(martrix[row]) - 1: return [l, r] # 更新指针,继续往后移动一位 heapq.heappush(h, (martrix[row][col + 1], row, col + 1)) max_v = max(max_v, martrix[row][col + 1]) (代码 1.3.8) 1675. 数组的最小偏移量题目描述1234567891011121314151617181920212223242526272829303132给你一个由 n 个正整数组成的数组 nums 。你可以对数组的任意元素执行任意次数的两类操作:如果元素是 偶数 ,除以 2例如,如果数组是 [1,2,3,4] ,那么你可以对最后一个元素执行此操作,使其变成 [1,2,3,2]如果元素是 奇数 ,乘上 2例如,如果数组是 [1,2,3,4] ,那么你可以对第一个元素执行此操作,使其变成 [2,2,3,4]数组的 偏移量 是数组中任意两个元素之间的 最大差值 。返回数组在执行某些操作之后可以拥有的 最小偏移量 。示例 1:输入:nums = [1,2,3,4]输出:1解释:你可以将数组转换为 [1,2,3,2],然后转换成 [2,2,3,2],偏移量是 3 - 2 = 1示例 2:输入:nums = [4,1,5,20,3]输出:3解释:两次操作后,你可以将数组转换为 [4,2,5,5,3],偏移量是 5 - 2 = 3示例 3:输入:nums = [2,10,8]输出:3提示:n == nums.length2 <= n <= 1051 <= nums[i] <= 109 思路题目说可对数组中每一项都执行任意次操作,但其实操作是有限的。 我们只能对奇数进行一次 2 倍操作,因为 2 倍之后其就变成了偶数了。 我们可以对偶数进行若干次除 2 操作,直到等于一个奇数,不难看出这也是一个有限次的操作。 以题目中的 [1,2,3,4] 来说。我们可以: 将 1 变成 2(也可以不变) 将 2 变成 1(也可以不变) 将 3 变成 6(也可以不变) 将 4 变成 2 或 1(也可以不变) 用图来表示就是下面这样的: 这不就相当于: 从 [[1,2], [1,2], [3,6], [1,2,4]] 这样的一个二维数组中的每一行分别选取一个数,并使得其差最小么?这难道不是和上面的题目一模一样么? 这里我直接将上面的题目解法封装成了一个 api 调用了,具体看代码。 代码1234567891011121314151617181920212223242526272829303132class Solution: def smallestRange(self, martrix: List[List[int]]) -> List[int]: l, r = -10**9, 10**9 # 将每一行最小的都放到堆中,同时记录其所在的行号和列号,一共 n 个齐头并进 h = [(row[0], i, 0) for i, row in enumerate(martrix)] heapq.heapify(h) # 维护最大值 max_v = max(row[0] for row in martrix) while True: min_v, row, col = heapq.heappop(h) # max_v - min_v 是当前的最大最小差值, r - l 为全局的最大最小差值。因为如果当前的更小,我们就更新全局结果 if max_v - min_v < r - l: l, r = min_v, max_v if col == len(martrix[row]) - 1: return [l, r] # 更新指针,继续往后移动一位 heapq.heappush(h, (martrix[row][col + 1], row, col + 1)) max_v = max(max_v, martrix[row][col + 1]) def minimumDeviation(self, nums: List[int]) -> int: matrix = [[] for _ in range(len(nums))] for i, num in enumerate(nums): if num & 1 == 1: matrix[i] += [num, num * 2] else: temp = [] while num and num & 1 == 0: temp += [num] num //= 2 temp += [num] matrix[i] += temp[::-1] a, b = self.smallestRange(matrix) return b - a (代码 1.3.9) 技巧三 - 事后小诸葛 这个技巧指的是:当从左到右遍历的时候,我们是不知道右边是什么的,需要等到你到了右边之后才知道。 如果想知道右边是什么,一种简单的方式是遍历两次,第一次遍历将数据记录下来,当第二次遍历的时候,用上次遍历记录的数据。这是我们使用最多的方式。不过有时候,我们也可以在遍历到指定元素后,往前回溯,这样就可以边遍历边存储,使用一次遍历即可。具体来说就是将从左到右的数据全部收集起来,等到需要用的时候,从里面挑一个用。如果我们都要取最大值或者最小值且极值会发生变动, 就可使用堆加速。直观上就是使用了时光机回到之前,达到了事后诸葛亮的目的。 这样说你肯定不明白啥意思。没关系,我们通过几个例子来讲一下。当你看完这些例子之后,再回头看这句话。 871. 最低加油次数题目描述1234567891011121314151617181920212223242526272829303132333435363738394041汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。沿途有加油站,每个 station[i] 代表一个加油站,它位于出发位置东面 station[i][0] 英里处,并且有 station[i][1] 升汽油。假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。 示例 1:输入:target = 1, startFuel = 1, stations = []输出:0解释:我们可以在不加油的情况下到达目的地。示例 2:输入:target = 100, startFuel = 1, stations = [[10,100]]输出:-1解释:我们无法抵达目的地,甚至无法到达第一个加油站。示例 3:输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]输出:2解释:我们出发时有 10 升燃料。我们开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。然后,我们从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料),并将汽油从 10 升加到 50 升。然后我们开车抵达目的地。我们沿途在1两个加油站停靠,所以返回 2 。 提示:1 <= target, startFuel, stations[i][1] <= 10^90 <= stations.length <= 5000 < stations[0][0] < stations[1][0] < ... < stations[stations.length-1][0] < target 思路为了能够获得最低加油次数,我们肯定希望能不加油就不加油。那什么时候必须加油呢?答案应该是如果你不加油,就无法到达下一个目的地的时候。 伪代码描述就是: 12345678cur = startFuel # 刚开始有 startFuel 升汽油last = 0 # 上一次的位置for i, fuel in stations: cur -= i - last # 走过两个 staton 的耗油为两个 station 的距离,也就是 i - last if cur < 0: # 我们必须在前面就加油,否则到不了这里 # 但是在前面的哪个 station 加油呢? # 直觉告诉我们应该贪心地选择可以加汽油最多的站 i,如果加上 i 的汽油还是 cur < 0,继续加次大的站 j,直到没有更多汽油可加或者 cur > 0 上面说了要选择可以加汽油最多的站 i,如果加了油还不行,继续选择第二多的站。这种动态求极值的场景非常适合使用 heap。 具体来说就是: 每经过一个站,就将其油量加到堆。 尽可能往前开,油只要不小于 0 就继续开。 如果油量小于 0 ,就从堆中取最大的加到油箱中去,如果油量还是小于 0 继续重复取堆中的最大油量。 如果加完油之后油量大于 0 ,继续开,重复上面的步骤。否则返回 -1,表示无法到达目的地。 那这个算法是如何体现事后小诸葛的呢?你可以把自己代入到题目中进行模拟。 把自己想象成正在开车,你的目标就是题目中的要求:最少加油次数。当你开到一个站的时候,你是不知道你的油量够不够支撑到下个站的,并且就算撑不到下个站,其实也许在上个站加油会更好。所以现实中你无论如何都无法知道在当前站,我是应该加油还是不加油的,因为信息太少了。 那我会怎么做呢?如果是我在开车的话,我只能每次都加油,这样都无法到达目的地,那肯定就无法到达目的地了。但如果这样可以到达目的地,我就可以说如果我们在那个站加油,这个站选择不加就可以最少加油次数到达目的地了。你怎么不早说呢? 这不就是事后诸葛亮么? 这个事后诸葛亮体现在我们是等到没油了才去想应该在之前的某个站加油。 所以这个事后诸葛亮本质上解决的是,基于当前信息无法获取最优解,我们必须掌握全部信息之后回溯。以这道题来说,我们可以先遍历一边 station,然后将每个 station 的油量记录到一个数组中,每次我们“预见“到无法到达下个站的时候,就从这个数组中取最大的。。。。 基于此,我们可以考虑使用堆优化取极值的过程,而不是使用数组的方式。 代码12345678910111213141516171819class Solution: def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int: stations += [(target, 0)] cur = startFuel ans = 0 h = [] last = 0 for i, fuel in stations: cur -= i - last while cur < 0 and h: cur -= heapq.heappop(h) ans += 1 if cur < 0: return -1 heappush(h, -fuel) last = i return ans (代码 1.3.10) 1488. 避免洪水泛滥题目描述1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨的时候,如果第 n 个湖泊是空的,那么它就会装满水,否则这个湖泊会发生洪水。你的目标是避免任意一个湖泊发生洪水。给你一个整数数组 rains ,其中:rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。请返回一个数组 ans ,满足:ans.length == rains.length如果 rains[i] > 0 ,那么ans[i] == -1 。如果 rains[i] == 0 ,ans[i] 是你第 i 天选择抽干的湖泊。如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组 。请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生(详情请看示例 4)。 示例 1:输入:rains = [1,2,3,4]输出:[-1,-1,-1,-1]解释:第一天后,装满水的湖泊包括 [1]第二天后,装满水的湖泊包括 [1,2]第三天后,装满水的湖泊包括 [1,2,3]第四天后,装满水的湖泊包括 [1,2,3,4]没有哪一天你可以抽干任何湖泊的水,也没有湖泊会发生洪水。示例 2:输入:rains = [1,2,0,0,2,1]输出:[-1,-1,2,1,-1,-1]解释:第一天后,装满水的湖泊包括 [1]第二天后,装满水的湖泊包括 [1,2]第三天后,我们抽干湖泊 2 。所以剩下装满水的湖泊包括 [1]第四天后,我们抽干湖泊 1 。所以暂时没有装满水的湖泊了。第五天后,装满水的湖泊包括 [2]。第六天后,装满水的湖泊包括 [1,2]。可以看出,这个方案下不会有洪水发生。同时, [-1,-1,1,2,-1,-1] 也是另一个可行的没有洪水的方案。示例 3:输入:rains = [1,2,0,1,2]输出:[]解释:第二天后,装满水的湖泊包括 [1,2]。我们可以在第三天抽干一个湖泊的水。但第三天后,湖泊 1 和 2 都会再次下雨,所以不管我们第三天抽干哪个湖泊的水,另一个湖泊都会发生洪水。示例 4:输入:rains = [69,0,0,0,69]输出:[-1,69,1,1,-1]解释:任何形如 [-1,69,x,y,-1], [-1,x,69,y,-1] 或者 [-1,x,y,69,-1] 都是可行的解,其中 1 <= x,y <= 10^9示例 5:输入:rains = [10,20,20]输出:[]解释:由于湖泊 20 会连续下 2 天的雨,所以没有没有办法阻止洪水。 提示:1 <= rains.length <= 10^50 <= rains[i] <= 10^9 思路如果上面的题用事后诸葛亮描述比较牵强的话,那后面这两个题可以说很适合了。 题目说明了我们可以在不下雨的时候抽干一个湖泊,如果有多个下满雨的湖泊,我们该抽干哪个湖呢?显然应该是抽干最近即将被洪水淹没的湖。但是现实中无论如何我们都不可能知道未来哪天哪个湖泊会下雨的,即使有天气预报也不行,因此它也不 100% 可靠。 但是代码可以啊。我们可以先遍历一遍 rain 数组就知道第几天哪个湖泊下雨了。有了这个信息,我们就可以事后诸葛亮了。 “今天天气很好,我开了天眼,明天湖泊 2 会被洪水淹没,我们今天就先抽干它,否则就洪水泛滥了。”。 和上面的题目一样,我们也可以不先遍历 rain 数组,再模拟每天的变化,而是直接模拟,即使当前是晴天我们也不抽干任何湖泊。接着在模拟的过程记录晴天的情况,等到洪水发生的时候,我们再考虑前面哪一个晴天应该抽干哪个湖泊。因此这个事后诸葛亮体现在我们是等到洪水泛滥了才去想应该在之前的某天采取什么手段。 算法: 遍历 rain, 模拟每天的变化 如果 rain 当前是 0 表示当前是晴天,我们不抽干任何湖泊。但是我们将当前天记录到 sunny 数组。 如果 rain 大于 0,说明有一个湖泊下雨了,我们去看下下雨的这个湖泊是否发生了洪水泛滥。其实就是看下下雨前是否已经有水了。这提示我们用一个数据结构 lakes 记录每个湖泊的情况,我们可以用 0 表示没有水,1 表示有水。这样当湖泊 i 下雨的时候且 lakes[i] = 1 就会发生洪水泛滥。 如果当前湖泊发生了洪水泛滥,那么就去 sunny 数组找一个晴天去抽干它,这样它就不会洪水泛滥,接下来只需要保持 lakes[i] = 1 即可。 这道题没有使用到堆,我是故意的。之所以这么做,是让大家明白事后诸葛亮这个技巧并不是堆特有的,实际上这就是一种普通的算法思想,就好像从后往前遍历一样。只不过,很多时候,我们事后诸葛亮的场景,需要动态取最大最小值, 这个时候就应该考虑使用堆了,这其实又回到文章开头的一个中心了,所以大家一定要灵活使用这些技巧,不可生搬硬套。 下一道题是一个不折不扣的事后诸葛亮 + 堆优化的题目。 代码1234567891011121314151617class Solution: def avoidFlood(self, rains: List[int]) -> List[int]: ans = [1] * len(rains) lakes = collections.defaultdict(int) sunny = [] for i, rain in enumerate(rains): if rain > 0: ans[i] = -1 if lakes[rain - 1] == 1: if 0 == len(sunny): return [] ans[sunny.pop()] = rain lakes[rain - 1] = 1 else: sunny.append(i) return ans (代码 1.3.11) 2021-04-06 fixed: 上面的代码有问题。错误的原因在于上述算法如果当前湖泊发生了洪水泛滥,那么就去 sunny 数组找一个晴天去抽干它,这样它就不会洪水泛滥部分的实现不对。sunny 数组找一个晴天去抽干它的根本前提是 出现晴天的时候湖泊里面要有水才能抽,如果晴天的时候,湖泊里面没有水也不行。这提示我们的 lakes 不存储 0 和 1 ,而是存储发生洪水是第几天。这样问题就变为在 sunny 中找一个日期大于 lakes[rain-1] 的项,并将其移除 sunny 数组。由于 sunny 数组是有序的,因此我们可以使用二分来进行查找。 由于我们需要删除 sunny 数组的项,因此时间复杂度不会因为使用了二分而降低。 正确的代码应该为: 123456789101112131415161718class Solution: def avoidFlood(self, rains: List[int]) -> List[int]: ans = [1] * len(rains) lakes = {} sunny = [] for i, rain in enumerate(rains): if rain > 0: ans[i] = -1 if rain - 1 in lakes: j = bisect.bisect_left(sunny, lakes[rain - 1]) if j == len(sunny): return [] ans[sunny.pop(j)] = rain lakes[rain - 1] = i else: sunny.append(i) return ans 1642. 可以到达的最远建筑题目描述123456789给你一个整数数组 heights ,表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。你从建筑物 0 开始旅程,不断向后面的建筑物移动,期间可能会用到砖块或梯子。当从建筑物 i 移动到建筑物 i+1(下标 从 0 开始 )时:如果当前建筑物的高度 大于或等于 下一建筑物的高度,则不需要梯子或砖块如果当前建筑的高度 小于 下一个建筑的高度,您可以使用 一架梯子 或 (h[i+1] - h[i]) 个砖块如果以最佳方式使用给定的梯子和砖块,返回你可以到达的最远建筑物的下标(下标 从 0 开始 )。 12345678910111213141516171819202122232425262728示例 1:输入:heights = [4,2,7,6,9,14,12], bricks = 5, ladders = 1输出:4解释:从建筑物 0 出发,你可以按此方案完成旅程:- 不使用砖块或梯子到达建筑物 1 ,因为 4 >= 2- 使用 5 个砖块到达建筑物 2 。你必须使用砖块或梯子,因为 2 < 7- 不使用砖块或梯子到达建筑物 3 ,因为 7 >= 6- 使用唯一的梯子到达建筑物 4 。你必须使用砖块或梯子,因为 6 < 9无法越过建筑物 4 ,因为没有更多砖块或梯子。示例 2:输入:heights = [4,12,2,7,3,18,20,3,19], bricks = 10, ladders = 2输出:7示例 3:输入:heights = [14,3,19,3], bricks = 17, ladders = 0输出:3 提示:1 <= heights.length <= 1051 <= heights[i] <= 1060 <= bricks <= 1090 <= ladders <= heights.length 思路我们可以将梯子看出是无限的砖块,只不过只能使用一次,我们当然希望能将好梯用在刀刃上。和上面一样,如果是现实生活,我们是无法知道啥时候用梯子好,啥时候用砖头好的。 没关系,我们继续使用事后诸葛亮法,一次遍历就可完成。和前面的思路类似,那就是我无脑用梯子,等梯子不够用了,我们就要开始事后诸葛亮了,要是前面用砖头就好了。那什么时候用砖头就好了呢?很明显就是当初用梯子的时候高度差,比现在的高度差小。 直白点就是当初我用梯子爬了个 5 米的墙,现在这里有个十米的墙,我没梯子了,只能用 10 个砖头了。要是之前用 5 个砖头,现在不就可以用一个梯子,从而省下 5 个砖头了吗? 这提示我们将用前面用梯子跨越的建筑物高度差存起来,等到后面梯子用完了,我们将前面被用的梯子“兑换”成砖头继续用。以上面的例子来说,我们就可以先兑换 10 个砖头,然后将 5 个砖头用掉,也就是相当于增加了 5 个砖头。 如果前面多次使用了梯子,我们优先“兑换”哪次呢?显然是优先兑换高度差大的,这样兑换的砖头才最多。这提示每次都从之前存储的高度差中选最大的,并在“兑换”之后将其移除。这种动态求极值的场景用什么数据结构合适?当然是堆啦。 代码123456789101112131415161718class Solution: def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int: h = [] for i in range(1, len(heights)): diff = heights[i] - heights[i - 1] if diff <= 0: continue if bricks < diff and ladders > 0: ladders -= 1 if h and -h[0] > diff: bricks -= heapq.heappop(h) else: continue bricks -= diff if bricks < 0: return i - 1 heapq.heappush(h, -diff) return len(heights) - 1 (代码 1.3.12) 四大应用接下来是本文的最后一个部分《四大应用》,目的是通过这几个例子来帮助大家巩固前面的知识。 1. topK求解 topK 是堆的一个很重要的功能。这个其实已经在前面的固定堆部分给大家介绍过了。 这里直接引用前面的话: “其实求第 k 小的数最简单的思路是建立小顶堆,将所有的数先全部入堆,然后逐个出堆,一共出堆 k 次。最后一次出堆的就是第 k 小的数。然而,我们也可不先全部入堆,而是建立大顶堆(注意不是上面的小顶堆),并维持堆的大小为 k 个。如果新的数入堆之后堆的大小大于 k,则需要将堆顶的数和新的数进行比较,并将较大的移除。这样可以保证堆中的数是全体数字中最小的 k 个,而这最小的 k 个中最大的(即堆顶)不就是第 k 小的么?这也就是选择建立大顶堆,而不是小顶堆的原因。” 其实除了第 k 小的数,我们也可以将中间的数全部收集起来,这就可以求出最小的 k 个数。和上面第 k 小的数唯一不同的点在于需要收集 popp 出来的所有的数。 需要注意的是,有时候权重并不是原本数组值本身的大小,也可以是距离,出现频率等。 相关题目: 面试题 17.14. 最小 K 个数 347. 前 K 个高频元素 973. 最接近原点的 K 个点 力扣中有关第 k 的题目很多都是堆。除了堆之外,第 k 的题目其实还会有一些找规律的题目,对于这种题目则可以通过分治+递归的方式来解决,具体就不再这里展开了,感兴趣的可以和我留言讨论。 2. 带权最短距离关于这点,其实我在前面部分也提到过了,只不过当时只是一带而过。原话是“不过 BFS 真的就没人用优先队列实现么?当然不是!比如带权图的最短路径问题,如果用队列做 BFS 那就需要优先队列才可以,因为路径之间是有权重的差异的,这不就是优先队列的设计初衷么。使用优先队列的 BFS 实现典型的就是 dijkstra 算法。” DIJKSTRA 算法主要解决的是图中任意两点的最短距离。 算法的基本思想是贪心,每次都遍历所有邻居,并从中找到距离最小的,本质上是一种广度优先遍历。这里我们借助堆这种数据结构,使得可以在 $logN$ 的时间内找到 cost 最小的点,其中 N 为 堆的大小。 代码模板: 1234567891011121314151617def dijkstra(graph, start, end): # 堆里的数据都是 (cost, i) 的二元祖,其含义是“从 start 走到 i 的距离是 cost”。 heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 (代码 1.4.1) 可以看出代码模板和 BFS 基本是类似的。如果你自己将堆的 key 设定为 steps 也可模拟实现 BFS,这个在前面已经讲过了,这里不再赘述。 比如一个图是这样的: 1234E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F \\ /\\ \\ || -------- 2 ---------> G ------- 1 ------ 我们使用邻接矩阵来构造: 1234567891011G = { \"B\": [[\"C\", 1]], \"C\": [[\"D\", 1]], \"D\": [[\"F\", 1]], \"E\": [[\"B\", 1], [\"G\", 2]], \"F\": [], \"G\": [[\"F\", 1]],}shortDistance = dijkstra(G, \"E\", \"C\")print(shortDistance) # E -- 3 --> F -- 3 --> C == 6 会了这个算法模板, 你就可以去 AC 743. 网络延迟时间 了。 完整代码: 12345678910111213141516171819202122232425262728class Solution: def dijkstra(self, graph, start, end): heap = [(0, start)] visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue visited.add(u) if u == end: return cost for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) return -1 def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, w in times: graph[fr - 1].append((to - 1, w)) ans = -1 for to in range(N): # 调用封装好的 dijkstra 方法 dist = self.dijkstra(graph, K - 1, to) if dist == -1: return -1 ans = max(ans, dist) return ans (代码 1.4.2) 你学会了么? 上面的算法并不是最优解,我只是为了体现将 dijkstra 封装为 api 调用 的思想。一个更好的做法是一次遍历记录所有的距离信息,而不是每次都重复计算。时间复杂度会大大降低。这在计算一个点到图中所有点的距离时有很大的意义。 为了实现这个目的,我们的算法会有什么样的调整? 提示:你可以使用一个 dist 哈希表记录开始点到每个点的最短距离来完成。想出来的话,可以用力扣 882 题去验证一下哦~ 其实只需要做一个小的调整就可以了,由于调整很小,直接看代码会比较好。 代码: 12345678910111213141516171819202122class Solution: def dijkstra(self, graph, start, end): heap = [(0, start)] # cost from start node,end node dist = {} while heap: (cost, u) = heapq.heappop(heap) if u in dist: continue dist[u] = cost for v, c in graph[u]: if v in dist: continue next = cost + c heapq.heappush(heap, (next, v)) return dist def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, w in times: graph[fr - 1].append((to - 1, w)) ans = -1 dist = self.dijkstra(graph, K - 1, to) return -1 if len(dist) != N else max(dist.values()) (代码 1.4.3) 可以看出我们只是将 visitd 替换成了 dist,其他不变。另外 dist 其实只是带了 key 的 visited,它这里也起到了 visitd 的作用。 如果你需要计算一个节点到其他所有节点的最短路径,可以使用一个 dist (一个 hashmap)来记录出发点到所有点的最短路径信息,而不是使用 visited (一个 hashset)。 类似的题目也不少, 我再举一个给大家 787. K 站中转内最便宜的航班。题目描述: 1234567891011121314有 n 个城市通过 m 个航班连接。每个航班都从城市 u 开始,以价格 w 抵达 v。现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到从 src 到 dst 最多经过 k 站中转的最便宜的价格。 如果没有这样的路线,则输出 -1。 示例 1:输入:n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]src = 0, dst = 2, k = 1输出: 200解释:城市航班图如下 1234567891011从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。示例 2:输入:n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]src = 0, dst = 2, k = 0输出: 500解释:城市航班图如下 123456789101112从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示。 提示:n 范围是 [1, 100],城市标签从 0 到 n - 1航班数量范围是 [0, n * (n - 1) / 2]每个航班的格式 (src, dst, price)每个航班的价格范围是 [1, 10000]k 范围是 [0, n - 1]航班没有重复,且不存在自环 这道题和上面的没有本质不同, 我仍然将其封装成 API 来使用,具体看代码就行。 这道题唯一特别的点在于如果中转次数大于 k,也认为无法到达。这个其实很容易,我们只需要在堆中用元组来多携带一个 steps即可,这个 steps 就是 不带权 BFS 中的距离。如果 pop 出来 steps 大于 K,则认为非法,我们跳过继续处理即可。 12345678910111213141516171819202122232425class Solution: # 改造一下,增加参数 K,堆多携带一个 steps 即可 def dijkstra(self, graph, start, end, K): heap = [(0, start, 0)] visited = set() while heap: (cost, u, steps) = heapq.heappop(heap) if u in visited: continue visited.add((u, steps)) if steps > K: continue if u == end: return cost for v, c in graph[u]: if (v, steps) in visited: continue next = cost + c heapq.heappush(heap, (next, v, steps + 1)) return -1 def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int: graph = collections.defaultdict(list) for fr, to, price in flights: graph[fr].append((to, price)) # 调用封装好的 dijkstra 方法 return self.dijkstra(graph, src, dst, K + 1) (代码 1.4.4) 3. 因子分解和上面两个应用一下,这个我在前面 《313. 超级丑数》部分也提到了。 回顾一下丑数的定义: 丑数就是质因数只包含 2, 3, 5 的正整数。 因此丑数本质就是一个数经过因子分解之后只剩下 2,3,5 的整数,而不携带别的因子了。 关于丑数的题目有很多,大多数也可以从堆的角度考虑来解。只不过有时候因子个数有限,不使用堆也容易解决。比如:264. 丑数 II 就可以使用三个指针来记录即可,这个技巧在前面也讲过了,不再赘述。 一些题目并不是丑数,但是却明确提到了类似因子的信息,并让你求第 k 大的 xx,这个时候优先考虑使用堆来解决。如果题目中夹杂一些其他信息,比如有序,则也可考虑二分法。具体使用哪种方法,要具体问题具体分析,不过在此之前大家要对这两种方法都足够熟悉才行。 4. 堆排序前面的三种应用或多或少在前面都提到过。而堆排序却未曾在前面提到。 直接考察堆排序的题目几乎没有。但是面试却有可能会考察,另外学习堆排序对你理解分治等重要算法思维都有重要意义。个人感觉,堆排序,构造二叉树,构造线段树等算法都有很大的相似性,掌握一种,其他都可以触类旁通。 实际上,经过前面的堆的学习,我们可以封装一个堆排序,方法非常简单。 这里我放一个使用堆的 api 实现堆排序的简单的示例代码: 1234567h = [9,5,2,7]heapq.heapify(h)ans = []while h: ans.append(heapq.heappop(h))print(ans) # 2,5,7,9 明白了示例, 那封装成通用堆排序就不难了。 123456def heap_sort(h): heapq.heapify(h) ans = [] while h: ans.append(heapq.heappop(h)) return ans 这个方法足够简单,如果你明白了前面堆的原理,让你手撸一个堆排序也不难。可是这种方法有个弊端,它不是原位算法,也就是说你必须使用额外的空间承接结果,空间复杂度为 $O(N)$。但是其实调用完堆排序的方法后,原有的数组内存可以被释放了,因此理论上来说空间也没浪费,只不过我们计算空间复杂度的时候取的是使用内存最多的时刻,因此使用原地算法毫无疑问更优秀。如果你实在觉得不爽这个实现,也可以采用原地的修改的方式。这倒也不难,只不过稍微改造一下前面的堆的实现即可,由于篇幅的限制,这里不多讲了。 总结堆和队列有千丝万缕的联系。 很多题目我都是先思考使用堆来完成。然后发现每次入堆都是 + 1,而不会跳着更新,比如下一个是 + 2,+3 等等,因此使用队列来完成性能更好。 比如 649. Dota2 参议院 和 1654. 到家的最少跳跃次数 等。 堆的中心就一个,那就是动态求极值。 而求极值无非就是最大值或者最小值,这不难看出。如果求最大值,我们可以使用大顶堆,如果求最小值,可以用最小堆。而实际上,如果没有动态两个字,很多情况下没有必要使用堆。比如可以直接一次遍历找出最大的即可。而动态这个点不容易看出来,这正是题目的难点。这需要你先对问题进行分析, 分析出这道题其实就是动态求极值,那么使用堆来优化就应该被想到。 堆的实现有很多。比如基于链表的跳表,基于数组的二叉堆和基于红黑树的实现等。这里我们介绍了两种主要实现 并详细地讲述了二叉堆的实现,不仅是其实现简单,而且其在很多情况下表现都不错,推荐大家重点掌握二叉堆实现。 对于二叉堆的实现,核心点就一点,那就是始终维护堆的性质不变,具体是什么性质呢?那就是 父节点的权值不大于儿子的权值(小顶堆)。为了达到这个目的,我们需要在入堆和出堆的时候,使用上浮和下沉操作,并恰当地完成元素交换。具体来说就是上浮过程和比它大的父节点进行交换,下沉过程和两个子节点中较小的进行交换,当然前提是它有子节点且子节点比它小。 关于堆化我们并没有做详细分析。不过如果你理解了本文的入堆操作,这其实很容易。因此堆化本身就是一个不断入堆的过程,只不过将时间上的离散的操作变成了一次性操作而已。 另外我给大家介绍了三个堆的做题技巧,分别是: 固定堆,不仅可以解决第 k 问题,还可有效利用已经计算的结果,避免重复计算。 多路归并,本质就是一个暴力解法,和暴力递归没有本质区别。如果你将其转化为递归,也是一种不能记忆化的递归。因此更像是回溯算法。 事后小诸葛。有些信息,我们在当前没有办法获取,就可用一种数据结构存起来,方便之后”东窗事发“的时候查。这种数据解决可以是很多,常见的有哈希表和堆。你也可以将这个技巧看成是事后后悔,有的人比较能接受这种叫法,不过不管叫法如何,指的都是这个含义。 最后给大家介绍了四种应用,这四种应用除了堆排序,其他在前面或多或少都讲过,它们分别是: topK 带权最短路径 因子分解 堆排序 这四种应用实际上还是围绕了堆的一个中心动态取极值,这四种应用只不过是灵活使用了这个特点罢了。因此大家在做题的时候只要死记动态求极值即可。如果你能够分析出这道题和动态取极值有关,那么请务必考虑堆。接下来我们就要在脑子中过一下复杂度,对照一下题目数据范围就大概可以估算出是否可行啦。 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。目前已经 39K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/categories/堆/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/tags/堆/"}]},{"title":"蓄水池抽样","slug":"reservoid-sampling","date":"2021-01-11T16:00:00.000Z","updated":"2023-01-05T12:24:50.176Z","comments":true,"path":"2021/01/12/reservoid-sampling/","link":"","permalink":"https://lucifer.ren/blog/2021/01/12/reservoid-sampling/","excerpt":"力扣中关于蓄水池抽样问题官方标签是 2 道,根据我的做题情况来看,可能有三四道。比重算是比较低的,大家可以根据自己的实际情况选择性掌握。 蓄水池抽样的算法思维很巧妙,代码简单且容易理解,就算不掌握它,作为了解也是很不错的。","text":"力扣中关于蓄水池抽样问题官方标签是 2 道,根据我的做题情况来看,可能有三四道。比重算是比较低的,大家可以根据自己的实际情况选择性掌握。 蓄水池抽样的算法思维很巧妙,代码简单且容易理解,就算不掌握它,作为了解也是很不错的。 问题描述给出一个数据流,我们需要在此数据流中随机选取 k 个数。由于这个数据流的长度很大,因此需要边遍历边处理,而不能将其一次性全部加载到内存。 请写出一个随机选择算法,使得数据流中所有数据被等概率选中。 这种问题的表达形式有很多。比如让你随机从一个矩形中抽取 k 个点,随机从一个单词列表中抽取 k 个单词等等,要求你等概率随机抽取。不管描述怎么变,其本质上都是一样的。今天我们就来看看如何做这种题。 算法描述这个算法叫蓄水池抽样算法(reservoid sampling)。 其基本思路是: 构建一个大小为 k 的数组,将数据流的前 k 个元素放入数组中。 对数据流的前 k 个数先不进行任何处理。 从数据流的第 k + 1 个数开始,在 [1, i] 之间选一个数 rand,其中 i 表示当前是第几个数。 如果 rand 大于等于 k 什么都不做 如果 rand 小于 k, 将 rand 和 i 交换,也就是说选择当前的数代替已经被选中的数(备胎)。 最终返回幸存的备胎即可 这种算法的核心在于先以某一种概率选取数,并在后续过程以另一种概率换掉之前已经被选中的数。因此实际上每个数被最终选中的概率都是被选中的概率 * 不被替换的概率。 伪代码: 伪代码参考的某一本算法书,并略有修改。 12345Init : a reservoir with the size: kfor i= k+1 to N if(random(1, i) < k) { SWAP the Mth value and ith value } 这样可以保证被选择的数是等概率的吗?答案是肯定的。 当 i <= k ,i 被选中的概率是 1。 到第 k + 1 个数时,第 k + 1 个数被选中的概率(走进上面的 if 分支的概率)是 $\\frac{k}{k+1}$,到第 k + 2 个数时,第 k + 2 个数被选中的概率(走进上面的 if 分支的概率)是 $\\frac{k}{k+2}$,以此类推。那么第 n 个数被选中的概率就是 $\\frac{k}{n}$ 上面分析了被选中的概率,接下来分析不被替换的概率。到第 k + 1 个数时,前 k 个数被替换的概率是 $\\frac{1}{k}$。到前 k + 2 个数时,第 k + 2 个数被替换的概率是 $\\frac{1}{k}$,以此类推。也就是说所有的被替换的概率都是 $\\frac{1}{k}$。知道了被替换的概率,那么不被替换的概率其实就是 1 - 被替换的概率。 因此对于前 k 个数,最终被选择的概率都是 1 * 不被 k + 1 替换的概率 * 不被 k + 2 替换的概率 * … 不被 n 替换的概率,即 1 * (1 - 被 k + 1 替换的概率) * (1 - 被 k + 2 替换的概率) * … (1 - 被 n 替换的概率),即 $1 \\times (1 - \\frac{k}{k+1} \\times \\frac{1}{k}) \\times (1 - \\frac{k}{k+2} \\times \\frac{1}{k}) \\times … \\times (1 - \\frac{k}{n} \\times \\frac{1}{k}) = \\frac{k}{n} $。 对于 第 i (i > k) 个数,最终被选择的概率是 第 i 步被选中的概率 * 不被第 i + 1 步替换的概率 * … * 不被第 n 步被替换的概率, 即 $\\frac{k}{k+1} \\times (1 - \\frac{k}{k+2} \\times \\frac{1}{k}) \\times … \\times (1 - \\frac{k}{n} \\times \\frac{1}{k}) = \\frac{k}{n} $。 总之,不管是哪个数,被选中的概率都是 $\\frac{k}{n}$,满足概率相等的需求。 相关题目 382. 链表随机节点 398. 随机数索引 497. 非重叠矩形中的随机点 总结蓄水池抽样算法核心代码非常简单。但是却不容易想到,尤其是之前没见过的情况下。其核心点在于每个数被最终选中的概率都是被选中的概率 * 不被替换的概率。于是我们可以采取某一种动态手段,使得每一轮都有概率选中和替换一些数字。 上面我们有给出了概率相等的证明过程,大家不妨自己尝试证明一下。之后结合文末的相关题目练习一下,效果会更好。","categories":[{"name":"蓄水池抽样","slug":"蓄水池抽样","permalink":"https://lucifer.ren/blog/categories/蓄水池抽样/"}],"tags":[{"name":"概率","slug":"概率","permalink":"https://lucifer.ren/blog/tags/概率/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"蓄水池抽样","slug":"蓄水池抽样","permalink":"https://lucifer.ren/blog/tags/蓄水池抽样/"}]},{"title":"几乎刷完了力扣所有的堆题,我发现了这些东西。。。","slug":"heap","date":"2020-12-25T16:00:00.000Z","updated":"2023-01-05T12:24:49.886Z","comments":true,"path":"2020/12/26/heap/","link":"","permalink":"https://lucifer.ren/blog/2020/12/26/heap/","excerpt":"大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(就是本文)","text":"大家好,我是 lucifer。今天给大家带来的是《堆》专题。先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。 几乎刷完了力扣所有的堆题,我发现了这些东西。。。(就是本文) 一点絮叨堆标签在 leetcode 一共有 42 道题。 为了准备这个专题,我将 leetcode 几乎所有的堆题目都刷了一遍。 可以看出,除了 3 个上锁的,其他我都刷了一遍。通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。 需要注意的是,本文不对堆和优先队列进行区分。因此本文提到的堆和优先队列大家可以认为是同一个东西。如果大家对两者的学术区别感兴趣,可以去查阅相关资料。 如果不做特殊说明,本文的堆均指的是小顶堆。 堆的题难度几何?堆确实是一个难度不低的专题。从官方的难度标签来看,堆的题目一共才 42 道,困难度将近 50%。没有对比就没有伤害,树专题困难度只有不到 10%。 从通过率来看,一半以上的题目平均通过率在 50% 以下。作为对比, 树的题目通过率在 50% 以下的只有不到三分之一。 不过大家不要太有压力。lucifer 给大家带来了一个口诀一个中心,两种实现,三个技巧,四大应用,我们不仅讲实现和原理,更讲问题的背景以及套路和模板。 文章里涉及的模板大家随时都可以从我的力扣刷题插件 leetcode-cheatsheet 中获取。 堆的使用场景分析堆其实就是一种数据结构,数据结构是为了算法服务的,那堆这种数据结构是为哪种算法服务的?它的适用场景是什么? 这是每一个学习堆的人第一个需要解决的问题。在什么情况下我们会使用堆呢?堆的原理是什么?如何实现一个堆?别急,本文将一一为你揭秘。 在进入正文之前,给大家一个学习建议 - 先不要纠结堆怎么实现的,咱先了解堆解决了什么问题。当你了解了使用背景和解决的问题之后,然后当一个调包侠,直接用现成的堆的 api 解决问题。等你理解得差不多了,再去看堆的原理和实现。我就是这样学习堆的,因此这里就将这个学习经验分享给你。 为了对堆的使用场景进行说明,这里我虚拟了一个场景。 下面这个例子很重要, 后面会反复和这个例子进行对比。 一个挂号系统问题描述假如你是一个排队挂号系统的技术负责人。该系统需要给每一个前来排队的人发放一个排队码(入队),并根据先来后到的原则进行叫号(出队)。 除此之外,我们还可以区分了几种客户类型, 分别是普通客户, VIP 客户 和 至尊 VIP 客户。 如果不同的客户使用不同的窗口的话,我该如何设计实现我的系统?(大家获得的服务不一样,比如 VIP 客户是专家级医生,普通客户是普通医生) 如果不同的客户都使用一个窗口的话,我该如何设计实现我的系统?(大家获得的服务都一样,但是优先级不一样。比如其他条件相同情况下(比如他们都是同时来挂号的),VIP 客户 优先级高于普通客户) 我该如何设计我的系统才能满足需求,并获得较好的扩展性? 初步的解决方案如果不同的客户使用不同的窗口。那么我们可以设计三个队列,分别存放正在排队的三种人。这种设计满足了题目要求,也足够简单。 如果我们只有一个窗口,所有的病人需要使用同一个队列,并且同样的客户类型按照上面讲的先到先服务原则,但是不同客户类型之间可能会插队。 简单起见,我引入了虚拟时间这个概念。具体来说: 普通客户的虚拟时间就是真实时间。 VIP 客户的虚拟时间按照实际到来时间减去一个小时。比如一个 VIP 客户是 14:00 到达的,我认为他是 13:00 到的。 至尊 VIP 客户的虚拟时间按照实际到来时间减去两个小时。比如一个 至尊 VIP 客户是 14:00 到达的,我认为他是 12:00 到的。 这样,我们只需要按照上面的”虚拟到达时间“进行先到先服务即可。 因此我们就可以继续使用刚才的三个队列的方式,只不过队列存储的不是真实时间,而是虚拟时间。每次开始叫号的时候,我们使用虚拟时间比较,虚拟时间较小的先服务即可。 不难看出,队列内部的时间都是有序。 而这里的虚拟时间,其实就是优先队列中的优先权重,虚拟时间越小,权重越大。 可以插队怎么办?这种算法很好地完成了我们的需求,复杂度相当不错。不过事情还没有完结,这一次我们又碰到新的产品需求: 如果有别的门诊的病人转院到我们的诊所,则按照他之前的排队信息算,比如 ta 是 12:00 在别的院挂的号,那么转到本院仍然是按照 12:00 挂号算。 如果被叫到号三分钟没有应答,将其作废。但是如果后面病人重新来了,则认为他是当前时间减去一个小时的虚拟时间再次排队。比如 ta 是 13:00 被叫号,没有应答,13:30 又回来,则认为他是 12:30 排队的,重新进队列。 这样就有了”插队“的情况了。该怎么办呢?一个简单的做法是,将其插入到正确位置,并重新调整后面所有人的排队位置。 如下图是插入一个 1:30 开始排队的普通客户的情况。 (查找插入位置) (将其插入) 如果队列使用数组实现, 上面插队过程的时间复杂度为 $O(N)$,其中 $N$ 为被插队的队伍长度。如果队伍很长,那么调整的次数明显增加。 不过我们发现,本质上我们就是在维护一个有序列表,而使用数组方式去维护有序列表的好处是可以随机访问,但是很明显这个需求并不需要这个特性。如果使用链表去实现,那么时间复杂度理论上是 $O(1)$,但是如何定位到需要插入的位置呢?朴素的思维是遍历查找,但是这样的时间复杂度又退化到了 $O(N)$。有没有时间复杂度更好的做法呢?答案就是本文的主角优先队列。 上面说了链表的实现核心在于查找也需要 $O(N)$,我们可以优化这个过程吗?实际上这就是优先级队列的链表实现,由于是有序的,我们可以用跳表加速查找,时间复杂度可以优化到 $O(logN)$。 其实算法界有很多类似的问题。比如建立数据库索引的算法,如果给某一个有序的列添加索引,不能每次插入一条数据都去调整所有的数据吧(上面的数组实现)?因此我们可以用平衡树来实现,这样每次插入可以最多调整 $(O(logN))$。优先队列的另外一种实现 - 二叉堆就是这个思想,时间复杂度也可以优化到 $O(logN)$ 本文只讲解常见的二叉堆实现,对于跳表和红黑树不再这里讲。 关于优先队列的二叉堆实现,我们会在后面给大家详细介绍。这里大家只有明白优先队列解决的问题是什么就可以了。 使用堆解决问题堆的两个核心 API 是 push 和 pop。 大家先不考虑它怎么实现的,你可以暂时把 ta 想象成一个黑盒,提供了两个 api: push: 推入一个数据,内部怎么组织我不管。对应我上面场景里面的排队和插队。 pop: 弹出一个数据,该数据一定是最小的,内部怎么实现我不管。对应我上面场景里面的叫号。 这里的例子其实是小顶堆。而如果弹出的数据一定是最大的,那么对应的实现为大顶堆。 借助这两个 api 就可以实现上面的需求。 12345678910# 12:00 来了一个普通的顾客(push)heapq.heappush(normal_pq, '12:00')# 12:30 来了一个普通顾客(push)heapq.heappush(normal_pq, '12:30')# 13:00 来了一个普通顾客(push)heapq.heappush(normal_pq, '13:00')# 插队(push)。时间复杂度可以达到 O(logN)。如何做到先不管,我们先会用就行,具体实现细节后面再讲。heapq.heappush(normal_pq, '12: 20')# 叫号(pop)。12:00 来的先被叫到。需要注意的是这里的弹出时间复杂度也变成了 O(logN),这或许就是幸福的代价吧。heapq.heappop(normal_pq) 小结上面这个场景单纯使用数组和链表都可以满足需求,但是使用其他数据结构在应对”插队“的情况表现地会更好。 具体来说: 如果永远都维护一个有序数组的方式取极值很容易,但是插队麻烦。 如果永远都维护一个有序链表的方式取极值也容易。 不过要想查找足够快,而不是线性扫描,就需要借助索引,这种实现对应的就是优先级队列的跳表实现。 如果永远都维护一个树的方式取极值也可以实现,比如根节点就是极值,这样 O(1) 也可以取到极值,但是调整过程需要 $O(logN)$。这种实现对应的就是优先级队列的二叉堆实现。 简单总结下就是,堆就是动态帮你求极值的。当你需要动态求最大或最小值就就用它。而具体怎么实现,复杂度的分析我们之后讲,现在你只要记住使用场景,堆是如何解决这些问题的以及堆的 api 就够了。 队列 VS 优先队列上面通过一个例子带大家了解了一下优先队列。那么在接下来讲具体实现之前,我觉得有必要回答下一个大家普遍关心的问题,那就是优先队列是队列么? 很多人觉得队列和优先队列是完全不同的东西,就好像 Java 和 JavaScript 一样,我看了很多文章都是这么说的。 而我不这么认为。实际上,普通的队列也可以看成是一个特殊的优先级队列, 这和网上大多数的说法优先级队列和队列没什么关系有所不同。我认为队列无非就是以时间这一变量作为优先级的优先队列,时间越早,优先级越高,优先级越高越先出队。 大家平时写 BFS 的时候都会用到队列来帮你处理节点的访问顺序。那使用优先队列行不行?当然可以了!我举个例子: 例题 - 513. 找树左下角的值题目描述12345678910111213141516171819202122232425262728293031定一个二叉树,在树的最后一行找到最左边的值。示例 1:输入: 2 / \\ 1 3输出:1 示例 2:输入: 1 / \\ 2 3 / / \\ 4 5 6 / 7输出:7 注意: 您可以假设树(即给定的根节点)不为 NULL。 思路我们可以使用 BFS 来做一次层次遍历,并且每一层我们都从右向左遍历,这样层次遍历的最后一个节点就是树左下角的节点。 常规的做法是使用双端队列(就是队列)来实现,由于队列的先进先出原则很方便地就能实现层次遍历的效果。 代码对于代码看不懂的同学,可以先不要着急。等完整读完本文之后再回过头看会容易很多。下同,不再赘述。 Python Code: 123456789101112131415class Solution: def findBottomLeftValue(self, root: TreeNode) -> int: if root is None: return None queue = collections.deque([root]) ans = None while queue: size = len(queue) for _ in range(size): ans = node = queue.popleft() if node.right: queue.append(node.right) if node.left: queue.append(node.left) return ans.val 实际上, 我们也可以使用优先队列的方式,思路和代码也几乎和上面完全一样。 123456789101112131415161718class Solution: def findBottomLeftValue(self, root: TreeNode) -> int: if root is None: return None queue = [] # 堆存储三元组(a,b,c),a 表示层级,b 表示节点编号(以完全二叉树的形式编号,空节点也编号),c 是节点本身 heapq.heappush(queue, (1, 1, root)) ans = None while queue: size = len(queue) for _ in range(size): level, i, node = heapq.heappop(queue) ans = node if node.right: heapq.heappush(queue, (level + 1, 2 * i + 1, node.right)) if node.left: heapq.heappush(queue, (level + 1, 2 * i + 2, node.left)) return ans.val 小结所有使用队列的地方,都可以使用优先队列来完成,反之却不一定。 既然优先队列这么厉害,那平时都用优先队列不就行了?为啥使用队列的地方没见过别人用堆呢?最核心的原因是时间复杂度更差了。 比如上面的例子,本来入队和出队都可是很容易地在 $O(1)$ 的时间完成。而现在呢?入队和出队的复杂度都是 $O(logN)$,其中 N 为当前队列的大小。因此在没有必要的地方使用堆,会大大提高算法的时间复杂度,这当然不合适。说的粗俗一点就是脱了裤子放屁。 不过 BFS 真的就没人用优先队列实现么?当然不是!比如带权图的最短路径问题,如果用队列做 BFS 那就需要优先队列才可以,因为路径之间是有权重的差异的,这不就是优先队列的设计初衷么。使用优先队列的 BFS 实现典型的就是 dijkstra 算法。 这再一次应征了我的那句话队列就是一种特殊的优先队列而已。特殊到大家的权重就是按照到来的顺序定,谁先来谁的优先级越高。在这种特殊情况下,我们没必须去维护堆来完成,进而获得更好的时间复杂度。 一个中心堆的问题核心点就一个,那就是动态求极值。动态和极值二者缺一不可。 求极值比较好理解,无非就是求最大值或者最小值,而动态却不然。比如要你求一个数组的第 k 小的数,这是动态么?这其实完全看你怎么理解。而在我们这里,这种情况就是动态的。 如何理解上面的例子是动态呢? 你可以这么想。由于堆只能求极值。比如能求最小值,但不能直接求第 k 小的值。 那我们是不是先求最小的值,然后将其出队(对应上面例子的叫号)。然后继续求最小的值,这个时候求的就是第 2 小了。如果要求第 k 小,那就如此反复 k 次即可。 这个过程,你会发现数据是在动态变化的,对应的就是堆的大小在变化。 接下来,我们通过几个例子来进行说明。 例一 - 1046. 最后一块石头的重量题目描述12345678910111213141516171819202122232425有一堆石头,每块石头的重量都是正整数。每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:如果 x == y,那么两块石头都会被完全粉碎;如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。 示例:输入:[2,7,4,1,8,1]输出:1解释:先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。 提示:1 <= stones.length <= 301 <= stones[i] <= 1000 思路题目比较简单,直接模拟即可。需要注意的是,每次选择两个最重的两个石头进行粉碎之后,最重的石头的重量便发生了变化。这会影响到下次取最重的石头。简单来说就是最重的石头在模拟过程中是动态变化的。 这种动态取极值的场景使用堆就非常适合。 当然看下这个数据范围1 <= stones.length <= 30 且 1 <= stones[i] <= 1000,使用计数的方式应该也是可以的。 代码Java Code: 1234567891011121314151617181920212223242526import java.util.PriorityQueue;public class Solution { public int lastStoneWeight(int[] stones) { int n = stones.length; PriorityQueue<Integer> maxHeap = new PriorityQueue<>(n, (a, b) -> b - a); for (int stone : stones) { maxHeap.add(stone); } while (maxHeap.size() >= 2) { Integer head1 = maxHeap.poll(); Integer head2 = maxHeap.poll(); if (head1.equals(head2)) { continue; } maxHeap.offer(head1 - head2); } if (maxHeap.isEmpty()) { return 0; } return maxHeap.poll(); }} 例二 - 313. 超级丑数题目描述123456789101112131415编写一段程序来查找第 n 个超级丑数。超级丑数是指其所有质因数都是长度为 k 的质数列表 primes 中的正整数。示例:输入: n = 12, primes = [2,7,13,19]输出: 32解释: 给定长度为 4 的质数列表 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。说明:1 是任何给定 primes 的超级丑数。 给定 primes 中的数字以升序排列。0 < k ≤ 100, 0 < n ≤ 10^6, 0 < primes[i] < 1000 。第 n 个超级丑数确保在 32 位有符整数范围内。 思路这道题看似和动态求极值没关系。其实不然,让我们来分析一下这个题目。 我们可以实现生成超级多的丑数,比如先从小到大生成 N 个丑数,然后直接取第 N 个么? 拿这道题来说, 题目有一个数据范围限制 0 < n ≤ 10^6,那我们是不是预先生成一个大小为 $10^6$ 的超级丑数数组,这样我们就可通过 $O(1)$ 的时间获取到第 N 个超级丑数了。 首先第一个问题就是时间和空间浪费。我们其实没有必要每次都计算所有的超级丑数,这样的预处理空间和时间都很差。 第二个问题是,我们如何生成 $10^6$ 以为的超级丑数呢? 通过丑数的定义,我们能知道超级丑数一定可以写出如下形式。 123if primes = [a,b,c,....]then f(ugly) = a * x1 * b * x2 * c * x3 ...其中 x1,x2,x3 均为正整数。 不妨将问题先做一下简化处理。考虑题目给的例子:[2,7,13,19]。 我们可以使用四个指针来处理。直接看下代码吧: 123456789101112131415public class Solution { public int solve(int n) { int ans[]=new int[n+5]; ans[0]=1; int p1=0,p2=0,p3=0,p4=0; for(int i=1;i<n;i++){ ans[i]=Math.min(ans[p1]*2,Math.min(ans[p2]*7,Math.min(ans[p3]*13,ans[p4]*19))); if(ans[i]==ans[p1]*2) p1++; if(ans[i]==ans[p2]*7) p2++; if(ans[i]==ans[p3]*13) p3++; if(ans[i]==ans[p3]*19) p4++; } return ans[n-1]; }} 这个技巧我自己称之为多路归并(实现想不到什么好的名字),我也会在后面的三个技巧也会对此方法使用堆来优化。 由于这里的指针是动态的,指针的数量其实和题目给的 primes 数组长度一致。因此实际上,我们可以使用记忆化递归的形式来完成,递归体和递归栈分别维护一个迭代变量即可。而这道题其实可以看出是一个状态机,因此使用动态规划来解决是符合直觉的。而这里,介绍一种堆的解法,相比于动态规划,个人认为更简单和符合直觉。 关于状态机,我这里有一篇文章原来状态机也可以用来刷 LeetCode?,大家可以参考一下哦。 实际上,我们可以动态维护一个当前最小的超级丑数。找到第一个, 我们将其移除,再找下一个当前最小的超级丑数(也就是全局第二小的超级丑数)。这样经过 n 轮,我们就得到了第 n 小的超级丑数。这种动态维护极值的场景正是堆的用武之地。 有没有觉得和上面石头的题目很像? 以题目给的例子 [2,7,13,19] 来说。 将 [2,7,13,19] 依次入堆。 出堆一个数字,也就是 2。这时取到了第一个超级丑数。 接着将 2 和 [2,7,13,19] 的乘积,也就是 [4,14,26,38] 依次入堆。 如此反复直到取到第 n 个超级丑数。 上面的正确性是毋庸置疑的,由于每次堆都可以取到最小的,每次我们也会将最小的从堆中移除。因此取 n 次自然就是第 n 大的超级丑数了。 堆的解法没有太大难度,唯一需要注意的是去重。比如 2 * 13 = 26,而 13 * 2 也是 26。我们不能将 26 入两次堆。解决的方法也很简单: 要么使用哈希表记录全部已经取出的数,对于已经取出的数字不再取即可。 另一种方法是记录上一次取出的数,由于取出的数字是按照数字大小不严格递增的,这样只需要拿上次取出的数和本次取出的数比较一下就知道了。 用哪种方法不用多说了吧? 代码Java Code: 12345678910111213141516171819class Solution { public int nthSuperUglyNumber(int n, int[] primes) { PriorityQueue<Long> queue=new PriorityQueue<>(); int count = 0; long ans = 1; queue.add(ans); while (count < n) { ans=queue.poll(); while (!queue.isEmpty() && ans == queue.peek()) { queue.poll(); } count++; for (int i = 0; i < primes.length ; i++) { queue.offer(ans * primes[i]); } } return (int)ans; }} ans 初始化为 1 的作用相当于虚拟头,仅仅起到了简化操作的作用 小结堆的中心就一个,那就是动态求极值。 而求极值无非就是最大值或者最小值,这不难看出。如果求最大值,我们可以使用大顶堆,如果求最小值,可以用最小堆。 而实际上,如果没有动态两个字,很多情况下没有必要使用堆。比如可以直接一次遍历找出最大的即可。而动态这个点不容易看出来,这正是题目的难点。这需要你先对问题进行分析, 分析出这道题其实就是动态求极值,那么使用堆来优化就应该被想到。类似的例子有很多,我也会在后面的小节给大家做更多的讲解。 两种实现上面简单提到了堆的几种实现。这里介绍两种常见的实现,一种是基于链表的实现- 跳表,另一种是基于数组的实现 - 二叉堆。 使用跳表的实现,如果你的算法没有经过精雕细琢,性能会比较不稳定,且在数据量大的情况下内存占用会明显增加。 因此我们仅详细讲述二叉堆的实现,而对于跳表的实现,仅讲述它的基本原理,对于代码实现等更详细的内容由于比较偏就不在这里讲了。 跳表跳表也是一种数据结构,因此 ta 其实也是服务于某种算法的。 跳表虽然在面试中出现的频率不大,但是在工业中,跳表会经常被用到。力扣中关于跳表的题目只有一个。但是跳表的设计思路值得我们去学习和思考。 其中有很多算法和数据结构技巧值得我们学习。比如空间换时间的思想,比如效率的取舍问题等。 上面提到了应付插队问题是设计堆应该考虑的首要问题。堆的跳表实现是如何解决这个问题的呢? 我们知道,不借助额外空间的情况下,在链表中查找一个值,需要按照顺序一个个查找,时间复杂度为 $O(N)$,其中 N 为链表长度。 (单链表) 当链表长度很大的时候, 这种时间是很难接受的。 一种常见的的优化方式是建立哈希表,将所有节点都放到哈希表中,以空间换时间的方式减少时间复杂度,这种做法时间复杂度为 $O(1)$,但是空间复杂度为 $O(N)$。 (单链表 + 哈希表) 为了防止链表中出现重复节点带来的问题,我们需要序列化节点,再建立哈希表,这种空间占用会更高,虽然只是系数级别的增加,但是这种开销也是不小的 。更重要的是,哈希表不能解决查找极值的问题,其仅适合根据 key 来获取内容。 为了解决上面的问题,跳表应运而生。 如下图所示,我们从链表中每两个元素抽出来,加一级索引,一级索引指向了原始链表,即:通过一级索引 7 的 down 指针可以找到原始链表的 7 。那怎么查找 10 呢? 注意这个算法要求链表是有序的。 (建立一级索引) 我们可以: 通过现在一级跳表中搜索到 7,发现下一个 18 大于 10 ,也就是说我们要找的 10 在这两者之间。 通过 down 指针回到原始链表,通过原始链表的 next 指针我们找到了 10。 这个例子看不出性能提升。但是如果元素继续增大, 继续增加索引的层数,建立二级,三级。。。索引,使得链表能够实现二分查找,从而获得更好的效率。但是相应地,我们需要付出额外空间的代价。 (增加索引层数) 理解了上面的点,你可以形象地将跳表想象为玩游戏的存档。 一个游戏有 10 关。如果我想要玩第 5 关的某一个地方,那么我可以直接从第五关开始,这样要比从第一关开始快。我们甚至可以在每一关同时设置很多的存档。这样我如果想玩第 5 关的某一个地方,也可以不用从第 5 关的开头开始,而是直接选择离你想玩的地方更近的存档,这就相当于跳表的二级索引。 跳表的时间复杂度和空间复杂度不是很好分析。由于时间复杂度 = 索引的高度 * 平均每层索引遍历元素的个数,而高度大概为 $logn$,并且每层遍历的元素是常数,因此时间复杂度为 $logn$,和二分查找的空间复杂度是一样的。 空间复杂度就等同于索引节点的个数,以每两个节点建立一个索引为例,大概是 n/2 + n/4 + n/8 + … + 8 + 4 + 2 ,因此空间复杂度是 $O(n)$。当然你如果每三个建立一个索引节点的话,空间会更省,但是复杂度不变。 理解了上面的内容,使用跳表实现堆就不难了。 入堆操作,只需要根据索引插到链表中,并更新索引(可选)。 出堆操作,只需要删除头部(或者尾部),并更新索引(可选)。 大家如果想检测自己的实现是否有问题,可以去力扣的1206. 设计跳表 检测。 接下来,我们看下一种更加常见的实现 - 二叉堆。 二叉堆二叉堆的实现,我们仅讲解最核心的两个操作: heappop(出堆) 和 heappush(入堆)。对于其他操作不再讲解,不过我相信你会了这两个核心操作,其他的应该不是难事。 实现之后的使用效果大概是这样的: 123456789h = min_heap()h.build_heap([5, 6, 2, 3])h.heappush(1)h.heappop() # 1h.heappop() # 2h.heappush(1)h.heappop() # 1h.heappop() # 3 基本原理本质上来说,二叉堆就是一颗特殊的完全二叉树。它的特殊性只体现在一点,那就是父节点的权值不大于儿子的权值(小顶堆)。 (一个小顶堆) 上面这句话需要大家记住,一切的一切都源于上面这句话。 由于父节点的权值不大于儿子的权值(小顶堆),那么很自然能推导出树的根节点就是最小值。这就起到了堆的取极值的作用了。 那动态性呢?二叉堆是怎么做到的呢? 出堆假如,我将树的根节点出堆,那么根节点不就空缺了么?我应该将第二小的顶替上去。怎么顶替上去呢?一切的一切还是那句话父节点的权值不大于儿子的权值(小顶堆)。 如果仅仅是删除,那么一个堆就会变成两个堆了,问题变复杂了。 (上图出堆之后会生成两个新的堆) 一个常见的操作是,把根结点和最后一个结点交换。但是新的根结点可能不满足 父节点的权值不大于儿子的权值(小顶堆)。 如下图,我们将根节点的 2 和尾部的数字进行交换后,这个时候是不满足堆性质的。 这个时候,其实只需要将新的根节点下沉到正确位置即可。这里的正确位置,指的还是那句话父节点的权值不大于儿子的权值(小顶堆)。如果不满足这一点,我们就继续下沉,直到满足。 我们知道根节点往下下沉的过程,其实有两个方向可供选择,是下沉到左子节点?还是下沉到右子节点?以小顶堆来说,答案应该是下沉到较小的子节点处,否则会错失正确答案。以上面的堆为例,如果下沉到右子节点 4,那么就无法得到正确的堆顶 3。因此我们需要下沉到左子节点。 下沉到如图位置,还是不满足 父节点的权值不大于儿子的权值(小顶堆),于是我们继续执行同样的操作。 有的同学可能有疑问。弹出根节点前堆满足堆的性质,但是弹出之后经过你上面讲的下沉操作,一定还满足么? 答案是肯定的。这个也不难理解。由于最后的叶子节点被提到了根节点,它其实最终在哪是不确定的,但是经过上面的操作,我们可以看出: 其下沉路径上的节点一定都满足堆的性质。 不在下沉路径上的节点都保持了堆之前的相对关系,因此也满足堆的性质。 因此弹出根节点后,经过上面的下沉操作一定仍然满足堆的性质。 时间复杂度方面可以证明,下沉和树的高度成正相关,因此时间复杂度为 $O(h)$,其中 h 为树高。而由于二叉堆是一颗完全二叉树,因此树高大约是 $logN$,其中 N 为树中的节点个数。 入堆入堆和出堆类似。我们可以直接往树的最后插入一个节点。和上面类似,这样的操作同样可能会破坏堆的性质。 之所以这么做的其中一个原因是时间复杂度更低,因为我们是用数组进行模拟的,而在数组尾部添加元素的时间复杂度为 $O(1)$。 这次我们发现,不满足堆的节点目前是刚刚被插入节点的尾部节点,因此不能进行下沉操作了。这一次我们需要执行上浮操作。 叶子节点是只能上浮的(根节点只能下沉,其他节点既可以下沉,又可以上浮) 和上面基本类似,如果不满足堆的性质,我们将其和父节点交换(上浮),继续这个过程,直到满足堆的性质。 (第一次上浮,仍然不满足堆特性,继续上浮) (满足了堆特性,上浮过程完毕) 经过这样的操作,其还是一个满足堆性质的堆。证明过程和上面类似,不再赘述。 需要注意的是,由于上浮只需要拿当前节点和父节点进行比对就可以了, 由于省去了判断左右子节点哪个更小的过程,因此更加简单。 实现对于完全二叉树来说使用数组实现非常方便。因为: 如果节点在数组中的下标为 i,那么其左子节点下标为 $2 \\times i$,右节点为 $2 \\times i$+1。 如果节点在数组中的下标为 i,那么父节点下标为 i//2(地板除)。 当然这要求你的数组从 1 开始存储数据。如果不是,上面的公式其实微调一下也可以达到同样的效果。不过这是一种业界习惯,我们还是和业界保持一致比较好。从 1 开始存储的另外一个好处是,我们可以将索引为 0 的位置空出来存储诸如堆大小的信息,这是一些大学教材里的做法,大家作为了解即可。 如图所示是一个完全二叉树和树的数组表示法。 (注意数组索引的对应关系) 形象点来看,我们可以可以画出如下的对应关系图: 这样一来,是不是和上面的树差不多一致了?有没有容易理解一点呢? 上面已经讲了上浮和下沉的过程。刚才也讲了父子节点坐标的关系。那么代码就呼之欲出了。我们来下最核心的上浮和下沉的代码实现吧。 伪代码: 123456789101112131415161718// x 是要上浮的元素,从树的底部开始上浮private void shift_up(int x) { while (x > 1 && h[x] > h[x / 2]) { // swqp 就是交换数组两个位置的值 swap(h[x], h[x / 2]); x /= 2; }}// x 是要下沉的元素,从树的顶部开始下沉private void shift_down(int x) { while (x * 2 <= n) { // minChild 是获取更小的子节点的索引并返回 mc = minChild(x); if (h[mc] <= h[x]) break; swap(h[x], h[mc]); x = mc; }} 这里 Java 语言为例,讲述一下代码的编写。其他语言的二叉堆实现可以去我的刷题插件 leetcode-cheatsheet 中获取。插件的获取方式在公众号力扣加加里,回复插件即可。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110import java.util.Arrays;import java.util.Comparator;/** * 用完全二叉树来构建 堆 * 前置条件 起点为 1 * 那么 子节点为 i <<1 和 i<<1 + 1 * 核心方法为 * shiftdown 交换下沉 * shiftup 交换上浮 * <p> * build 构建堆 */public class Heap { int size = 0; int queue[]; public Heap(int initialCapacity) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new int[initialCapacity]; } public Heap(int[] arr) { size = arr.length; queue = new int[arr.length + 1]; int i = 1; for (int val : arr) { queue[i++] = val; } } public void shiftDown(int i) { int temp = queue[i]; while ((i << 1) <= size) { int child = i << 1; // child!=size 判断当前元素是否包含右节点 if (child != size && queue[child + 1] < queue[child]) { child++; } if (temp > queue[child]) { queue[i] = queue[child]; i = child; } else { break; } } queue[i] = temp; } public void shiftUp(int i) { int temp = queue[i]; while ((i >> 1) > 0) { if (temp < queue[i >> 1]) { queue[i] = queue[i >> 1]; i >>= 1; } else { break; } } queue[i] = temp; } public int peek() { int res = queue[1]; return res; } public int pop() { int res = queue[1]; queue[1] = queue[size--]; shiftDown(1); return res; } public void push(int val) { if (size == queue.length - 1) { queue = Arrays.copyOf(queue, size << 1+1); } queue[++size] = val; shiftUp(size); } public void buildHeap() { for (int i = size >> 1; i > 0; i--) { shiftDown(i); } } public static void main(String[] args) { int arr[] = new int[]{2,7,4,1,8,1}; Heap heap = new Heap(arr); heap.buildHeap(); System.out.println(heap.peek()); heap.push(5); while (heap.size > 0) { int num = heap.pop(); System.out.printf(num + \"\"); } }} 小结堆的实现有很多。比如基于链表的跳表,基于数组的二叉堆和基于红黑树的实现等。这里我们详细地讲述了二叉堆的实现,不仅是其实现简单,而且其在很多情况下表现都不错,推荐大家重点掌握二叉堆实现。 对于二叉堆的实现,核心点就一点,那就是始终维护堆的性质不变,具体是什么性质呢?那就是 父节点的权值不大于儿子的权值(小顶堆)。为了达到这个目的,我们需要在入堆和出堆的时候,使用上浮和下沉操作,并恰当地完成元素交换。具体来说就是上浮过程和比它大的父节点进行交换,下沉过程和两个子节点中较小的进行交换,当然前提是它有子节点且子节点比它小。 关于堆化我们并没有做详细分析。不过如果你理解了本文的入堆操作,这其实很容易。因此堆化本身就是一个不断入堆的过程,只不过将时间上的离散的操作变成了一次性操作而已。 预告本文预计分两个部分发布。这是第一部分,后面的内容更加干货,分别是三个技巧和四大应用。 三个技巧 多路归并 固定堆 事后小诸葛 四大应用 topK 带权最短距离 因子分解 堆排序 这两个主题是专门教你怎么解题的。掌握了它,力扣中的大多数堆的题目都不在话下(当然我指的仅仅是题目中涉及到堆的部分)。 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。目前已经 37K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/categories/堆/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"堆","slug":"堆","permalink":"https://lucifer.ren/blog/tags/堆/"}]},{"title":"来和大家聊聊我是如何刷题的(第三弹)","slug":"shuati-silu3","date":"2020-12-20T16:00:00.000Z","updated":"2023-01-05T12:24:49.999Z","comments":true,"path":"2020/12/21/shuati-silu3/","link":"","permalink":"https://lucifer.ren/blog/2020/12/21/shuati-silu3/","excerpt":"前两篇的地址在这里,没有看过的同学建议先看下。 来和大家聊聊我是如何刷题的(第一弹) 来和大家聊聊我是如何刷题的(第二弹) 本章或许是这个系列的最终章。这次给大家聊一点硬核的,聊一些几乎所有算法题都能用得上的超实用思想。 上一节给大家抛出了两个问题,分别是: 如何锁定使用哪种算法?比如我看到了这道题,我怎么知道该用什么解法呢?二分?动态规划? 一看就会,一写就废, 如何克服? 今天,我就来圆一个当初吹下的牛逼。话不多说,直接上干货。如果你觉得有用,请三连支持我一下,让我能够坚持下去,给大家带来更多的干货。","text":"前两篇的地址在这里,没有看过的同学建议先看下。 来和大家聊聊我是如何刷题的(第一弹) 来和大家聊聊我是如何刷题的(第二弹) 本章或许是这个系列的最终章。这次给大家聊一点硬核的,聊一些几乎所有算法题都能用得上的超实用思想。 上一节给大家抛出了两个问题,分别是: 如何锁定使用哪种算法?比如我看到了这道题,我怎么知道该用什么解法呢?二分?动态规划? 一看就会,一写就废, 如何克服? 今天,我就来圆一个当初吹下的牛逼。话不多说,直接上干货。如果你觉得有用,请三连支持我一下,让我能够坚持下去,给大家带来更多的干货。 如何锁定使用哪种算法?为什么很多人刚看了一眼题目就知道怎么解? 一种可能是 ta 之前做过同样或者类似的题目,形成了内在的记忆,直接提取了之前的记忆。 另一种可能是题目给出了明确的提示信息,他们根据这些信息”蒙“的,这种蒙就是题感。 最后一种是刚开始也没思路,尝试暴力解,发现某些步骤可以优化,慢慢剥茧抽丝,推导出最终答案。 接下来,我们来聊下第二种和第三种。至于第一种则不是一篇文章能解决的,这需要大家多做题,并且做题的时候要多总结多交流。 关键字关键字可以对解题起到提示作用。这很好理解,假设题目没有限制信息等关键字,那就是耍流氓,毫无算法可言了。 比如”在一个数组中找 target“,这道题就很无聊,正常不会有这种算法题。 可能的出题形式是加上有序两个字,变成有序数组。那有序就是关键字了。 其他的例子有很多,接下来我们来看下常见的关键字以及对应的可能解法有哪些。 如果题目是求极值,计数,很有可能是动态规划,堆等。 如果题目是有序的,则可能是双指针。比如二分法。 如果题目要求连续,则可能是滑动窗口。 如果题目要求所有可能,需要路径信息,则可能是回溯。 如上的这些只是看到关键词你应该第一时间想到的可能解法,究竟正确与否,以及复杂度是否达标需要在脑子里二次加工。 关于复杂度是否达标这一点,后面给大家介绍。 限制条件很多题目都会给一些数据范围的提示,大家一定要注意看。 比如 1681. 最小不兼容性,题目描述就不看了,我们不打算在这里讲具体怎么解。这道题的函数签名如下: 1def minimumIncompatibility(self, nums: List[int], k: int) -> int: 这道题的提示是这样的: 1231 <= k <= nums.length <= 16nums.length 能被 k 整除。1 <= nums[i] <= nums.length 看到了这个你有什么想法么? 注意到 nums 的长度和值都很小,这道题很可能是暴力回溯 + 状态压缩。关于回溯和状态压缩技巧可以翻翻我的历史文章。 这里再给大家一个超实用小技巧。 如果 n 是 10 左右,那么算法通常是 n! 的时间复杂度。 如果 n 是 20 左右,那么算法通常是 2^n 的时间复杂度 因此 1681. 最小不兼容性 这道题的复杂度很可能就是指数级别。 那为什么 10 左右就是 n!,20 是 2^n? 这里给大家介绍一个你可能不知道的技巧。请大家记住一个数字 1000 万。 上面之所以是 10 左右, 20 左右就是因为你把 n 带进去差不多都是 1000 万。 再比如一道题是 n 是 $10^7$,那很可能是$O(N)$复杂度,因为 $10 ^7$ 就是 1000 万。 再比如,我之前写的一篇文章《穿上衣服我就不认识你了?来聊聊最长上升子序列》,上面所有的题时间复杂度都是 $N^2$,基本都可以通过所有的测试用例。为什么?因为题目数据范围差不多是 2500,那 2500 的平方是多少?是 600 多万,因此数据范围是 3000 以内, 平方差不多都可解,当然我说的只是大多数情况,并且需要注意越接近临界值越可能超时。 再比如1631. 最小体力消耗路径。题目描述: 12345你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。请你返回从左上角走到右下角的最小 体力消耗值 。。 示例 1: 1234输入:heights = [[1,2,2],[3,8,2],[5,3,5]]输出:2解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。 这道题的函数签名如下: 12class Solution: def minimumEffortPath(self, heights: List[List[int]]) -> int: 这道题的提示是这样的: 1234rows == heights.lengthcolumns == heights[i].length1 <= rows, columns <= 1001 <= heights[i][j] <= 10^6 首先,我们至少需要从左上走到右下,那么时间复杂度就已经是 $O(rows * columns)$ 了。题目说了这两个数字都不大于 100,因此最大就是 $10^4$。而对于路线上的高度差绝对值的数据范围也不超过 $10^6$。 暴力法就是一个个试, 复杂度是二者直接相乘,也就是 $10^10$,大于前面给大家讲的 $10^7$,因此这个复杂度通常是不能 AC 的。 而上面说了 $O(rows * columns)$ 是不可能省的,因为你至少要走一次。但如果 $10^6$ 不是线性去试,而是指数的话呢?而指数复杂度首先想到二分。 此题的伪代码: 1234567891011121314class Solution: def minimumEffortPath(self, heights: List[List[int]]) -> int: def test(mid, x, y): # dosomething l, r = 0, 10**6 # 最左满足条件的值的二分模板。大家可以去 leetcode-cheatsheet 插件获取更多算法模板 while l <= r: mid = (l + r) // 2 # 测试有没有一条路径从(0,0)出发到达(rows-1,cols-1),且路径上的高度绝对值差不大于 mid if test(mid, 0, 0): r = mid - 1 else: l = mid + 1 return l 你说 1000 万这个数字重要不重要?1000 万不仅是我的人生目标,更是做题时刻铭记的一个数字!^_^ 暴力优化最后给大家介绍的”识别题目可能的解法“的技巧是暴力优化。 一句话概括就是先暴力解,然后思考性能瓶颈,再尝试使用数据结构和算法对瓶颈进行优化。 比如 316. 去除重复字母,我就是先暴力求出来。发现每次都直接判断是否在是否在栈上需要 $O(N)$ 的时间,太慢了。由于我就用了哈希表进行优化。而使用哈希表这点,绝对不是我一开始就想到的,而是先暴力求解,求解的过程发现算法的性能瓶颈才意识到该用哈希表的。关于这道题的详细的解法就不再这里讲了,大家点进去看我的题解就行。或者直接去力扣搜题,排名第一的非官方题解应该就是我。 总结一下就是,大家一定不要小看暴力法。暴力法解出来剪剪枝说不定就过了。如果不过,思考下瓶颈在哪,用合适的数据结构和算法优化一下说不定也就过了。这可不是随便说说。比如下面要讲的硬币找零问题,就是暴力解发现瓶颈,加个记忆化去除重复子问题就是动态规划了 一看就会,一写就废, 如何克服?针对这个问题,之前我给大家的建议是多复习, 多动手写。 后来我和几个朋友聊了一下,发现自己有点幸存者偏差。我发现很多人在没有算法思维的情况下就开始学习算法了,这很不可取。 不过算法思维这东西你让我在这一篇文章给你整的明明白白的,这也不现实。今天我给大家分享一个我认为最最重要的一个算法思想 - 分治。 分治思维“一看就会,一写就废, 如何克服?”,有一个可能是你没有分治思维。 我们的大脑天生适合处理一些简单的东西,而不适合处理看起来就很复杂的东西。因此面对一个很复杂的东西,第一件事情应该是思考是否可以将其分解 ,然后逐个击破。 举个例子给大家,如下是一道力扣的 hard 题 《2 出现的次数》,题目描述如下: 12345678910编写一个方法,计算从 0 到 n (含 n) 中数字 2 出现的次数。示例:输入: 25输出: 9解释: (2, 12, 20, 21, 22, 23, 24, 25)(注意 22 应该算作两次)提示:n <= 10^9 很多人一看到题就蒙了,这要多少种情况啊? 总不可能一个数字一个数字试过去吧? 其实看一眼数据范围中 n 上限是 10^9,大于 1000 万,就知道不能这样暴力。 于是悄悄打开题解,不仅感叹“原来是这样啊!”,“这怎么想到的?这什么脑子啊!” 来让我告诉你,你缺啥。 你缺的不是一个好使的脑子,而是一个懂得将复杂问题变成若干个简单问题的意识和能力。 以这道题来说, 我可以将其分解为几个子问题。 从 0 到 n (含 n) 中 个位 数字 2 出现的次数 从 0 到 n (含 n) 中 十位 数字 2 出现的次数 。。。 最终的答案就是以上几个子问题的和。 经过这样的思路,大家一下子就能打开思路。剩下的任务就简单了。因为每次固定一位之后,就将数字分为了左右两部分,那么该位是 2 的次数就是左右所有可能的笛卡尔积,即 a * b。 比如 n 是 135。 百位上不可能是 2,因为 2xx 一定超过 135 了。 那十位有多少个 2 呢?按照上面的思路: 左边就是百位,百位可能是 0 或者 1,共 2 种可能。 右边就是个位,个位可能是 [0 - 9] 共 10 种可能。 那么十位是 2 的次数就是 2 * 10 = 20。 那个位有多少个 2 呢?按照上面的思路: 左边就是十位和百位,其可能是 [0-13],共 14 种可能。 右边啥都没有,1 种可能。 那么个位是 2 的次数就是 14 种。 因此不超过 135 的数字中 2 的出现次数就是 20 + 14 = 34 种。 当然,这里面还有一些细节,比如如果某一位比 2 小或者正好是 2 怎么办?我就不在这里讲了。这里直接贴下代码,大家自己继续完成好了。 123456789101112class Solution: def numberOf2sInRange(self, n: int) -> int: ans = 0 m = 1 while m <= n: cur = n // m % 10 if cur > 2: ans += (n // m // 10 + 1) * m else: if cur == 2: ans += n % m + 1 ans += (n // m // 10) * m m *= 10 return ans 把 2 换成其他数字 x,那就可以计算不超过 n 的 x 的出现次数。 举这个例子就想告诉大家为啥一些题目你压根就没有思路的原因: 要么就是这种题没见过,那没办法,多做题呗。 要么就是你算法思维还不够。比如我上面讲的分治的算法思维。 一看就会又说明这种题你是回答过的,因此一看就会,一写就废,一般都是没有养成良好的算法思维,而分治就是一种非常重要的算法思维。当算法思维有了,剩下的细节就慢慢练习就好了,这没有捷径。但是算法思维是有捷径的,大家在刷题之前要特别注重算法思维的学习。 我再举几个例子给大家,帮助大家加深理解。 三个题目带你理解分治思想 在一个数组 nums 中找值为 target 的元素,并返回数组下标,题目保证 nums 中有且仅有一个数等于 target。 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。(322. 零钱兑换) n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。(51. N 皇后) 这几道题覆盖了简单中等和困难三种难度。接下来,我们来看下这几个题。 第一题对于第一题, 答案无非就是 [0, n - 1]。因此我们可以将问题分解为以下几个子问题: 是 0 么?no 是 1 么?no 是 2 么?no 。。。 最终的答案就是子问题中回答为 “yes” 的索引。严格意义上来说,这里只有分,没有治,而且这个分和前面的分有微妙的差异。前面的分完之后后面还要用,这个分是直接给扔掉了。类似的有二分法,二分法就是一种只有分没有治的“分治法”。 第二题coins 是个变量, amount 也是变量,它们关系感觉好多的样子?我该怎么理清呢? 我们从特殊入手,比如 coins = [1, 2, 5], amount = 11。为了方便描述,原问题我用 f([1,2,5], 11) 表示 coins 为 [1,2,5],amount 为 11 的最少需要多少硬币凑齐。 我也不知道最终的最少硬币方案是怎么选的。那我就所有情况都走一遍呗,比较一下哪种方案用硬币最少就用哪个不就行了么? 最终的算法还真就是基于这个朴实的想法来的。 选第一枚硬币的时候,一共只有三种情况:选择 1,选择 2,选择 5。 如果我们先选了 1,那么再凑出 10 就行了。那怎么凑出 10 呢?不就是 f([1, 2, 5], 10) 么? 如果我们先选了 2,那么再凑出 9 就行了。那怎么凑出 9 呢?不就是 f([1, 2, 5], 9) 么? 如果我们先选了 5,那么再凑出 6 就行了。那怎么凑出 6 呢?不就是 f([1, 2, 5], 6) 么? 上面是选取一个硬币的情况,由于没有凑到 amount,我们继续重复,直到凑到 amount。 于是你可以画出类似如下的逻辑树结构,由于节点太多我没有画全。 有没有发现你的大脑直接处理大问题没有思路,但将其分解为小问题就简单了许多?分完了,我们还要治。 这就好像你是主管,向下面布置了作业,布置完了你还要收作业将他们汇总起来搞个 ppt 啥的。 不过这也不难。由于问题是最少硬币,那么治就取最少呗。 11 + min(f([1,2,5], 10),f([1,2,5], 9),f([1,2,5], 6)) 总结一下: 这道题的分我们可以从几个特例入手就可以打开思维。上面的分的手段用伪代码描述就是: 123for (int coin : coins) { f(coins, amount - coin)} 分完了就是处理边界和治了。 完整的分治代码就是: 12345678910111213141516public int f(coins, amount) { if (amount < 0) { // 非法解,用正无穷表示 return Float.POSITIVE_INFINITY } // 叶子节点 if (amount == 0) { // 找到一个解,是不是最小的”治“阶段处理。 return 0 } int ans = Float.POSITIVE_INFINITY for (int coin : coins) { ans = Math.min(ans, 1 + f(coins, amount - coins)) } return ans} 为了突出我的算法主框架,略去了一些细节。 比如原题在无解的时候需要返回 - 1,而我返回的是正无穷。 如果之前做过这道题的朋友应该知道这是一个典型的背包问题。如果现在让我做,我可能也直接自底向上 dp table 解决了(不过 dp table 和记忆化递归没有本质的思维差别)。但是算法是如何想出来的这一点,是如何一步一步优化的,大家一定钻到底,这样刷题效率才高。 第三题不懂题目意思的可以去看下力扣原题 51. N 皇后。这道题就是典型的回溯题目,什么是回溯?一言以蔽之,那就是一个一个试,不行了就返回上一步继续试。 这么多格子我该放哪呢?每个格子还有制约关系!好乱,没有思路。 别急,继续使用分治的思维。这道题是让我们将 N 个皇后放到 N X N 的棋盘上。那不就是: 第一行的皇后应该放到第几列? 第二行的皇后应该放到第几列? 第三行的皇后应该放到第几列? 。。。 改成”第 x 列的皇后应该放到第几行?”这种子问题划分模式也是可以的。 伪代码: 1234567public int placeRow(i) { // 决定应该放到第几列}for (int i=0;i<n;i++) { placeRow(i)} 如果上面的子问题都解决了,那整个问题不就解决了么? 但是上面的子问题,还是无法直接解决。比如“第一行的皇后应该放到第几列?”我也不知道啊。没关系,我们继续对“第一行的皇后应该放到第几列?” 这个问题进行分解。 第一行的皇后放到第 1 列么? 第一行的皇后放到第 2 列么? 第一行的皇后放到第 3 列么? 。。。 继续完善上面的 placeRow 代码即可。这里给出伪代码: 1234567891011public boolean canPlaceQueue(i, j) { // 根据目前的棋局(放了是否能不相互攻击),分析 i 和 j 这个位置能否放女王。}public int placeRow(i) { for (int j=0;j<n;j++) { if (canPlaceQueue(i, j)) { // 将女王放到 (i,j),更新当前棋局 placeQueue(i, j) } }} 现在的问题就只剩下实现canPlaceQueue(i, j) 和 placeQueue(i, j)了,这两个函数根据题目要求模拟实现即可。 需要注意的是我们做了一个placeQueue(i, j) 的操作,这可能是一个 mutable 的操作。因此如果一条路行不通需要回溯,那么 mutable 的数据需要撤销修改。当然如果你的数据是 immutable 就无所谓了。不过 immutable 则有可能内存移除或者超时的风险。 由于这里只是讲思维的,不是讲题目本身的,因此还是点到为止,后面的算法细节我就不讲了,希望读者能自己将代码完善一下。 更多类似的例子实在太多了,根本举不过来,我随口给大家说几个。 如果让你求一个数组的连续子数组总个数,你会如何求?其中连续指的是数组的索引连续。 比如 [1,3,4],其连续子数组有:[1], [3], [4], [1,3], [3,4] , [1,3,4],你需要返回 6。分治就好了,连续子数组个数等于:以索引为 0 结尾的子数组个数 + 以索引为 1 结尾的子数组个数 + … + 以索引为 n - 1 结尾的子数组个数 70. 爬楼梯 让你求爬到最后一级台阶有多少种方法。这太多了,我数不过来。但是我可以将其分解成两个子问题。如果我用 f(n) 表示爬到第 n 级的方法数,那么 f(n) = f(n - 1) + f(n - 2)。但是 n - 1 我也不会啊,没关系,我们继续分解。这和上面的硬币问题有多大差别么? 对于这道题,分就是拆成两个子问题,治就是求和。 这就是最简单的无选择的递推动态规划 746. 使用最小花费爬楼梯 换了个皮又来了? 220 场周赛 - 跳跃游戏 VI 这不还是上面爬楼梯换皮么?这次变成了一次能爬 k 级台阶罢了。 这道题数组长度是 $10^5$,如果不做优化复杂度会是 $N^2$,算起来就是 $10^10$ 过不了,大于上面给大家讲的 1000 万。如何优化有点跑题了,就不在这里讲了。 62. 不同路径 穿个二维的衣服就看不出你是爬楼梯了? 相关换皮题目太多,大家可以去我的插件里看。 总结本次给大家分享了一个很重要的算法思想分治,很多题都可以用到这个思想。能运用分治思想的专题有“动态规划”,”分治“,“回溯” 等,大家在平时做题的时候可以参考我的这种思考方式。 如果你碰到一个复杂的问题,可以尝试以下几个方法。 不妨先尝试将其拆解,看能否将其拆解成几个小问题。 在草稿上画画图,从特殊情况入手,看能否发现什么蛛丝马迹 暴力模拟。看能否通过剪枝和添加恰当的数据结构来优化算法,使之通过。 如果你有更好的干货技巧,非常希望你能和我交流,万分期待! 除了算法思想,我还和大家分享两个超实用的技巧,分别是: 看关键字。关键字很多时候起到了提示作用,甭管对不对,咱要想到。想到之后迅速脑子中过一下能不能 AC。 看限制条件。 记住一个数字就行了,1000 万。 最后和大家说了一个小心得 - ”不要小看暴力法“。暴力法不仅能帮助你打开思路,有时候甚至暴力 + 剪枝(或数据结构优化)就过了。大力出奇迹,欧耶!(^o^)/ 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/categories/刷题方法/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/tags/刷题方法/"}]},{"title":"力扣 APP 全新改版,史诗级增强!","slug":"leetcode-app-1","date":"2020-12-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.468Z","comments":true,"path":"2020/12/16/leetcode-app-1/","link":"","permalink":"https://lucifer.ren/blog/2020/12/16/leetcode-app-1/","excerpt":"这次的改版真的是判若两人,PC 端的几乎所有功能都可以在新版 APP 中看到,并且体验更好。 不仅之前令我不爽的地方全部不见了,而且多了一些我想都没想到的好用功能。 比如摇一摇功能。 我拿到了体验版第一时间就给大家写了这篇体验报告。下面,西法就带你看看全新版本都有啥。","text":"这次的改版真的是判若两人,PC 端的几乎所有功能都可以在新版 APP 中看到,并且体验更好。 不仅之前令我不爽的地方全部不见了,而且多了一些我想都没想到的好用功能。 比如摇一摇功能。 我拿到了体验版第一时间就给大家写了这篇体验报告。下面,西法就带你看看全新版本都有啥。 每日一题 & 推荐题库这可能是大家最关注的一个功能了。我想看一个每日一题还必须去 PC 端才能看到,这就不方便了。 新榜的 APP 直接就可以在题库标签的顶部看到。 除了每日一题, 新榜的 APP 还把其他题库一起装进来了。比如大家熟悉的《剑指 Offer》 和 《程序员面试金典》。 代码展示之前 APP 代码真的不是给人看的,还会换行,所以我一般都只能是复制出来到别的地方看。 后面力扣对代码进行了优化, 使得代码不换行,而是通过左右滑动的方式查看,相比之前体验好了很多。而现在新版 APP 对代码的展示又进行了优化,真的是丝滑般柔顺了,大家看下对比效果。 (旧版效果) (新版效果) 这对比应该很明显了。手动点赞 o( ̄ ▽  ̄)d 消息功能增强相比之前手机 APP 只能是关注的人参与了社区讨论才会收到通知,写个题解什么的根本就不会接受到通知。 现在大家可以在今天标签页下直接能看到你关注的所有人的动态。 这才是关注应该有的样子嘛。 小提示:力扣的小伙伴的点下我头像的关注按钮,这样就会第一时间收到我的动态啦~ 除此之外,通知功能也对齐 PC 端,对消息内容进行了分组展示。 其他功能摇一摇切换中英文描述,摇一摇随机一题 学习分析 下载现在还在灰度内测阶段,内测的话需要填写一份问卷 https://shimo.im/forms/8CDdY3VcyRRgwhPq/fill 。不想填问卷也没关系,正式版也快和大家见面了,等到正式版发布大家就可以去各大 APP Store 中进行下载了。","categories":[{"name":"软件工具","slug":"软件工具","permalink":"https://lucifer.ren/blog/categories/软件工具/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"《西法的刷题秘籍》电子书开放下载啦~","slug":"leetcode-ebook-1","date":"2020-12-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.819Z","comments":true,"path":"2020/12/13/leetcode-ebook-1/","link":"","permalink":"https://lucifer.ren/blog/2020/12/13/leetcode-ebook-1/","excerpt":"2019-07-10 :纪念项目 Star 突破 1W 的一个短文, 记录了项目的”兴起”之路,大家有兴趣可以看一下,如果对这个项目感兴趣,请点击一下 Star, 项目会持续更新,感谢大家的支持。 2019-10-08: 纪念 LeetCode 项目 Star 突破 2W,并且 Github 搜索“LeetCode”,排名第一。 2020-04-12: 项目突破三万 Star。 2020-04-14: 官网力扣加加上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/","text":"2019-07-10 :纪念项目 Star 突破 1W 的一个短文, 记录了项目的”兴起”之路,大家有兴趣可以看一下,如果对这个项目感兴趣,请点击一下 Star, 项目会持续更新,感谢大家的支持。 2019-10-08: 纪念 LeetCode 项目 Star 突破 2W,并且 Github 搜索“LeetCode”,排名第一。 2020-04-12: 项目突破三万 Star。 2020-04-14: 官网力扣加加上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/ 前言这是我将我的所有公开的算法资料整理的一个电子书,全部题目信息中文化,以前会有一些英文描述,感谢 @CYL 的中文整理。 我写这本电子书花费了大量的时间和精力,除了内容上的创作,还要做一些电子书的排版,以让大家获得更好的阅读体验。光数学公式的展示,我就研究了多个插件的要源码,并魔改了一下才使得导出的电子书支持 latex。 不过有些动图,在做成电子书的时候自然就变没了,如果需要看动图的, 可以去我的公众号《力扣加加》或者我的 leetcode 题解仓库看。 由于是电子书,因此阅读体验可能会更好, 但是相应地就不能获得及时的更新,因此你可以收藏一下我的同步电子书的网站 西法的刷题秘籍 - 在线版。后期可能将每日一题, 91 天学算法其他章节的讲义等也整理进来。 电子书有更新我也会在公众号《力扣加加》进行通知, 感兴趣的同学可以关注一下。 目前导出了四种格式,可惜的是这几种格式都有自己的不足: 在线版。 实时更新,想要及时获取最新信息的可以用在线版。 html。 方便大家在线观看,由于是 html ,实际上大家也可以保存起来离线观看。 pdf。可使用 pdf 阅读器和浏览器(比如谷歌)直接观看,阅读体验一般,生成的目录不能导航。 mobi。 下载一个 Kindle 客户端就可以看,不需要购买 Kindle。 epub。 数学公式和主题都比较不错, 但是代码没有高亮。 大家选择适合自己的格式下载即可。 在线版 html, pdf,mobi 和 epub 格式,关注我的公众号《力扣加加》回复电子书即可。 介绍leetcode 题解,记录自己的 leetcode 解题之路。 本仓库目前分为五个部分: 第一个部分是 leetcode 经典题目的解析,包括思路,关键点和具体的代码实现。 第二部分是对于数据结构与算法的总结 第三部分是 anki 卡片, 将 leetcode 题目按照一定的方式记录在 anki 中,方便大家记忆。 第四部分是每日一题,每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块。 第五部分是计划, 这里会记录将来要加入到以上三个部分内容 只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 非科学人士看过来如果是国内的非科学用户,可以使用 https://lucifer.ren/leetcode ,整站做了静态化,速度贼快!但是阅读体验可能一般,大家也可以访问力扣加加(暂时没有静态化)获得更好的阅读体验。 另外需要科学的,我推荐一个工具, 用户体验真的是好,用起来超简单, 提供一站式工具,包括网络检测工具,浏览器插件等,支持多种客户端(还有我最喜欢的 Switch 加速器),价格也不贵,基础套餐折算到月大约 11.2 块/月。它还支持签到送天数,也就是说你可以每天签到无限续期。地址:https://glados.space/landing/M9OHH-Q88JQ-DX72D-R04RN 怎么刷 LeetCode? 我是如何刷 LeetCode 的 算法小白如何高效、快速刷 leetcode? 刷题插件 刷题效率低?或许你就差这么一个插件 力扣刷题插件 91 天学算法 91 天,遇见不一样的自己 食用指南 我对大部分题目的复杂度都进行了分析,除了个别分析起来复杂的题目,大家一定要对一道题的复杂度了如指掌才可以。 有些题目我是故意不写的, 比如所有的回溯题目我都没写, 不过它们全部都是指数的复杂度 我对题目难度进行了分类的保留,因此你可以根据自己的情况刷。我推荐大家从简单开始,逐步加大难度,直到困难。 这里有一张互联网公司面试中经常考察的问题类型总结的思维导图,我们可以结合图片中的信息分析一下。 (图片来自 leetcode) 其中算法,主要是以下几种: 基础技巧:分治、二分、贪心 排序算法:快速排序、归并排序、计数排序 搜索算法:回溯、递归、深度优先遍历,广度优先遍历,二叉搜索树等 图论:最短路径、最小生成树 动态规划:背包问题、最长子序列 数据结构,主要有如下几种: 数组与链表:单 / 双向链表 栈与队列 哈希表 堆:最大堆 / 最小堆 树与图:最近公共祖先、并查集 字符串:前缀树(字典树) / 后缀树 精彩预告0042.trapping-rain-water: 0547.friend-circles: backtrack problems: 0198.house-robber: 0454.4-sum-ii: anki 卡片Anki 主要分为两个部分:一部分是关键点到题目的映射,另一部分是题目到思路,关键点,代码的映射。 全部卡片都在 anki-card 使用方法: anki - 文件 - 导入 - 下拉格式选择“打包的 anki 集合”,然后选中你下载好的文件,确定即可。 更多关于 anki 使用方法的请查看 anki 官网 目前已更新卡片一览(仅列举正面): 二分法解决问题的关键点是什么,相关问题有哪些? 如何用栈的特点来简化操作, 涉及到的题目有哪些? 双指针问题的思路以及相关题目有哪些? 滑动窗口问题的思路以及相关题目有哪些? 回溯法解题的思路以及相关题目有哪些? 数论解决问题的关键点是什么,相关问题有哪些? 位运算解决问题的关键点是什么,相关问题有哪些? 已加入的题目有:#2 #3 #11","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"电子书","slug":"电子书","permalink":"https://lucifer.ren/blog/tags/电子书/"}]},{"title":"来和大家聊聊我是如何刷题的(第二弹)","slug":"shuati-silu2","date":"2020-12-11T16:00:00.000Z","updated":"2023-01-05T12:24:49.860Z","comments":true,"path":"2020/12/12/shuati-silu2/","link":"","permalink":"https://lucifer.ren/blog/2020/12/12/shuati-silu2/","excerpt":"上一篇的地址在这里,没有看过的同学建议先看第一篇 来和大家聊聊我是如何刷题的(第一弹)。 这次继续给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第二篇,本系列至少会出三篇。这次分享的内容是代码书写技巧以及调试技巧。 本系列旨在分享一些所有题目都适用的技巧以及一些刷题经验,帮助大家高效刷题。如果想重点突破某一类题目,可以关注我的专题系列。 话不多说,直接上干货。","text":"上一篇的地址在这里,没有看过的同学建议先看第一篇 来和大家聊聊我是如何刷题的(第一弹)。 这次继续给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第二篇,本系列至少会出三篇。这次分享的内容是代码书写技巧以及调试技巧。 本系列旨在分享一些所有题目都适用的技巧以及一些刷题经验,帮助大家高效刷题。如果想重点突破某一类题目,可以关注我的专题系列。 话不多说,直接上干货。 先刷什么?刷多少?正式介绍技巧之前先回答一个问题,这也是我被问的比较多的两个问题是:我该先刷什么算法?每一种算法我该刷多少? 现在我们就来看下这个问题。 先贴一个 91 天学算法 中某一小节的讲义中的一部分内容: 递归(10) BFS & DFS(20) 双指针(20) 滑动窗口(6) 哈希表(20) 回溯(5) 动态规划(20) 排序(3) 分治(20) 堆(3) 贪心(5) 设计题(5) 图(5) 位运算(5) 并查集(3) 如果你不知道从何刷起,可以参考我的这个刷题顺序,其中括号是我推荐的最小刷题量,就是说再少不能少于这个数字。如果你想多刷,可以按照我的这个比例去刷。等到你自己有个概念,知道自己哪里薄弱了,再去针对加强即可。 具体题目太多了不列举了,给大家几个题目集合做参考: 🔥 热题 HOT 100 👨‍💻 精选 TOP 面试题 企业题库。 比如 🐧 腾讯精选练习 50 题,企业题库 - 字节跳动(当你就想去某一家公司的时候可以用) 剑指 Offer 网友内幕(主要是面经) 力扣的探索和标签 知道该刷什么,以及刷多少了,可能你已经迫不及待投入题海了。 不要着急,可以先看下西法有没有写过相关专题,如果写过, 强烈建议你先看下,一定能让你事半功倍。 我已经开始刷专题了,有没有什么通用的技巧呢?答案是有,而且很多。本文只介绍一部分, 后续我们继续这个话题,给大家带来更多干货技巧。 代码书写技巧代码书写技巧,这次给大家带来三个技巧: 改参数 zip 函数的妙用 关于取模 改参数力扣的参数是可以改名字的,如下图: 你可以将名字改成一个短的或者你熟悉的。比如上面这道题,我写的时候就可以: 123class Solution: def findMedianSortedArrays(self, A: List[int], B: List[int]) -> float: # can use A and B now 这可以使得代码看起来简洁且具有一致性。 经常看我题解的小伙伴应该注意到我的代码比较简洁。一方面是因为我经常用 Python,另一方面就是因为这个技巧。 这里我顺便吐槽一下力扣。力扣的形参命名相当不规范。比如二维数组有时候是 mat,有时候是 nums,有时候是 matrix,有时候又是 grid 。。。 真心不舒服,不过有了这个技巧,大家就不要依赖官方了,自己统一一下就好。 就拿我来说,二维数组我就用 mat 或者 matrix,一维数组用 nums 或者 A 或者 A 和 B(两个一维数组的情况)。比如: 123456# A 和 B 是两个一维的数组def test(A, B): for a in A: # do something for b in B: # do something else 其实不仅仅是形参的命名要统一,我们内部的代码也是一样的。对于我来说: 堆我习惯叫 h 图我习惯叫 graph 队列我习惯叫 q 。。。 大家没有必要和我一样,但是一定要保持一致性,这样可以显著增加代码可读性,可读性高了,调试工作也会变得轻松。 zip 函数的妙用力扣有一些题目会给你两个或者三个一维数组,这两个一维数组的是有关联的。 比如给你两个一维数组 A 和 B,其中 A[i] 表示第 i 个人的体重,B[i] 表示第 i 个人的身高。也就是说都是表示第 i 个人,但是表示的东西不一样。 其实逻辑上就相当于结构体,而且如下结构体的形式在工作中更常见。 1234interface Person { weight: number; height: number;} 但是力扣以两个数组的形式给你了,其实这样不难啊,不就是用一个索引记录么? 1234for(int i= 0;i<A.length;i++) { int weight = A[i] int height = B[i]} 但是如果我需要对重量排序呢?如果你仅仅对 A 排序了,B 也需要进行相应调整的,否则对应关系就乱了。那遇到这样的情况该怎么办呢? 这里介绍一个我经常使用的技巧 zip。 123456zipped = zip(A, B)# 下面我对其进行排序也不会改变相对顺序zipped.sort()# 比如 A 是 [1,2,3] B 是 [4,5,6]# 那么 zipped 就是 [[1,4], [2,5], [3,6]]# 那么 zipped[i][0] 就是第 i 个人的体重,zippd[i][1] 就是第 i 个人的身高 由于 A 和 B “捆绑”到一起了,因此排序也不会改变其相对顺序。 如下是我在力扣 1383 题中使用 zip 技巧的例子: 另外 zip 还有一些其他用处。比如我想要获取当前数组位置的前一项。 不用 zip 可以这么做: 123for i in range(1, len(A)): pre = A[i - 1] cur = A[i] 如果使用 zip 可以这样: 12for pre, cur in zip(A, A[1:]): # do something 这里的原理也很简单。我举个例子你就懂了。比如有一个数组 A :[1,2,3,4]。 那么 A[1:] 就是 [2,3,4] 我将如上两个数组 zip 起来就是 [[1,2], [2,3], [3,4]],所以我对 zip 之后的结果进行遍历就可以方便地写代码了。 这个技巧用处不大,可以不必掌握,大家知道有这么回事就行 有的人可能想问,我的语言没有 zip 怎么办? 我的答案是自行实现 zip。 比如 JavaScript 可以这样实现 zip: 1const zip = (rows) => rows[0].map((_, c) => rows.map((row) => row[c])); 你把它改造成自己的语言版本即可。 关于取模力扣中有很多题目需要你对返回值取模,而且一般都是对 109 + 7 取模。 题目答案让取模那肯定是答案太大了,为啥太大了呢?有啥想法没? 比如 1680. 连接连续二进制数字 题目描述: 1给你一个整数 n ,请你将 1 到 n 的二进制表示连接起来,并返回连接结果对应的 十进制 数字对 109 + 7 取余的结果。 如果我忘记取模或者仅在返回的时候取模都可能会报错,正确的姿势是提前取模。 以上面的题目来说,代码这样写是可以过的。 1234567class Solution: def concatenatedBinary(self, n: int) -> int: ans = 0 mod = 10 **9 + 7 for i in range(1, n + 1): ans = (ans * pow(2, len(bin(i)[2:])) + i) % mod return ans % mod 而如果我这么写会超时(没有提前取模,只是在最后返回才取模): 1234567class Solution: def concatenatedBinary(self, n: int) -> int: ans = 0 mod = 10 **9 + 7 for i in range(1, n + 1): ans = (ans * pow(2, len(bin(i)[2:])) + i) return ans % mod 如果不提前 mod, python 可能超时,其他语言可能溢出。 提前取 mod,会把数值限定在 int 能处理的范围,使用机器自身整数运算功能进行快速运算,而如果之后取 mod,由于 python 对大整数支持的特性,会将 ans 转换为大整数再进行运算,计算相对耗时。 说到溢出,我想起来一个小技巧,那就是二分取中间值的时候如果书写不当也可能溢出。 12# 如下代码,如果 l 和 r 比较大,则可能发生大数溢出mid = (l + r) // 2 这里的 // 是地板除 解决的方案也很简单,这样写就行了: 1mid = (r - l) // 2 + l 另外一个和取模有关的小技巧是判断奇偶。 判断一个数是奇数还是偶数可以通过和 2 取模。 如果返回值是 0 则是偶数,否则是奇数。 需要注意的是和 2 取模为 1 奇数,但是反之不然。即和 2 取模不是 1 也可能是奇数,比如负数,因此还需要多个判断,不如用我上面的方法,即和 2 取模判断是否等于 0。 欢迎大家补充其他小技巧~ 其他技巧这里有一个要和大家强调的点,很多刚刷题的人都不知道,那就是尽量不要使用全局变量。 如果使用全局变量且没有及时清除,不仅可能有性能问题,更可能会在多个测试用例之间形成干扰,导致出错。而且在力扣设计题目通常是会多次调用某一个 api 的。这个时候更是如此,所以不要使用全局变量。 有一些朋友向我反馈“为啥本地好好的,放到力扣上提交就不行”,请先检查下有没有使用全局变量。 调试技巧调试技巧,我们这里先讲两个: 批量测试 数据结构可视化(树的可视化) 批量测试力扣的测试用例其实是可以一次写多个的。 如上图,该题目有两个参数。那两行其实就是一个完整用例。 我这里输入了六行,也就是三个用例。这个时候点击执行,就可以一次执行三个用例。 妈妈再也不用担心我提交太频繁啦~ 执行成功后,我们可以一次查看所有的差异。 如果你老是考虑不到各种边界,那这个功能简直是福音。 另外如果你打比赛,你可以把题目给的测试用例批量复制到这里一次执行看结果,非常有用。 为了方便大家复制所有题目内置的测试用例,我的刷题插件 leetcode-cheatsheet增加了一个功能一键复制所有的内置用例。 正常情况,下点击之后会提示你复制成功,你只需要 ctrl + v 粘贴到测试用例的输入框即可。 但是力扣网站有很多不是很统一的地方,这就需要我不断进行兼容。比如如下兼容代码: 上面代码指的是力扣测试用例的 html 标签是不定的,并且有时候是”输入:“(注意是中文的:),有时候又是”输入:“(注意是英文的:)。 因此难免有我无法兼容的情况。因此就会发生类似这样的情况: 遇到这样的情况,你可以点击弹出信息的反馈文字,给我反馈。不过据我的测试,大部分情况是没问题的。 插件目前已经发布到谷歌商店了,通过谷歌商店安装的朋友审核通过后会自动更新。离线安装的朋友需要手动安装,不过我的更新蛮频繁的,强烈建议在线安装。商店地址:https://chrome.google.com/webstore/detail/leetcode-cheatsheet/fniccleejlofifaakbgppmbbcdfjonle?hl=en-US 上线几天已经有 100 多人安装了, 你确定不试试么? 树的可视化力扣支持大家对树进行可视化,只要点击这个树结构可视化按钮即可(只有树的题目才有这个按钮)。 如果你写了多个数组,也并不会生成多个树,貌似是以最后一次输入为准。 力扣暂时没有提供其他数据结构的可视化,比如数组,链表等。这可能对大部分人来说没什么,但是对于我这样经常写题解,画图的人就不一样了。如果可以快速画图,那么对我效率肯定有大幅度的提升。 lucifer 建议大家也养成写题解的好习惯。 因此我打算在我的刷题插件里面加其他数据结构的可视化功能, 已经在规划啦~ 现在草稿了一些东西。 比如这样的树: 和这样的树: 现在其实还有些问题,而且我想多加几种数据结构方便写题解,所以就之后再说好了。 本地调试技巧我们可以通过按照编辑器插件在本地编辑器中写代码,然后通过编辑器插件将其提交到力扣即可。 这样你在本地的调试插件都可以用于算法调试了。 这里推荐两个可视化调试插件: Cyberbrain vscode-debug-visualizer 推荐一个网站OI Wiki 致力于成为一个免费开放且持续更新的 编程竞赛 (competitive programming) 知识整合站点,大家可以在这里获取与竞赛相关的、有趣又实用的知识。我们为大家准备了竞赛中的基础知识、常见题型、解题思路以及常用工具等内容,帮助大家更快速深入地学习编程竞赛中涉及到的知识。 地址:https://oi-wiki.org/ 其他力扣提交成功之后除了可以看到自己的排名情况(击败百分之多少),还可以查看别人的提交代码。 这里可以看所有分段的分布情况,也可以直接点击对应的柱子,查看别人的代码怎么写的。比如我这里直接点开了击败 100% 的代码,研究下 ta 是怎么写的。 发现确实代码比我的好,于是我就又”学会“了一招(技能++)。 以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 预告下期给大家讲更加干货的技巧,一定不要错过哦。 一看就会,一写就废, 如何克服? 如何锁定使用哪种算法。比如我看到了这道题,我怎么知道该用什么解法呢?二分?动态规划?","categories":[{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/categories/刷题方法/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/tags/刷题方法/"}]},{"title":"我是如何把简单题目做成困难的?","slug":"91-tougao-xiaoyang-01","date":"2020-12-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.834Z","comments":true,"path":"2020/12/04/91-tougao-xiaoyang-01/","link":"","permalink":"https://lucifer.ren/blog/2020/12/04/91-tougao-xiaoyang-01/","excerpt":"作者:小漾 来源:https://github.com/suukii/91-days-algorithm 大家好,我是 lucifer,众所周知,我是一个小前端 (不是) 。其实,我是 lucifer 的 1379 号迷妹观察员,我是一粒纳米前端。(不要回答,不要回答,不要回答!!!) 这是第一次投稿,所以可以废话几句,说一下我为什么做题和写题解。刚开始做算法题的时候,只是纯粹觉得好玩,所以不仅没有刷题计划,写题解也只是随便记下几笔,几个月后自己也看不懂的那种。一次偶然机会发现了 lucifer 的明星题解仓库,是找到了 onepiece 的感觉。受他的启发,我也开始写些尽量能让人看懂的题解,虽然还赶不上 lucifer,但跟自己比总算是有了些进步。 身为迷妹观察员,lucifer 的 91 天学算法当然是不能错过的活动,现在活动的第二期正在 🔥 热进行中,有兴趣的同学了解一下呀。言归正传,跟着 91 课程我不再是漫无目的,而是计划清晰,按照课程安排的专题来做题,这样不仅更有利于了解某一类题涉及的相关知识,还能熟悉这类题的套路,再次遇见相似题型也能更快有思路。 废话就这么多,以下是正文部分。等等,还有最后一句,上面的”不要回答”是个三体梗,不知道有没有人 GET 到我。 今天给大家带来一道力扣简单题,官方题解只给出了一种最优解。本文比较贪心,打算带大家用四种姿势来解决这道题。 ​","text":"作者:小漾 来源:https://github.com/suukii/91-days-algorithm 大家好,我是 lucifer,众所周知,我是一个小前端 (不是) 。其实,我是 lucifer 的 1379 号迷妹观察员,我是一粒纳米前端。(不要回答,不要回答,不要回答!!!) 这是第一次投稿,所以可以废话几句,说一下我为什么做题和写题解。刚开始做算法题的时候,只是纯粹觉得好玩,所以不仅没有刷题计划,写题解也只是随便记下几笔,几个月后自己也看不懂的那种。一次偶然机会发现了 lucifer 的明星题解仓库,是找到了 onepiece 的感觉。受他的启发,我也开始写些尽量能让人看懂的题解,虽然还赶不上 lucifer,但跟自己比总算是有了些进步。 身为迷妹观察员,lucifer 的 91 天学算法当然是不能错过的活动,现在活动的第二期正在 🔥 热进行中,有兴趣的同学了解一下呀。言归正传,跟着 91 课程我不再是漫无目的,而是计划清晰,按照课程安排的专题来做题,这样不仅更有利于了解某一类题涉及的相关知识,还能熟悉这类题的套路,再次遇见相似题型也能更快有思路。 废话就这么多,以下是正文部分。等等,还有最后一句,上面的”不要回答”是个三体梗,不知道有没有人 GET 到我。 今天给大家带来一道力扣简单题,官方题解只给出了一种最优解。本文比较贪心,打算带大家用四种姿势来解决这道题。 ​ 题目描述题目地址:https://leetcode-cn.com/problems/shortest-distance-to-a-character 1234567891011给定一个字符串 S 和一个字符 C。返回一个代表字符串 S 中每个字符到字符串 S 中的字符 C 的最短距离的数组。示例 1:输入: S = "loveleetcode", C = 'e'输出: [3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]说明:字符串 S 的长度范围为 [1, 10000]。C 是一个单字符,且保证是字符串 S 里的字符。S 和 C 中的所有字母均为小写字母。 解法 1:中心扩展法思路这是最符合直觉的思路,对每个字符分别进行如下处理: 从当前下标出发,分别向左、右两个方向去寻找目标字符 C。 只在一个方向找到的话,直接计算字符距离。 两个方向都找到的话,取两个距离的最小值。 复杂度分析我们需要对每一个元素都进行一次扩展操作,因此时间复杂度就是 $N$ * 向两边扩展的总时间复杂度。 而最坏的情况是目标字符 C 在字符串 S 的左右两个端点位置,这个时候时间复杂度是 $O(N)$,因此总的时间复杂度就是 $O(N^2)$ 时间复杂度:$O(N^2)$,N 为 S 的长度。 空间复杂度:$O(1)$。 代码JavaScript Code 1234567891011121314151617181920212223242526272829303132333435363738/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { // 结果数组 res var res = Array(S.length).fill(0); for (let i = 0; i < S.length; i++) { // 如果当前是目标字符,就什么都不用做 if (S[i] === C) continue; // 定义两个指针 l, r 分别向左、右两个方向寻找目标字符 C,取最短距离 let l = i, r = i, shortest = Infinity; while (l >= 0) { if (S[l] === C) { shortest = Math.min(shortest, i - l); break; } l--; } while (r < S.length) { if (S[r] === C) { shortest = Math.min(shortest, r - i); break; } r++; } res[i] = shortest; } return res;}; 解法 2:空间换时间思路空间换时间是编程中很常见的一种 trade-off (反过来,时间换空间也是)。 因为目标字符 C 在 S 中的位置是不变的,所以我们可以提前将 C 的所有下标记录在一个数组 cIndices 中。 然后遍历字符串 S 中的每个字符,到 cIndices 中找到距离当前位置最近的下标,计算距离。 复杂度分析和上面方法类似,只是向两边扩展的动作变成了线性扫描 cIndices,因此时间复杂度就是 $N$ * 线性扫描 cIndices的时间复杂度。 时间复杂度:$O(N*K)$,N 是 S 的长度,K 是字符 C 在字符串中出现的次数。由于 $K <= N$。因此时间上一定是优于上面的解法的。 空间复杂度:$O(K)$,K 为字符 C 出现的次数,这是记录字符 C 出现下标的辅助数组消耗的空间。 实际上,由于 cIndices 是一个单调递增的序列,因此我们可以使用二分来确定最近的 index,时间可以优化到 $N*logK$,这个就留给各位来解决吧。如果对二分不熟悉的,可以看看我往期的《二分专题》 代码JavaScript Code 1234567891011121314151617181920212223242526272829303132333435/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { // 记录 C 字符在 S 字符串中出现的所有下标 var cIndices = []; for (let i = 0; i < S.length; i++) { if (S[i] === C) cIndices.push(i); } // 结果数组 res var res = Array(S.length).fill(Infinity); for (let i = 0; i < S.length; i++) { // 目标字符,距离是 0 if (S[i] === C) { res[i] = 0; continue; } // 非目标字符,到下标数组中找最近的下标 for (const cIndex of cIndices) { const dist = Math.abs(cIndex - i); // 小小剪枝一下 // 注:因为 cIndices 中的下标是递增的,后面的 dist 也会越来越大,可以排除 if (dist >= res[i]) break; res[i] = dist; } } return res;}; 解法 3:贪心思路其实对于每个字符来说,它只关心离它最近的那个 C 字符,其他的它都不管。所以这里还可以用贪心的思路: 先 从左往右 遍历字符串 S,用一个数组 left 记录每个字符 左侧 出现的最后一个 C 字符的下标; 再 从右往左 遍历字符串 S,用一个数组 right 记录每个字符 右侧 出现的最后一个 C 字符的下标; 然后同时遍历这两个数组,计算距离最小值。 优化 1 再多想一步,其实第二个数组并不需要。因为对于左右两侧的 C 字符,我们也只关心其中距离更近的那一个,所以第二次遍历的时候可以看情况覆盖掉第一个数组的值: 字符左侧没有出现过 C 字符 i - left > right - i (i 为当前字符下标,left 为字符左侧最近的 C 下标,right 为字符右侧最近的 C 下标) 如果出现以上两种情况,就可以进行覆盖,最后再遍历一次数组计算距离。 优化 2 如果我们是直接记录 C 与当前字符的距离,而不是记录 C 的下标,还可以省掉最后一次遍历计算距离的过程。 复杂度分析上面我说了要开辟一个数组。而实际上题目也要返回一个数组,这个数组的长度也恰好是 $N$,这个空间是不可避免的。因此我们直接利用这个数组,而不需要额外开辟空间,因此这里空间复杂度是 $O(1)$,而不是 $O(N)$,具体可以看下方代码区。 时间复杂度:$O(N)$,N 是 S 的长度。 空间复杂度:$O(1)$。 代码JavaScript Code 1234567891011121314151617181920212223242526272829/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { var res = Array(S.length); // 第一次遍历:从左往右 // 找到出现在左侧的 C 字符的最后下标 for (let i = 0; i < S.length; i++) { if (S[i] === C) res[i] = i; // 如果左侧没有出现 C 字符的话,用 Infinity 进行标记 else res[i] = res[i - 1] === void 0 ? Infinity : res[i - 1]; } // 第二次遍历:从右往左 // 找出现在右侧的 C 字符的最后下标 // 如果左侧没有出现过 C 字符,或者右侧出现的 C 字符距离更近,就更新 res[i] for (let i = S.length - 1; i >= 0; i--) { if (res[i] === Infinity || res[i + 1] - i < i - res[i]) res[i] = res[i + 1]; } // 计算距离 for (let i = 0; i < res.length; i++) { res[i] = Math.abs(res[i] - i); } return res;}; 直接计算距离: JavaScript Code 123456789101112131415161718192021/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { var res = Array(S.length); for (let i = 0; i < S.length; i++) { if (S[i] === C) res[i] = 0; // 记录距离:res[i - 1] + 1 else res[i] = res[i - 1] === void 0 ? Infinity : res[i - 1] + 1; } for (let i = S.length - 1; i >= 0; i--) { // 更新距离:res[i + 1] + 1 if (res[i] === Infinity || res[i + 1] + 1 < res[i]) res[i] = res[i + 1] + 1; } return res;}; Python Code: 12345678910111213class Solution: def shortestToChar(self, S: str, C: str) -> List[int]: pre = -len(S) ans = [] for i in range(len(S)): if S[i] == C: pre = i ans.append(i - pre) pre = len(S) * 2 for i in range(len(S) - 1, -1, -1): if S[i] == C: pre = i ans[i] = min(ans[i], pre - i) return ans 解法 4:窗口思路把 C 看成分界线,将 S 划分成一个个窗口。然后对每个窗口进行遍历,分别计算每个字符到窗口边界的距离最小值,并在遍历的过程中更新窗口信息即可。 复杂度分析由于更新窗口里的“搜索”下一个窗口的操作总共只需要 $N$ 次,因此时间复杂度仍然是 $N$,而不是 $N^2$。 时间复杂度:$O(N)$,N 是 S 的长度。 空间复杂度:$O(1)$。 代码JavaScript Code 1234567891011121314151617181920212223242526/** * @param {string} S * @param {character} C * @return {number[]} */var shortestToChar = function (S, C) { // 窗口左边界,如果没有就初始化为 Infinity let l = S[0] === C ? 0 : Infinity, // 窗口右边界 r = S.indexOf(C, 1); const res = Array(S.length); for (let i = 0; i < S.length; i++) { // 计算字符到当前窗口左右边界的最小距离 res[i] = Math.min(Math.abs(i - l), Math.abs(r - i)); // 遍历完了当前窗口的字符后,将整个窗口右移 if (i === r) { l = r; r = S.indexOf(C, l + 1); } } return res;}; 小结本文给大家介绍了这道题的四种解法,从直觉思路入手,到使用空间换时间的策略,再到贪心算法思想。最后是一个窗口的解法简单直白,同时复杂度也是最优的思路。 对于刚开始做题的人来说,”做出来”是首要任务,但如果你有余力的话,也可以试试这样”一题多解”,多锻炼一下自己。 但无论怎样,只要你对算法感兴趣,一定要考虑关注 lucifer 这个算法灯塔哦。不要嫌我啰嗦,真话不啰嗦。 更多题解可以访问:https://github.com/suukii/91-days-algorithm end大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 我整理的 1000 多页的电子书已限时免费下载,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"图解数据结构","slug":"91-tougao-tianxing-01","date":"2020-12-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.709Z","comments":true,"path":"2020/12/02/91-tougao-tianxing-01/","link":"","permalink":"https://lucifer.ren/blog/2020/12/02/91-tougao-tianxing-01/","excerpt":"参加了 lucifer 的 91 天学算法活动,不知不觉中已经一月有余。从盲目地做到有目的、有套路地去做。 在 lucifer 的 91 课程中,从基础到进阶到专题,在这个月中,经历了基础篇的洗礼,不管在做题思路,还是做题速度都有了很大的提升,这个课程,没什么好说的,点赞点赞再点赞。也意识到学习好数据结构有多重要,不仅是思维方式的改变,还是在工程上的应用。 对一个问题使用画图、举例、分解这 3 种方法将其化繁为简,形成清晰思路再动手写代码,一张好的图能够更好地帮助去理解一个算法。因此本次分享如何使用画图同时结合经典的题目的方法去阐述数据结构。 ​","text":"参加了 lucifer 的 91 天学算法活动,不知不觉中已经一月有余。从盲目地做到有目的、有套路地去做。 在 lucifer 的 91 课程中,从基础到进阶到专题,在这个月中,经历了基础篇的洗礼,不管在做题思路,还是做题速度都有了很大的提升,这个课程,没什么好说的,点赞点赞再点赞。也意识到学习好数据结构有多重要,不仅是思维方式的改变,还是在工程上的应用。 对一个问题使用画图、举例、分解这 3 种方法将其化繁为简,形成清晰思路再动手写代码,一张好的图能够更好地帮助去理解一个算法。因此本次分享如何使用画图同时结合经典的题目的方法去阐述数据结构。 ​ 数据结构与算法有用么?这里我摘录了一个知乎的高赞回答给大家做参考: 个人认为数据结构是编程最重要的基本功没有之一!学了顺序表和链表,你就知道,在查询操作更多的程序中,你应该用顺序表;而修改操作更多的程序中,你要使用链表;而单向链表不方便怎么办,每次都从头到尾好麻烦啊,怎么办?你这时就会想到双向链表 or 循环链表。学了栈之后,你就知道,很多涉及后入先出的问题,例如函数递归就是个栈模型、Android 的屏幕跳转就用到栈,很多类似的东西,你就会第一时间想到:我会用这东西来去写算法实现这个功能。学了队列之后,你就知道,对于先入先出要排队的问题,你就要用到队列,例如多个网络下载任务,我该怎么去调度它们去获得网络资源呢?再例如操作系统的进程(or 线程)调度,我该怎么去分配资源(像 CPU)给多个任务呢?肯定不能全部一起拥有的,资源只有一个,那就要排队!那么怎么排队呢?用普通的队列?但是对于那些优先级高的线程怎么办?那也太共产主义了吧,这时,你就会想到了优先队列,优先队列怎么实现?用堆,然后你就有疑问了,堆是啥玩意?自己查吧,敲累了。总之好好学数据结构就对了。我觉得数据结构就相当于:我塞牙了,那么就要用到牙签这“数据结构”,当然你用指甲也行,只不过“性能”没那么好;我要拧螺母,肯定用扳手这个“数据结构”,当然你用钳子也行,只不过也没那么好用。学习数据结构,就是为了了解以后在 IT 行业里搬砖需要用到什么工具,这些工具有什么利弊,应用于什么场景。以后用的过程中,你会发现这些基础的“工具”也存在着一些缺陷,你不满足于此工具,此时,你就开始自己在这些数据结构的基础上加以改造,这就叫做自定义数据结构。而且,你以后还会造出很多其他应用于实际场景的数据结构。。你用这些数据结构去造轮子,不知不觉,你成了又一个轮子哥。 既然这么有用,那我们怎么学习呢?我的建议是先把常见的数据结构学个大概,然后开始安装专题的形式突破算法。这篇文章就是给大家快速过一下一部分常见的数据结构。 从逻辑上分,数据结构分为线性和非线性两大类。 线性数据结构包括数组、链表、栈、队列。 非线性结构包括树、哈希表、堆、图。 而我们常用的数据结构主要是数组、链表、栈、树,这同时也是本文要讲的内容。 数据结构一览数组数组的定义为存放在连续内存空间上的相同类型数据的集合。因为内存空间连续,所以能在 O(1)的时间进行存取。 剑指 offer03.数组中的重复的数字题目描述: 12在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 分析: 重复意味至少出现两次,那么找重复就变成了统计数字出现的频率了。那如何统计数字频率呢?(不使用哈希表),我们可以开辟一个长度为 n 的数组 count_nums,并且初始化为 0,遍历数组 nums,使用 nums[i]为 count_nums 赋值. 图解: (注意:数组下标从 0 开始) 剑指 offer21. 调整数组顺序使奇数位于偶数前面题目描述: 12输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 分析: 根据题目要求,需要我们调整数组中奇偶数的顺序,那这样的话,我们可以从数组的两端同时开始遍历,右边遇到奇数的时候停下,左边遇到偶数的时候停下,然后进行交换。 1122.数组的相对排序题目描述: 1234567891011给你两个数组,arr1 和 arr2,arr2 中的元素各不相同arr2 中的每个元素都出现在 arr1 中对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。示例输入:arr1 = [2,3,1,3,2,4,6,7,9,2], arr2 = [2,1,4,3,9,6]输出:[2,2,2,1,4,3,3,9,6,7] 分析: 观察输出,发现数字,因为 arr1 总是根据 arr2 中元素的相对大小来排序,所以只相当于在 arr2 中进行填充,每个地方该填充多少呢?这个时候就需要去统计 arr1 中每个数字出现的频率。 小结在数组中,因为数组是一个有序的结构,这里的有序是指在位置上的有序,所以大多数只需要考虑顺序或者相对顺序即可。 链表链表是一种线性数据结构,其中的每个元素实际上是一个单独的对象,每一个节点里存到下一个节点的指针(Pointer)。就像我们玩的寻宝游戏一样,当我们找到一个宝箱的时候,里面还存在寻找下一个宝箱的藏宝图,依次类推,每一个宝箱都是如此,一直到找到最终宝藏。 通过单链表,可以衍生出循环链表,双向链表等。 我们来看下链表中比较经典的几个题目。 面试题 02.02. 返回倒数第 k 个节点题目描述: 1234567实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。示例:输入: 1->2->3->4->5 和 k = 2输出: 4 分析: 想要找到倒数第 k 个节点,如果此时在数组中,那我们只需要用最后一个数组的索引减去 k 就能找到这个值,但是链表是不能直接通过索引得到的。如果此时,我们知道最后一个节点的位置,然后往前找 k 个不就找到我们需要的节点了吗?等价于我们要找的节点和最后一个节点相隔 k 个位置。所以当有一个指针 front 出发 k 步后,我们再出发,等 front 到达终点时,我们刚好到达倒数第 k 个节点。 我们把这种解法叫做双指针,或者快慢指针,或者前后指针,这种方法可以用于寻找链表中间节点,判断是链表中是否存在环(循环链表)并寻找环入口。 61. 旋转链表题目描述: 12345678给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。示例:输入: 1->2->3->4->5->NULL, k = 2输出: 4->5->1->2->3->NULL 分析: 每个数字向后移动 k 位,那么最后 k 位就需要移动到前面,和找倒数第 k 位数字很相似,k 位后面的都移到开头。唯一需要注意的地方就是,k 的值可能大于链表长度的 2 倍及以上,所以需要算出链表的长度,以保证尽快找到倒数 k 的位置。 解法 1找到位置后,直接断开 解法 2制作循环链表,然后再找倒数第 k 个数,然后断开循环链表 24. 两两交换链表中的节点题目描述: 12345678给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。示例:输入:head = [1,2,3,4]输出:[2,1,4,3] 分析: 原理很简单,两个指针,分别指向相邻的两个节点,然后再添加一个临时指针做换交换的中介添加 dummy 节点,不用考虑头节点的情况,更加方便。直接上图: 除了同时操作一个链表之外,有的题目也会给出两个或者更多的链表,如两数相加,如 leetcode 中 2.两数相加、21.合并两个有序链表、160.相交链表 21.相交节点题目描述: 12编写一个程序,找到两个单链表相交的起始节点。 如下面的两个链表 分析: 我们知道,对于任意两个数 ab,一定有 a+b-c=b+a-c, 基于 a+b-c=b+a-c,我们可以设置两个指针,分别指向 A 和 B,以相同的步长同时移动,并在第一次到达尾节点的时候,指向另一个链表,如果存在相交节点,也就是说 c > 0,那么两个指针一定会相遇,相遇处也就是相交节点。而当不存在时,即 c=0,那么两个指针最终都会指向空节点。 小结链表中的操作无非就是两种,插入,删除。解题方法无非就是添加 dummy 节点(解决头节点的判断问题)、快慢指针(快慢不一定是单次步长一样,应该理解为平均步长,即使用了相同的时间,走的路程的长度来定义快慢)。 栈栈是一种先进后出(FILO, First In Last Out)的数据结构,可以把栈理解为 ![image](https://p.ipic.vip/sdiphq.png) 没错,就是上图的罐装薯片,想要吃到最底下的那片,必须依次吃完上面的。而在装薯片的时候,最底下的反而是最先装进去的。 在 leetcode 里面关于栈比较经典的题目有:20.有效的括号;150.逆波兰表达式求值 20.有效的括号题目描述: 123456789给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。示例:"{[()][]}()"| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 || --- | --- | --- | --- | --- | --- | --- | --- | --- | --- || { | [ | ( | ) | ] | [ | ] | } | ( | ) | 分析: 一个有效的括号为,右边必须和左边对应,且存在至少一对有效括号的索引为[i, i+1]。 那么,我们只要是括号左边部分,就入栈,右边部分,就和栈顶元素比较。 图解: 150.逆波兰表达式求值题目描述: 123456根据 逆波兰表示法,求表达式的值。有效的运算符包括 +, -, \\*, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。示例:["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+"] 分析: 根据运算法则,我们可以知道,一个运算有 num1,operation,num2 三部分组成。在一个逆波兰表达式中,运算符前面两个 num 就是这个运算的组成。 我们要做事情就是,找到一个运算符的时候,同时找到他前面的两个数,而栈的现金先去特性满足这个需求,使用栈来解决。 227.基本计算器 II题目描述: 1234实现一个基本的计算器来计算一个简单的字符串表达式的值。字符串表达式仅包含非负整数,+, - ,\\*,/ 四种运算符和空格 。 整数除法仅保留整数部分。示例:3+5\\*2/2-3 分析: 与逆波兰表达式不同的地方是,这里运算符两边是操作数。但是,这又有什么问题呢?万变不离核心,我们只需要在找到运算符的同时,得到运算符两边的操作数。问题来了,还需要考虑运算符的优先级,想到的一个方法就是,只进行乘除法运算,最后进行加法运算,不进行减法运算(减去一个数 ⟺ 加上这个数的负数) 如果能够把这个字符串表达式相似地转换位逆波兰表达式,那就能直接套用逆波兰表达式的代码了,回顾一下,逆波兰表达式是,每次有操作符的时候,就从栈顶出来两个元素。可以通过使用两个栈来实现,一个栈用来存储操作数,一个栈用来存储操作符。如果比栈顶的操作符符优先级低或者相同,那么就从操作符栈取出栈顶运算符号 496.下一个更大元素 I题目描述: 12345678给定两个 没有重复元素的数组 nums1 和 nums2 ,其中 nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1。示例:输入: nums1 = [4,1,2], nums2 = [1,3,4,2].输出: [-1,3,-1] 分析: 题目要求我们找出 nums1 中每个元素在 nums2 中的下一个比这个元素大的值,又提到 nums1 是 nums2 的一个子集,我们不妨找出 nums2 中每个元素的下一个比他大的值,然后映射到 nums1 中。 那如何找出 nums2 中每个元素的下一个比他大的值呢?对于当前元素,若下一个元素比他大,则找到了,否则的话,就把这个元素添加到栈中,一直到找到一个元素比栈顶元素大,这时候把栈里面所有小于这个元素的元素都出栈,听起来很绕,无妨,看图——> 最后栈中依然有数据存在,这是为什么呢?因为这些元素后面找不到比他更大的值了。观察示例数据,4 后面没有比他更大的值了,5 和 1 也是。我们还能观察到栈中元素是从大到小的,可以称这个栈为==单调递减栈==(如 1019.寻找链表中的下一个更大节点,503.下一个更大元素 II、402.移掉 k 位数字,39.每日温度,在 1673.找出最具有竞争力的子序列中,其实只需要构建一个单调递增栈,然后截取前 k 个。)。 回到题目,需要找到 nums1 中元素在 nums2 中的下一个比其大的值,只需要在刚才保存的信息中进行查找,找不到的则不存在,可以使用哈比表保存每个数对应的下一个较大值。 小结栈由于其随时可以出栈和进栈,产生非常多的组合,带来了非常多的变化,所以读懂题目非常重要,然后选择方法,正所谓题目是有限的,方法是有限的。所以紧跟 lucifer 大佬学习套路,是一条值得坚持的道路,毕竟自古深情留不住,唯有套路得人心,这里推荐 lucifer 大佬的《单调栈模板带你秒杀八道题》,带你乱杀。 树树虽相比于链表来说,至少有两个节点(n 个节点就叫 n 叉树),但是树是一个抽象的概念,可以理解为一个不停做选择的过程,给定一个起始条件,会产生多种结果,而这些结果又成为新的条件,以此类推,直到不再有新的条件。在树种,起始条件就是根节点,不再产生新的条件的就是叶子节点。在树种,使用较多的是二叉树。一颗二叉树不管有多大,我们都可以把他拆分为五种形式, 不管是在树上进行什么操作,都需要进行遍历,遍历的方式有两种:广度优先遍历(BFS)和深度优先遍历(DFS)。简单来说,广度就是先找到有多少种可能,然后找出这些可能有多少种可能;而深度就是每次只根据一个条件来找,直到最终没有条件。话不多说,上图。 如果是试错的话,广度是一次把所有的结果都试一试,深度则是一条路走到黑。 这里直接借用 lucifer 大佬的广度、深度优先遍历模板(套路) 12345678function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } for (const child of root.children) { dfs(child) }} 深度优先遍历根据逻辑处理(==敲黑板,很重要==)的先后分为前序遍历、中序遍历、后序遍历 1234567891011121314151617181920212223242526// 前序遍历function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } // 主要逻辑 dfs(root.left) dfs(root.right)//中序遍历function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) // 主要逻辑 dfs(root.right)// 后序遍历function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) dfs(root.right) // 主要逻辑}} 接下来就要实操了 199. 二叉树的右视图题目描述: 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 示例: 输入: [1,2,3,null,5,null,4] 输出: [1, 3, 4] 解释: 分析: 此题即可以使用广度优先,也可以深度优先。使用广度优先,只需要将每一层的节点用一个数组保存下来,然后输出最后一个使用深度优先,这里我使用的是根右左的方式,这样能保证在每进入到一个新的层时,第一个访问到的就是最右边的元素。 上图: 112. 路径总和题目描述: 12给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。说明: 叶子节点是指没有子节点的节点。 示例: 分析: 求一条路径(根节点到叶子节点),这不就一条路走到底吗,没什么好犹豫的,选择深度优先遍历。因为需要获得路径上的和,我们需要把每个节点的值(状态)传递给下一个节点。 在 113. 路径总和 II 中,和本题类似,只需要把节点加入到数组中传递给下一个节点;在 129. 求根到叶子节点数字之和,需要把当前值*10 传递给下一个节点。 662. 二叉树最大宽度题目描述: 12给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的 null 节点也计入长度)之间的长度。 示例: 分析: 最大宽度,不就是找出哪一层最长吗?广度优先搜索会更加方便,需要注意的是,非两端节点的 null 节点也要算到长度中,所以现在每一层存储的不仅仅是有值节点。 上图 513. 找树左下角的值题目描述: 1给定一个二叉树,在树的最后一行找到最左边的值。 示例: </center>分析:两个关键信息,一个最后一行,一个最左边。好像广度,深度都可以找到,在这里以深度进行说明,最后一行就是depth最大的,所以在深度遍历的时候,每次给一层传递depth信息。 与此题类似的还有 111. 二叉树的最小深度,104.二叉树的最大深度 感觉,就这?好像也没什么难的啊,学完 lucifer 的课程,我就是这么膨胀。 小结无非就是,深度遍历时,是否传递信息给下一层,给下一层传递什么信息;广度遍历时,是否保存每一层,是否保存空节点。 总结本次给大家介绍了四种比较常见的数据结构,分别是数组,链表,栈和树。这四种只有树是逻辑上的非线性数据结构,因为一个节点可能有多个孩子,而其他数据结构只有一个前驱和一个后继。 由于先进后出的特性,我们可以用数组轻松地在 $O(1)$ 时间复杂度模拟栈的操作。但是队列就没那么好命了,我们必须使用链表来优化时间复杂度。 链表的考题相对比较单一,只要记住那几个点就好了。 树的题目比较丰富,和它的非线性数据结构有很大关系。由于其是非线性的,因此有了各种遍历方式,常见的是广度优先和深度优先,很多题目都是灵活运用这两种遍历方式问题就迎刃而解。 关注 lucifer,学习算法不迷路。 参考: 基础的数据结构(总览) 几乎刷完了力扣所有的链表题,我发现了这些东西 几乎刷完了力扣所有的树题,我发现了这些东西 回炉重铸, 91 天见证不一样的自己(第二期)","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"来和大家聊聊我是如何刷题的(第一弹)","slug":"shuati-silu","date":"2020-11-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.897Z","comments":true,"path":"2020/11/29/shuati-silu/","link":"","permalink":"https://lucifer.ren/blog/2020/11/29/shuati-silu/","excerpt":"今天给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第一篇。 话不多说,直接上干货。","text":"今天给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第一篇。 话不多说,直接上干货。 我建议大家 BFS我的做法是集中时间只刷某一类的题目。这样对某一类题目就很有心得,做题就有题感,不会做一道是一道,下次碰到类似的题,甚至原题都不会。其实很多算法都是息息相关的,等你攻克了足够多的专题之后,算法知识才能融会贯通。 我建议大家刷题的时候是广度优先,逐个突破 碰到不会的适当放弃,而不是深度优先,”死磕某一个知识“。比如大家在刷树的专题, 碰到一个树型 DP 不会, 这个时候应该果断放弃,等大家刷到 DP 的时候再回过头捡起来。 听起来简单,但是我从哪个专题开始, 题目那么多我该刷哪个呢? 下面是我的 91 天刷题活动的目录: 可以看出,我们的章节安排就是一个专题一个专题, 从简单到困难。大家也可以参考这个模式。如果你实在不知道。 刷题路线可以从网上找,你如果懒得找,而且也不嫌弃在下的话,可以参考我的 leetcode 题解仓库,把里面的题目刷下,或者参加我的 91 天学算法。 BFS 就是在必要的时候不求甚解。比如,我在 穿上衣服我就不认识你了?来聊聊最长上升子序列 中提取了很多 LIS(最长上升子序列)题目。很多人评论说”这效率不行,不如贪心啊!“。 这点我承认。但是我这里的主要目的是给大家横向对比题目,做到多题同解。 大家想看效率高的,其实也不难。 LIS 也可以用 贪心 + 二分 达到不错的效率。代码如下: 代码文字版如下: 12345678910class Solution: def lengthOfLIS(self, A: List[int]) -> int: d = [] for a in A: i = bisect.bisect_left(d, a) if i < len(d): d[i] = a elif not d or d[-1] < a: d.append(a) return len(d) 所以我的意思是,大家在适当的时候要不求甚解,不去追求这些东西。等大家把一个套路学的差不多,咱再学下一个。所谓君子报仇,十年不晚 ^_^ 另外插一句题外话, LIS 真的很有用,大家一定要掌握,掌握了平方的解法再去看看 $NlogN$ 的解法,一些 HARD 题目必须要 $NlogN$ 才能过。 比如这道题: 题目后的提示如下: 1233 <= nums.length <= 10001 <= nums[i] <= 109题目保证 nums 删除一些元素后一定能得到山形数组。 看到这些,大概估算我们的时间复杂度 $N^2logN$,基本过是没问题的,果然就过了。 再次印证了,刷题的多少是次要的,吃透一类题才是王道,这其实就和我的BFS 刷题大法相呼应。 套路很重要以上的这些,其实都是帮助大家识别套路,提高刷题效率的。知道了广度优先,也知道了刷什么题也是不够的。比如: 这些专题有哪些考点?如何应对? 有模板么? 我如何想到用这种解法? 等等 针对这些问题,我写了很多文章给大家。比如前面一段时间,我给大家写了两篇专题: 几乎刷完了力扣所有的树题,我发现了这些东西 几乎刷完了力扣所有的链表题,我发现了这些东西 大家的反响大部分都是不错的。 在之前, 我还写了几篇解套篇,就是将力扣相同解法的题目汇总起来,帮助大家解套,比如: 穿上衣服我就不认识你了?来聊聊最长上升子序列 你的衣服我扒了 - 《最长公共子序列》 甚至还写了母题系列(不过大家不太喜欢,就没继续更新了): 《我是你的妈妈呀》 - 第一期 你认真看完我写的,基本上覆盖了专题下的大部分考点。 你接下来想看啥? 欢迎去我的刷题群告诉我(关注公众号《力扣加加》回复 leetcode 根据提示操作即可)。 掌握多个编程语言刷题以及打比赛都讲究速度,天下武功唯快不破。 这个快,一方面是运行速度快,另一方面是编码速度快。你可以看出很多人刷题,打比赛都会不断切换语言的。我们要承认不同语言效率是不一样的,这个效率可能是执行,也可能是编码。具体使用哪种语言,看你的需求。 论编码速度,那肯定动态语言快,论执行速度那肯定静态语言快。 所以我的建议是大家至少掌握一静一动,即掌握一个动态语言,一个静态语言。 我个人动态语言用的 Python 和 JS,静态语言用的 Java 和 CPP,大家可以作为参考。 一个小建议是你选择的语言要是题解比较热门的。那什么语言是热门的?其实很容易。力扣题解区,语言排名高的基本就是了,如下图: 掌握语言不仅能帮助你在效率中运用自如,并且还容易看懂别人的题解。除此之外还有一个用,那就是回头复习的时候用。拿我来说, 我会不固定回去刷以前做过的题,但是一道题做过了就没新鲜感了,这个时候我就换个语言继续刷,又是一番滋味。 使用模拟面试这个技巧,我之前提到过。力扣也有模拟面试的功能,大家也可以线下真人白板面试。不管如何,建议大家一定要有时间观念和一次 AC 的标准。 使用模板很多题目都是模板题。你如我在 二分法专题 就给大家总结了无数的模板,其实还有很多专题都有,大家去我的历史文章翻翻就有。 但是大家一定理解之后再去用模板。 不要没理解直接套,这是不好的。 更多技巧,期待下次。 预告最后最后给大家一个小道消息,和上面的解题模板有关。 接下来,力扣加加的刷题插件计划推出刷题模板功能。 给大家提供多种刷题模板,可以直接复制使用。 各个模板都有都有的题目,大家可以直达题目进行”默写“。 更多功能,等你来提~","categories":[{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/categories/刷题方法/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"刷题方法","slug":"刷题方法","permalink":"https://lucifer.ren/blog/tags/刷题方法/"}]},{"title":"几乎刷完了力扣所有的树题,我发现了这些东西。。。","slug":"tree","date":"2020-11-22T16:00:00.000Z","updated":"2023-01-05T12:24:50.071Z","comments":true,"path":"2020/11/23/tree/","link":"","permalink":"https://lucifer.ren/blog/2020/11/23/tree/","excerpt":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。(就是本文)","text":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 本系列包含以下专题: 几乎刷完了力扣所有的链表题,我发现了这些东西。。。 几乎刷完了力扣所有的树题,我发现了这些东西。。。(就是本文) 一点絮叨首先亮一下本文的主角 - 树(我的化妆技术还行吧^_^): 树标签在 leetcode 一共有 175 道题。 为了准备这个专题,我花了几天时间将 leetcode 几乎所有的树题目都刷了一遍。 除了 35 个上锁的,1 个不能做的题(1628 题不知道为啥做不了), 4 个标着树的标签但却是图的题目,其他我都刷了一遍。通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。 食用指南大家好,我是 lucifer。今天给大家带来的是《树》专题。另外为了保持章节的聚焦性和实用性,省去了一些内容,比如哈夫曼树,前缀树,平衡二叉树(红黑树等),二叉堆。这些内容相对来说实用性没有那么强,如果大家对这些内容也感兴趣,可以关注下我的仓库 leetcode 算法题解,大家有想看的内容也可以留言告诉我哦~ 另外要提前告知大家的是本文所讲的很多内容都很依赖于递归。关于递归的练习我推荐大家把递归过程画到纸上,手动代入几次。等大脑熟悉了递归之后就不用这么辛苦了。 实在懒得画图的同学也可以找一个可视化递归的网站,比如 https://recursion.now.sh/。 等你对递归有了一定的理解之后就仔细研究一下树的各种遍历方法,再把本文看完,最后把文章末尾的题目做一做,搞定个递归问题不大。 文章的后面《两个基本点 - 深度优先遍历》部分,对于如何练习树的遍历的递归思维我也提出了一种方法 最后要强调的是,本文只是帮助你搞定树题目的常见套路,但不是说树的所有题目涉及的考点都讲。比如树状 DP 这种不在本文的讨论范围,因为这种题更侧重的是 DP,如果你不懂 DP 多半是做不出来的,你需要的是学完树和 DP 之后再去学树状 DP。如果你对这些内容感兴趣,可以期待我的后续专题。 前言提到树大家更熟悉的是现实中的树,而现实中的树是这样的: 而计算机中的树其实是现实中的树的倒影。 计算机的数据结构是对现实世界物体间关系的一种抽象。比如家族的族谱,公司架构中的人员组织关系,电脑中的文件夹结构,html 渲染的 dom 结构等等,这些有层次关系的结构在计算机领域都叫做树。 首先明确一下,树其实是一种逻辑结构。比如笔者平时写复杂递归的时候,尽管笔者做的题目不是树,也会画一个递归树帮助自己理解。 树是一种重要的思维工具 以最简单的计算 fibonacci 数列为例: 12345function fn(n) { if (n == 0 || n == 1) return n; return fn(n - 1) + fn(n - 2);} 很明显它的入参和返回值都不是树,但是却不影响我们用树的思维去思考。 继续回到上面的代码,根据上面的代码可以画出如下的递归树。 其中树的边表示的是返回值,树节点表示的是需要计算的值,即 fn(n)。 以计算 5 的 fibbonacci 为例,过程大概是这样的(动图演示): 这其实就是一个树的后序遍历,你说树(逻辑上的树)是不是很重要?关于后序遍历咱们后面再讲,现在大家知道是这么回事就行。 大家也可以去 这个网站 查看上面算法的单步执行效果。当然这个网站还有更多的算法的动画演示。 上面的图箭头方向是为了方便大家理解。其实箭头方向变成向下的才是真的树结构。 广义的树真的很有用,但是它范围太大了。 本文所讲的树的题目是比较狭隘的树,指的是输入(参数)或者输出(返回值)是树结构的题目。 基本概念 树的基本概念难度都不大,为了节省篇幅,我这里简单过一下。对于你不熟悉的点,大家自行去查找一下相关资料。我相信大家也不是来看这些的,大家应该想看一些不一样的东西,比如说一些做题的套路。 树是一种非线性数据结构。树结构的基本单位是节点。节点之间的链接,称为分支(branch)。节点与分支形成树状,结构的开端,称为根(root),或根结点。根节点之外的节点,称为子节点(child)。没有链接到其他子节点的节点,称为叶节点(leaf)。如下图是一个典型的树结构: 每个节点可以用以下数据结构来表示: 1234Node { value: any; // 当前节点的值 children: Array<Node>; // 指向其儿子} 其他重要概念: 树的高度:节点到叶子节点的最大值就是其高度。 树的深度:高度和深度是相反的,高度是从下往上数,深度是从上往下。因此根节点的深度和叶子节点的高度是 0。 树的层:根开始定义,根为第一层,根的孩子为第二层。 二叉树,三叉树,。。。 N 叉树,由其子节点最多可以有几个决定,最多有 N 个就是 N 叉树。 二叉树二叉树是树结构的一种,两个叉就是说每个节点最多只有两个子节点,我们习惯称之为左节点和右节点。 注意这个只是名字而已,并不是实际位置上的左右 二叉树也是我们做算法题最常见的一种树,因此我们花大篇幅介绍它,大家也要花大量时间重点掌握。 二叉树可以用以下数据结构表示: 12345Node { value: any; // 当前节点的值 left: Node | null; // 左儿子 right: Node | null; // 右儿子} 二叉树分类 完全二叉树 满二叉树 二叉搜索树 平衡二叉树 红黑树 。。。 二叉树的表示 链表存储 数组存储。非常适合完全二叉树 树题难度几何?很多人觉得树是一个很难的专题。实际上,只要你掌握了诀窍,它并没那么难。 从官方的难度标签来看,树的题目处于困难难度的一共是 14 道, 这其中还有 1 个标着树的标签但是却是图的题目,因此困难率是 13 / 175 ,也就是 7.4 % 左右。如果排除上锁的 5 道,困难的只有 9 道。大多数困难题,相信你看完本节的内容,也可以做出来。 从通过率来看,只有不到三分之一的题目平均通过率在 50% 以下,其他(绝大多数的题目)通过率都是 50%以上。50% 是一个什么概念呢?这其实很高了。举个例子来说, BFS 的平均通过率差不多在 50%。 而大家认为比较难的二分法和动态规划的平均通过率差不多 40%。 大家不要对树有压力, 树和链表一样是相对容易的专题,今天 lucifer 给大家带来了一个口诀一个中心,两个基本点,三种题型,四个重要概念,七个技巧,帮助你克服树这个难关。 一个中心一个中心指的是树的遍历。整个树的专题只有一个中心点,那就是树的遍历,大家务必牢牢记住。 不管是什么题目,核心就是树的遍历,这是一切的基础,不会树的遍历后面讲的都是白搭。 其实树的遍历的本质就是去把树里边儿的每个元素都访问一遍(任何数据结构的遍历不都是如此么?)。但怎么访问的?我不能直接访问叶子节点啊,我必须得从根节点开始访问,然后根据子节点指针访问子节点,但是子节点有多个(二叉树最多两个)方向,所以又有了先访问哪个的问题,这造成了不同的遍历方式。 左右子节点的访问顺序通常不重要,极个别情况下会有一些微妙区别。比如说我们想要访问一棵树的最左下角节点,那么顺序就会产生影响,但这种题目会比较少一点。 而遍历不是目的,遍历是为了更好地做处理,这里的处理包括搜索,修改树等。树虽然只能从根开始访问,但是我们可以选择在访问完毕回来的时候做处理,还是在访问回来之前做处理,这两种不同的方式就是后序遍历和先序遍历。 关于具体的遍历,后面会给大家详细讲,现在只要知道这些遍历是怎么来的就行了。 而树的遍历又可以分为两个基本类型,分别是深度优先遍历和广度优先遍历。这两种遍历方式并不是树特有的,但却伴随树的所有题目。值得注意的是,这两种遍历方式只是一种逻辑而已,因此理论可以应用于任何数据结构,比如 365. 水壶问题 中,就可以对水壶的状态使用广度优先遍历,而水壶的状态可以用一个二元组来表示。 遗憾的是这道题的广度优先遍历解法在 LeetCode 上提交会超时 树的遍历迭代写法很多小朋友表示二叉树前中后序的递归写法没问题,但是迭代就写不出来,问我有什么好的方法没有。 这里就给大家介绍一种写迭代遍历树的实操技巧,统一三种树的遍历方式,包你不会错,这个方法叫做双色标记法。 如果你会了这个技巧,那么你平时练习大可只用递归。然后面试的时候,真的要求用迭代或者是对性能有特别要求的那种题目,那你就用我的方法套就行了,下面我来详细讲一下这种方法。 我们知道垃圾回收算法中,有一种算法叫三色标记法。 即: 用白色表示尚未访问 灰色表示尚未完全访问子节点 黑色表示子节点全部访问 那么我们可以模仿其思想,使用双色标记法来统一三种遍历。 其核心思想如下: 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。 如果遇到的节点为灰色,则将节点的值输出。 使用这种方法实现的中序遍历如下: 123456789101112131415class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: WHITE, GRAY = 0, 1 res = [] stack = [(WHITE, root)] while stack: color, node = stack.pop() if node is None: continue if color == WHITE: stack.append((WHITE, node.right)) stack.append((GRAY, node)) stack.append((WHITE, node.left)) else: res.append(node.val) return res 可以看出,实现上 WHITE 就表示的是递归中的第一次进入过程,Gray 则表示递归中的从叶子节点返回的过程。 因此这种迭代的写法更接近递归写法的本质。 如要实现前序、后序遍历,也只需要调整左右子节点的入栈顺序即可,其他部分是无需做任何变化。 (前中后序遍历只需要调整这三句话的位置即可) 注:这张示意图的前序和后序画反了 可以看出使用三色标记法,其写法类似递归的形式,因此便于记忆和书写。 有的同学可能会说,这里的每一个节点都会入栈出栈两次,相比普通的迭代入栈和出栈次数整整加了一倍,这性能可以接受么?我要说的是这种时间和空间的增加仅仅是常数项的增加,大多数情况并不会都程序造成太大的影响。 除了有时候比赛会比较恶心人,会卡常(卡常是指通过计算机原理相关的、与理论复杂度无关的方法对代码运行速度进行优化)。反过来看,大家写的代码大多数是递归,要知道递归由于内存栈的开销,性能通常比这里的二色标记法更差才对, 那为啥不用一次入栈的迭代呢?更极端一点,为啥大家不都用 morris 遍历 呢? morris 遍历 是可以在常数的空间复杂度完成树的遍历的一种算法。 我认为在大多数情况下,大家对这种细小的差异可以不用太关注。另外如果这种遍历方式完全掌握了,再根据递归的思想去写一次入栈的迭代也不是难事。 无非就是调用函数的时候入栈,函数 return 时候出栈罢了。更多二叉树遍历的内容,大家也可以访问我之前写的专题《二叉树的遍历》。 小结简单总结一下,树的题目一个中心就是树的遍历。树的遍历分为两种,分别是深度优先遍历和广度优先遍历。关于树的不同深度优先遍历(前序,中序和后序遍历)的迭代写法是大多数人容易犯错的地方,因此我介绍了一种统一三种遍历的方法 - 二色标记法,这样大家以后写迭代的树的前中后序遍历就再也不用怕了。如果大家彻底熟悉了这种写法,再去记忆和练习一次入栈甚至是 Morris 遍历即可。 其实用一次入栈和出栈的迭代实现递归也很简单,无非就是还是用递归思想,只不过你把递归体放到循环里边而已。大家可以在熟悉递归之后再回头看看就容易理解了。树的深度遍历的递归技巧,我们会在后面的《两个基本点》部分讲解。 两个基本点上面提到了树的遍历有两种基本方式,分别是深度优先遍历(以下简称 DFS)和广度优先遍历(以下简称 BFS),这就是两个基本点。这两种遍历方式下面又会细分几种方式。比如 DFS 细分为前中后序遍历, BFS 细分为带层的和不带层的。 DFS 适合做一些暴力枚举的题目,DFS 如果借助函数调用栈,则可以轻松地使用递归来实现。 BFS 不是 层次遍历而 BFS 适合求最短距离,这个和层次遍历是不一样的,很多人搞混。这里强调一下,层次遍历和 BFS 是完全不一样的东西。 层次遍历就是一层层遍历树,按照树的层次顺序进行访问。 (层次遍历图示) BFS 的核心在于求最短问题时候可以提前终止,这才是它的核心价值,层次遍历是一种不需要提前终止的 BFS 的副产物。这个提前终止不同于 DFS 的剪枝的提前终止,而是找到最近目标的提前终止。比如我要找距离最近的目标节点,BFS 找到目标节点就可以直接返回。而 DFS 要穷举所有可能才能找到最近的,这才是 BFS 的核心价值。实际上,我们也可以使用 DFS 实现层次遍历的效果,借助于递归,代码甚至会更简单。 如果找到任意一个满足条件的节点就好了,不必最近的,那么 DFS 和 BFS 没有太大差别。同时为了书写简单,我通常会选择 DFS。 以上就是两种遍历方式的简单介绍,下面我们对两者进行一个详细的讲解。 深度优先遍历深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止,属于盲目搜索。 深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。因发明「深度优先搜索算法」,约翰 · 霍普克洛夫特与罗伯特 · 塔扬在 1986 年共同获得计算机领域的最高奖:图灵奖。 截止目前(2020-02-21),深度优先遍历在 LeetCode 中的题目是 129 道。在 LeetCode 中的题型绝对是超级大户了。而对于树的题目,我们基本上都可以使用 DFS 来解决,甚至我们可以基于 DFS 来做层次遍历,而且由于 DFS 可以基于递归去做,因此算法会更简洁。 在对性能有很高要求的场合,我建议你使用迭代,否则尽量使用递归,不仅写起来简单快速,还不容易出错。 DFS 图解: (图片来自 https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) 算法流程 首先将根节点放入stack中。 从stack中取出第一个节点,并检验它是否为目标。如果找到所有的节点,则结束搜寻并回传结果。否则将它某一个尚未检验过的直接子节点加入stack中。 重复步骤 2。 如果不存在未检测过的直接子节点。将上一级节点加入stack中。重复步骤 2。 重复步骤 4。 若stack为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。 这里的 stack 可以理解为自己实现的栈,也可以理解为调用栈。如果是调用栈的时候就是递归,如果是自己实现的栈的话就是迭代。 算法模板一个典型的通用的 DFS 模板可能是这样的: 12345678910111213const visited = {}function dfs(i) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } visited[i] = true // 将当前状态标为已搜索 for (根据i能到达的下个状态j) { if (!visited[j]) { // 如果状态j没有被搜索过 dfs(j) } }} 上面的 visited 是为了防止由于环的存在造成的死循环的。 而我们知道树是不存在环的,因此树的题目大多数不需要 visited,除非你对树的结构做了修改,比如就左子树的 left 指针指向自身,此时会有环。再比如 138. 复制带随机指针的链表 这道题需要记录已经复制的节点,这些需要记录 visited 信息的树的题目少之又少。 因此一个树的 DFS 更多是: 123456789function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } for (const child of root.children) { dfs(child) }} 而几乎所有的题目几乎都是二叉树,因此下面这个模板更常见。 1234567function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) dfs(root.right)} 而我们不同的题目除了 if (满足特定条件部分不同之外),还会写一些特有的逻辑,这些逻辑写的位置不同,效果也截然不同。那么位置不同会有什么影响,什么时候应该写哪里呢?接下来,我们就聊聊两种常见的 DFS 方式。 两种常见分类前序遍历和后序遍历是最常见的两种 DFS 方式。而另外一种遍历方式 (中序遍历)一般用于平衡二叉树,这个我们后面的四个重要概念部分再讲。 前序遍历如果你的代码大概是这么写的(注意主要逻辑的位置): 12345678function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } // 主要逻辑 dfs(root.left) dfs(root.right)} 那么此时我们称为前序遍历。 后续遍历而如果你的代码大概是这么写的(注意主要逻辑的位置): 12345678function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } dfs(root.left) dfs(root.right) // 主要逻辑} 那么此时我们称为后序遍历。 值得注意的是, 我们有时也会会写出这样的代码: 123456789function dfs(root) { if (满足特定条件){ // 返回结果 or 退出搜索空间 } // 做一些事 dfs(root.left) dfs(root.right) // 做另外的事} 如上代码,我们在进入和退出左右子树的时候分别执行了一些代码。那么这个时候,是前序遍历还是后续遍历呢?实际上,这属于混合遍历了。不过我们这里只考虑主逻辑的位置,关键词是主逻辑。 如果代码主逻辑在左右子树之前执行,那么就是前序遍历。如果代码主逻辑在左右子树之后执行,那么就是后序遍历。关于更详细的内容, 我会在七个技巧 中的前后遍历部分讲解,大家先留个印象,知道有着两种方式就好。 递归遍历的学习技巧上面的《一个中心》部分,给大家介绍了一种干货技巧《双色遍历》统一三种遍历的迭代写法。 而树的遍历的递归的写法其实大多数人都没问题。为什么递归写的没问题,用栈写迭代就有问题呢? 本质上其实还是对递归的理解不够。那 lucifer 今天给大家介绍一种练习递归的技巧。其实文章开头也提到了,那就是画图 + 手动代入。有的同学不知道怎么画,这里我抛砖引玉分享一下我学习递归的画法。 比如我们要前序遍历一棵这样的树: 12345 1 / \\2 3 / \\ 4 5 图画的还算比较清楚, 就不多解释了。大家遇到题目多画几次这样的递归图,慢慢就对递归有感觉了。 广度优先遍历树的遍历的两种方式分别是 DFS 和 BFS,刚才的 DFS 我们简单过了一下前序和后序遍历,对它们有了一个简单印象。这一小节,我们来看下树的另外一种遍历方式 - BFS。 BFS 也是图论中算法的一种,不同于 DFS, BFS 采用横向搜索的方式,在数据结构上通常采用队列结构。 注意,DFS 我们借助的是栈来完成,而这里借助的是队列。 BFS 比较适合找最短距离/路径和某一个距离的目标。比如给定一个二叉树,在树的最后一行找到最左边的值。,此题是力扣 513 的原题。这不就是求距离根节点最远距离的目标么? 一个 BFS 模板就解决了。 BFS 图解: (图片来自 https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) 算法流程 首先将根节点放入队列中。 从队列中取出第一个节点,并检验它是否为目标。 如果找到目标,则结束搜索并回传结果。 否则将它所有尚未检验过的直接子节点加入队列中。 若队列为空,表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传“找不到目标”。 重复步骤 2。 算法模板12345678910111213141516const visited = {}function bfs() { let q = new Queue() q.push(初始状态) while(q.length) { let i = q.pop() if (visited[i]) continue if (i 是我们要找的目标) return 结果 for (i的可抵达状态j) { if (j 合法) { q.push(j) } } } return 没找到} 两种常见分类BFS 我目前使用的模板就两种,这两个模板可以解决所有的树的 BFS 问题。 前面我提到了“BFS 比较适合找最短距离/路径和某一个距离的目标”。 如果我需要求的是最短距离/路径,我是不关心我走到第几步的,这个时候可是用不标记层的目标。而如果我需要求距离某个节点距离等于 k 的所有节点,这个时候第几步这个信息就值得被记录了。 小于 k 或者 大于 k 也是同理。 标记层一个常见的 BFS 模板,代入题目只需要根据题目微调即可。 12345678910111213141516171819202122class Solution: def bfs(k): # 使用双端队列,而不是数组。因为数组从头部删除元素的时间复杂度为 N,双端队列的底层实现其实是链表。 queue = collections.deque([root]) # 记录层数 steps = 0 # 需要返回的节点 ans = [] # 队列不空,生命不止! while queue: size = len(queue) # 遍历当前层的所有节点 for _ in range(size): node = queue.popleft() if (step == k) ans.append(node) if node.right: queue.append(node.right) if node.left: queue.append(node.left) # 遍历完当前层所有的节点后 steps + 1 steps += 1 return ans 不标记层不带层的模板更简单,因此大家其实只需要掌握带层信息的目标就够了。 一个常见的 BFS 模板,代入题目只需要根据题目微调即可。 1234567891011121314class Solution: def bfs(k): # 使用双端队列,而不是数组。因为数组从头部删除元素的时间复杂度为 N,双端队列的底层实现其实是链表。 queue = collections.deque([root]) # 队列不空,生命不止! while queue: node = queue.popleft() # 由于没有记录 steps,因此我们肯定是不需要根据层的信息去判断的。否则就用带层的模板了。 if (node 是我们要找到的) return node if node.right: queue.append(node.right) if node.left: queue.append(node.left) return -1 以上就是 BFS 的两种基本方式,即带层和不带层,具体使用哪种看题目是否需要根据层信息做判断即可。 小结树的遍历是后面所有内容的基础,而树的遍历的两种方式 DFS 和 BFS 到这里就简单告一段落,现在大家只要知道 DFS 和 BFS 分别有两种常见的方式就够了,后面我会给大家详细补充。 三种题型树的题目就三种类型,分别是:搜索类,构建类和修改类,而这三类题型的比例也是逐渐降低的,即搜索类的题目最多,其次是构建类,最后是修改类。这一点和链表有很大的不同,链表更多的是修改类。 接下来,lucifer 给大家逐一讲解这三种题型。 搜索类搜索类的题目是树的题目的绝对大头。而搜索类只有两种解法,那就是 DFS 和 BFS,下面分别介绍。 几乎所有的搜索类题目都可以方便地使用递归来实现,关于递归的技巧会在七个技巧中的单/双递归部分讲解。还有一小部分使用递归不好实现,我们可以使用 BFS,借助队列轻松实现,比如最经典的是求二叉树任意两点的距离,树的距离其实就是最短距离,因此可以用 BFS 模板解决。这也是为啥我说DFS 和 BFS是树的题目的两个基本点的原因。 所有搜索类的题目只要把握三个核心点,即开始点,结束点 和 目标即可。 DFS 搜索DFS 搜索类的基本套路就是从入口开始做 dfs,然后在 dfs 内部判断是否是结束点,这个结束点通常是叶子节点或空节点,关于结束这个话题我们放在七个技巧中的边界部分介绍,如果目标是一个基本值(比如数字)直接返回或者使用一个全局变量记录即可,如果是一个数组,则可以通过扩展参数的技巧来完成,关于扩展参数,会在七个技巧中的参数扩展部分介绍。 这基本就是搜索问题的全部了,当你读完后面的七个技巧,回头再回来看这个会更清晰。 套路模板: 123456789101112131415161718192021222324252627# 其中 path 是树的路径, 如果需要就带上,不需要就不带def dfs(root, path): # 空节点 if not root: return # 叶子节点 if not root.left and not root.right: return path.append(root) # 逻辑可以写这里,此时是前序遍历 dfs(root.left) dfs(root.right) # 需要弹出,不然会错误计算。 # 比如对于如下树: \"\"\" 5 / \\ 4 8 / / \\ 11 13 4 / \\ / \\ 7 2 5 1 \"\"\" # 如果不 pop,那么 5 -> 4 -> 11 -> 2 这条路径会变成 5 -> 4 -> 11 -> 7 -> 2,其 7 被错误地添加到了 path path.pop() # 逻辑也可以写这里,此时是后序遍历 return 你想返回的数据 比如剑指 Offer 34. 二叉树中和为某一值的路径 这道题,题目是:输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。 这不就是从根节点开始,到叶子节点结束的所有路径搜索出来,挑选出和为目标值的路径么?这里的开始点是根节点, 结束点是叶子节点,目标就是路径。 对于求这种满足特定和的题目,我们都可以方便地使用前序遍历 + 参数扩展的形式,关于这个,我会在七个技巧中的前后序部分展开。 由于需要找到所有的路径,而不仅仅是一条,因此这里适合使用回溯暴力枚举。关于回溯,可以参考我的 回溯专题 12345678910111213141516171819202122class Solution: def pathSum(self, root: TreeNode, target: int) -> List[List[int]]: def backtrack(nodes, path, cur, remain): # 空节点 if not cur: return # 叶子节点 if cur and not cur.left and not cur.right: if remain == cur.val: nodes.append((path + [cur.val]).copy()) return # 选择 path.append(cur.val) # 递归左右子树 backtrack(nodes, path, cur.left, remain - cur.val) backtrack(nodes, path, cur.right, remain - cur.val) # 撤销选择 path.pop(-1) ans = [] # 入口,路径,目标值全部传进去,其中路径和path都是扩展的参数 backtrack(ans, [], root, target) return ans 再比如:1372. 二叉树中的最长交错路径,题目描述: 1234567891011给你一棵以 root 为根的二叉树,二叉树中的交错路径定义如下:选择二叉树中 任意 节点和一个方向(左或者右)。如果前进方向为右,那么移动到当前节点的的右子节点,否则移动到它的左子节点。改变前进方向:左变右或者右变左。重复第二步和第三步,直到你在树中无法继续移动。交错路径的长度定义为:访问过的节点数目 - 1(单个节点的路径长度为 0 )。请你返回给定树中最长 交错路径 的长度。比如: 12此时需要返回 3解释:蓝色节点为树中最长交错路径(右 -> 左 -> 右)。 这不就是从任意节点开始,到任意节点结束的所有交错路径全部搜索出来,挑选出最长的么?这里的开始点是树中的任意节点,结束点也是任意节点,目标就是最长的交错路径。 对于入口是任意节点的题目,我们都可以方便地使用双递归来完成,关于这个,我会在七个技巧中的单/双递归部分展开。 对于这种交错类的题目,一个好用的技巧是使用 -1 和 1 来记录方向,这样我们就可以通过乘以 -1 得到另外一个方向。 886. 可能的二分法 和 785. 判断二分图 都用了这个技巧。 用代码表示就是: 1next_direction = cur_direction * - 1 这里我们使用双递归即可解决。 如果题目限定了只从根节点开始,那就可以用单递归解决了。值得注意的是,这里内部递归需要 cache 一下 , 不然容易因为重复计算导致超时。 我的代码是 Python,这里的 lru_cache 就是一个缓存,大家可以使用自己语言的字典模拟实现。 12345678910111213class Solution: @lru_cache(None) def dfs(self, root, dir): if not root: return 0 if dir == -1: return int(root.left != None) + self.dfs(root.left, dir * -1) return int(root.right != None) + self.dfs(root.right, dir * -1) def longestZigZag(self, root: TreeNode) -> int: if not root: return 0 return max(self.dfs(root, 1), self.dfs(root, -1), self.longestZigZag(root.left), self.longestZigZag(root.right)) 这个代码不懂没关系,大家只有知道搜索类题目的大方向即可,具体做法我们后面会介绍,大家留个印象就行。更多的题目以及这些技巧的详细使用方式放在七个技巧部分展开。 BFS 搜索这种类型相比 DFS,题目数量明显降低,套路也少很多。题目大多是求距离,套用我上面的两种 BFS 模板基本都可以轻松解决,这个不多介绍了。 构建类除了搜索类,另外一个大头是构建类。构建类又分为两种:普通二叉树的构建和二叉搜索树的构建。 普通二叉树的构建而普通二叉树的构建又分为三种: 给你两种 DFS 的遍历的结果数组,让你构建出原始的树结构。比如根据先序遍历和后序遍历的数组,构造原始二叉树。这种题我在构造二叉树系列 系列里讲的很清楚了,大家可以去看看。 这种题目假设输入的遍历的序列中都不含重复的数字,想想这是为什么。 给你一个 BFS 的遍历的结果数组,让你构建出原始的树结构。 最经典的就是 剑指 Offer 37. 序列化二叉树。我们知道力扣的所有的树表示都是使用数字来表示的,而这个数组就是一棵树的层次遍历结果,部分叶子节点的子节点(空节点)也会被打印。比如:[1,2,3,null,null,4,5],就表示的是如下的一颗二叉树: 我们是如何根据这样的一个层次遍历结果构造出原始二叉树的呢?这其实就属于构造二叉树的内容,这个类型目前力扣就这一道题。这道题如果你彻底理解 BFS,那么就难不倒你。 还有一种是给你描述一种场景,让你构造一个符合条件的二叉树。这种题和上面的没啥区别,套路简直不要太像,比如 654. 最大二叉树,我就不多说了,大家通过这道题练习一下就知道了。 除了这种静态构建,还有一种很很罕见的动态构建二叉树的,比如 894. 所有可能的满二叉树 ,对于这个题,直接 BFS 就好了。由于这种题很少,因此不做多的介绍。大家只要把最核心的掌握了,这种东西自然水到渠成。 二叉搜索树的构建普通二叉树无法根据一种序列重构的原因是只知道根节点,无法区分左右子树。如果是二叉搜索树,那么就有可能根据一种遍历序列构造出来。 原因就在于二叉搜索树的根节点的值大于所有的左子树的值,且小于所有的右子树的值。因此我们可以根据这一特性去确定左右子树的位置,经过这样的转换就和上面的普通二叉树没有啥区别了。比如 1008. 前序遍历构造二叉搜索树 修改类上面介绍了两种常见的题型:搜索类和构建类。还有一种比例相对比较小的题目类型是修改类。 当然修改类的题目也是要基于搜索算法的,不找到目标怎么删呢? 修改类的题目有两种基本类型。 题目要求的修改一种是题目让你增加,删除节点,或者是修改节点的值或者指向。 修改指针的题目一般不难,比如 116. 填充每个节点的下一个右侧节点指针,这不就是 BFS 的时候顺便记录一下上一次访问的同层节点,然后增加一个指针不就行了么?关于 BFS ,套用我的带层的 BFS 模板就搞定了。 增加和删除的题目一般稍微复杂,比如 450. 删除二叉搜索树中的节点 和 669. 修剪二叉搜索树。西法我教你两个套路,面对这种问题就不带怕的。那就是后续遍历 + 虚拟节点,这两个技巧同样放在后面的七个技巧部分讲解。是不是对七个技巧很期待?^_^ 实际工程中,我们也可以不删除节点,而是给节点做一个标记,表示已经被删除了,这叫做软删除。 算法需要,自己修改另外一种是为了方便计算,自己加了一个指针。 比如 863. 二叉树中所有距离为 K 的结点 通过修改树的节点类,增加一个指向父节点的引用 parent,问题就转化为距离目标节点一定距离的问题了,此时可是用我上面讲的带层的 BFS 模板解决。 动态语言可以直接加属性(比如上面的 parent),而静态语言是不允许的,因此你需要增加一个新的类定义。不过你也可以使用字典来实现, key 是 node 引用, value 是你想记录的东西,比如这里的 parent 节点。 比如对于 Java 来说,我们可以: 12345678910class Solution { Map<TreeNode, TreeNode> parent; public void dfs(TreeNode node, TreeNode parent) { if (node != null) { parent.put(node, parent); dfs(node.left, node); dfs(node.right, node); } }} 简单回顾一下这一小节的知识。 接下来是做树的题目不得不知的四个重要概念。 四个重要概念二叉搜索树二叉搜索树(Binary Search Tree),亦称二叉查找树。 二叉搜索树具有下列性质的二叉树: 若左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若右子树不空,则右子树上所有节点的值均大于它的根节点的值; 左、右子树也分别为二叉排序树; 没有键值相等的节点。 对于一个二叉查找树,常规操作有插入,查找,删除,找父节点,求最大值,求最小值。 天生适合查找二叉查找树,之所以叫查找树就是因为其非常适合查找。 举个例子,如下一颗二叉查找树,我们想找节点值小于且最接近 58 的节点,搜索的流程如图所示: (图片来自 https://www.geeksforgeeks.org/floor-in-binary-search-tree-bst/) 可以看出每次向下走,都会排除了一个分支,如果一颗二叉搜索树同时也是一颗二叉平衡树的话,那么其搜索过程时间复杂度就是 $O(logN)$。实际上,平衡二叉搜索树的查找和有序数组的二分查找本质都是一样的,只是数据的存储方式不同罢了。那为什么有了有序数组二分,还需要二叉搜索树呢?原因在于树的结构对于动态数据比较友好,比如数据是频繁变动的,比如经常添加和删除,那么就可以使用二叉搜索树。理论上添加和删除的时间复杂度都是 $O(h)$,其中 h 为树的高度,如果是一颗平衡二叉搜索树,那么时间复杂度就是 $O(logN)$。而数组的添加和删除的时间复杂度为 $O(N)$,其中 N 为数组长度。 方便搜索,是二叉搜索树核心的设计初衷。不让查找算法时间复杂度退化到线性是平衡二叉树的初衷。 我们平时说的二分很多是数组的二分,因为数组可以随机访问嘛。不过这种二分实在太狭义了,二分的本质是将问题规模缩小到一半,因此二分和数据结构没有本质关系,但是不同的数据结构却给二分赋予了不同的色彩。比如跳表就是链表的二分,二叉搜索树就是树的二分等。随着大家对算法和数据结构的了解的加深,会发现更多有意思的东西^_^ 中序遍历是有序的另外二叉查找树有一个性质,这个性质对于做题很多帮助,那就是: 二叉搜索树的中序遍历的结果是一个有序数组。 比如 98. 验证二叉搜索树 就可以直接中序遍历,并一边遍历一边判断遍历结果是否是单调递增的,如果不是则提前返回 False 即可。 再比如 99. 恢复二叉搜索树,官方难度为困难。题目大意是给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。 我们可以先中序遍历发现不是递增的节点,他们就是被错误交换的节点,然后交换恢复即可。这道题难点就在于一点,即错误交换可能错误交换了中序遍历的相邻节点或者中序遍历的非相邻节点,这是两种 case,需要分别讨论。 类似的题目很多,不再赘述。练习的话大家可以做一下这几道题。 94. 二叉树的中序遍历 98. 验证二叉搜索树 173. 二叉搜索树迭代器 250. 统计同值子树 大家如果碰到二叉搜索树的搜索类题目,一定先想下能不能利用这个性质来做。 完全二叉树一棵深度为 k 的有 n 个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为 i(1≤i≤n)的结点与满二叉树中编号为 i 的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。 如下就是一颗完全二叉树: 直接考察完全二叉树的题目虽然不多,貌似只有一道 222. 完全二叉树的节点个数(二分可解),但是理解完全二叉树对你做题其实帮助很大。 如上图,是一颗普通的二叉树。如果我将其中的空节点补充完全,那么它就是一颗完全二叉树了。 这有什么用呢?这很有用!我总结了两个用处: 我们可以给完全二叉树编号,这样父子之间就可以通过编号轻松求出。比如我给所有节点从左到右从上到下依次从 1 开始编号。那么已知一个节点的编号是 i,那么其左子节点就是 2 _ i,右子节点就是 2 _ 1 + 1,父节点就是 (i + 1) / 2。 熟悉二叉堆的同学可能发现了,这就是用数组实现的二叉堆,其实二叉堆就是完全二叉树的一个应用。 有的同学会说,”但是很多题目都不是完全二叉树呀,那不是用不上了么?“其实不然,我们只要想象它存在即可,我们将空节点脑补上去不就可以了?比如 662. 二叉树最大宽度。题目描述: 12345678910111213141516给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。示例 1:输入: 1 / \\ 3 2 / \\ \\ 5 3 9输出: 4解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。 很简单,一个带层的 BFS 模板即可搞定,简直就是默写题。不过这里需要注意两点: 入队的时候除了要将普通节点入队,还要空节点入队。 出队的时候除了入队节点本身,还要将节点的位置信息入队,即下方代码的 pos。 参考代码: 12345678910111213141516171819202122232425262728# Definition for a binary tree node.# class TreeNode:# def __init__(self, x):# self.val = x# self.left = None# self.right = Noneclass Solution: def widthOfBinaryTree(self, root: TreeNode) -> int: q = collections.deque([(root, 0)]) steps = 0 cur_depth = leftmost = ans = 0 while q: for _ in range(len(q)): node, pos = q.popleft() if node: # 节点编号关关系是不是用上了? q.append((node.left, pos * 2)) q.append((node.right, pos * 2 + 1)) # 逻辑开始 if cur_depth != steps: cur_depth = steps leftmost = pos ans = max(ans, pos - leftmost + 1) # 逻辑结束 steps += 1 return ans 再比如剑指 Offer 37. 序列化二叉树。如果我将一个二叉树的完全二叉树形式序列化,然后通过 BFS 反序列化,这不就是力扣官方序列化树的方式么?比如: 12345 1 / \\2 3 / \\ 4 5 序列化为 “[1,2,3,null,null,4,5]”。 这不就是我刚刚画的完全二叉树么?就是将一个普通的二叉树硬生生当成完全二叉树用了。 其实这并不是序列化成了完全二叉树,下面会纠正。 将一颗普通树序列化为完全二叉树很简单,只要将空节点当成普通节点入队处理即可。代码: 12345678910111213141516class Codec: def serialize(self, root): q = collections.deque([root]) ans = '' while q: cur = q.popleft() if cur: ans += str(cur.val) + ',' q.append(cur.left) q.append(cur.right) else: # 除了这里不一样,其他和普通的不记录层的 BFS 没区别 ans += 'null,' # 末尾会多一个逗号,我们去掉它。 return ans[:-1] 细心的同学可能会发现,我上面的代码其实并不是将树序列化成了完全二叉树,这个我们稍后就会讲到。另外后面多余的空节点也一并序列化了。这其实是可以优化的,优化的方式也很简单,那就是去除末尾的 null 即可。 你只要彻底理解我刚才讲的我们可以给完全二叉树编号,这样父子之间就可以通过编号轻松求出。比如我给所有节点从左到右从上到下依次从 1 开始编号。那么已知一个节点的编号是 i,那么其左子节点就是 2 * i,右子节点就是 2 * i + 1,父节点就是 i / 2。 这句话,那么反序列化对你就不是难事。 如果我用一个箭头表示节点的父子关系,箭头指向节点的两个子节点,那么大概是这样的: 我们刚才提到了: 1 号节点的两个子节点的 2 号 和 3 号。 2 号节点的两个子节点的 4 号 和 5 号。 。。。 i 号节点的两个子节点的 2 * i 号 和 2 * i + 1 号。 此时你可能会写出类似这样的代码: 1234567891011121314151617181920212223def deserialize(self, data): if data == 'null': return None nodes = data.split(',') root = TreeNode(nodes[0]) # 从一号开始编号,编号信息一起入队 q = collections.deque([(root, 1)]) while q: cur, i = q.popleft() # 2 * i 是左节点,而 2 * i 编号对应的其实是索引为 2 * i - 1 的元素, 右节点同理。 if 2 * i - 1 < len(nodes): lv = nodes[2 * i - 1] if 2 * i < len(nodes): rv = nodes[2 * i] if lv != 'null': l = TreeNode(lv) # 将左节点和 它的编号 2 * i 入队 q.append((l, 2 * i)) cur.left = l if rv != 'null': r = TreeNode(rv) # 将右节点和 它的编号 2 * i + 1 入队 q.append((r, 2 * i + 1)) cur.right = r return root 但是上面的代码是不对的,因为我们序列化的时候其实不是完全二叉树,这也是上面我埋下的伏笔。因此遇到类似这样的 case 就会挂: 这也是我前面说”上面代码的序列化并不是一颗完全二叉树“的原因。 其实这个很好解决, 核心还是上面我画的那种图: 其实我们可以: 用三个指针分别指向数组第一项,第二项和第三项(如果存在的话),这里用 p1,p2,p3 来标记,分别表示当前处理的节点,当前处理的节点的左子节点和当前处理的节点的右子节点。 p1 每次移动一位,p2 和 p3 每次移动两位。 p1.left = p2; p1.right = p3。 持续上面的步骤直到 p1 移动到最后。 因此代码就不难写出了。反序列化代码如下: 123456789101112131415161718192021def deserialize(self, data): if data == 'null': return None nodes = data.split(',') root = TreeNode(nodes[0]) q = collections.deque([root]) i = 0 while q and i < len(nodes) - 2: cur = q.popleft() lv = nodes[i + 1] rv = nodes[i + 2] i += 2 if lv != 'null': l = TreeNode(lv) q.append(l) cur.left = l if rv != 'null': r = TreeNode(rv) q.append(r) cur.right = r return root 这个题目虽然并不是完全二叉树的题目,但是却和完全二叉树很像,有借鉴完全二叉树的地方。 路径关于路径这个概念,leetcode 真的挺喜欢考察的,不信你自己去 leetcode 官网搜索一下路径,看有多少题。树的路径这种题目的变种很多,算是一种经典的考点了。 要明白路径的概念,以及如何解决这种题,只需要看一个题目就好了 124.二叉树中的最大路径和,虽然是困难难度,但是搞清楚概念的话,和简单难度没啥区别。 接下来,我们就以这道题讲解一下。 这道题的题目是 给定一个非空二叉树,返回其最大路径和。路径的概念是:一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。这听起来真的不容易理解,力扣给的 demo 我也没搞懂,这里我自己画了几个图来给大家解释一下这个概念。 首先是官网给的两个例子: 接着是我自己画的一个例子: 如图红色的部分是最大路径上的节点。 可以看出: 路径可以由一个节点做成,可以由两个节点组成,也可以由三个节点组成等等,但是必须连续。 路径必须是”直来直去“的,不能有分叉。 比如上图的路径的左下角是 3,当然也可以是 2,但是 2 比较小。但是不可以 2 和 3 同时选。 我们继续回到 124 题。题目说是 ”从任意节点出发…….“ 看完这个描述我会想到大概率是要么全局记录最大值,要么双递归。 如果使用双递归,那么复杂度就是 $O(N^2)$,实际上,子树的路径和计算出来了,可以推导出父节点的最大路径和,因此如果使用双递归会有重复计算。一个可行的方式是记忆化递归。 如果使用全局记录最大值,只需要在递归的时候 return 当前的一条边(上面提了不能拐),并在函数内部计算以当前节点出发的最大路径和,并更新全局最大值即可。 这里的核心其实是 return 较大的一条边,因为较小的边不可能是答案。 这里我选择使用第二种方法。 代码: 12345678910111213class Solution: ans = float('-inf') def maxPathSum(self, root: TreeNode) -> int: def dfs(node): if not node: return 0 l = dfs(node.left) r = dfs(node.right) # 选择当前的节点,并选择左右两边,当然左右两边也可以不选。必要时更新全局最大值 self.ans = max(self.ans, max(l,0) + max(r, 0) + node.val) # 只返回一边,因此我们挑大的返回。当然左右两边也可以不选 return max(l, r, 0) + node.val dfs(root) return self.ans 类似题目 113. 路径总和 I 距离和路径类似,距离也是一个相似且频繁出现的一个考点,并且二者都是搜索类题目的考点。原因就在于最短路径就是距离,而树的最短路径就是边的数目。 这两个题练习一下,碰到距离的题目基本就稳了。 834.树中距离之和 863.二叉树中所有距离为 K 的结点 七个技巧上面数次提到了七个技巧,相信大家已经迫不及待想要看看这七个技巧了吧。那就让我拿出本章压箱底的内容吧~ 注意,这七个技巧全部是基于 dfs 的,bfs 掌握了模板就行,基本没有什么技巧可言。 认真学习的小伙伴可以发现了, 上面的内容只有二叉树的迭代写法(双色标记法) 和 两个 BFS 模板 具有实操性,其他大多是战略思想上的。算法思想固然重要,但是要结合具体实践落地才能有实践价值,才能让我们把知识消化成自己的。而这一节满满的全是实用干货ヽ( ̄ ω  ̄( ̄ ω  ̄〃)ゝ。 dfs(root)第一个技巧,也是最容易掌握的一个技巧。我们写力扣的树题目的时候,函数的入参全都是叫 root。而这个技巧是说,我们在写 dfs 函数的时候,要将函数中表示当前节点的形参也写成 root。即: 12def dfs(root): # your code 而之前我一直习惯写成 node,即: 12def dfs(node): # your code 可能有的同学想问:” 这有什么关系么?“。我总结了两个原因。 第一个原因是:以前 dfs 的形参写的是 node, 而我经常误写成 root,导致出错(这个错误并不会抛错,因此不是特别容易发现)。自从换成了 root 就没有发生这样的问题了。 第二个原因是:这样写相当于把 root 当成是 current 指针来用了。最开始 current 指针指向 root,然后不断修改指向树的其它节点。这样就概念就简化了,只有一个当前指针的概念。如果使用 node,就是当前指针 + root 指针两个概念了。 (一开始 current 就是 root) (后面 current 不断改变。具体如何改变,取决于你的搜索算法,是 dfs 还是 bfs 等) 单/双递归上面的技巧稍显简单,但是却有用。这里介绍一个稍微难一点的技巧,也更加有用。 我们知道递归是一个很有用的编程技巧,灵活使用递归,可以使自己的代码更加简洁,简洁意味着代码不容易出错,即使出错了,也能及时发现问题并修复。 树的题目大多数都可以用递归轻松地解决。如果一个递归不行,那么来两个。(至今没见过三递归或更多递归) 单递归大家写的比较多了,其实本篇文章的大部分递归都是单递归。 那什么时候需要两个递归呢?其实我上面已经提到了,那就是如果题目有类似,任意节点开始 xxxx 或者所有 xxx这样的说法,就可以考虑使用双递归。但是如果递归中有重复计算,则可以使用双递归 + 记忆化 或者直接单递归。 比如 面试题 04.12. 求和路径,再比如 563.二叉树的坡度 这两道题的题目说法都可以考虑使用双递归求解。 双递归的基本套路就是一个主递归函数和一个内部递归函数。主递归函数负责计算以某一个节点开始的 xxxx,内部递归函数负责计算 xxxx,这样就实现了以所有节点开始的 xxxx。 其中 xxx 可以替换成任何题目描述,比如路径和等 一个典型的加法双递归是这样的: 1234567def dfs_inner(root): # 这里写你的逻辑,就是前序遍历 dfs_inner(root.left) dfs_inner(root.right) # 或者在这里写你的逻辑,那就是后序遍历def dfs_main(root): return dfs_inner(root) + dfs_main(root.left) + dfs_main(root.right) 大家可以用我的模板去套一下上面两道题试试。 前后遍历前面我的链表专题也提到了前后序遍历。由于链表只有一个 next 指针,因此只有两种遍历。而二叉树有两个指针,因此常见的遍历有三个,除了前后序,还有一个中序。而中序除了二叉搜索树,其他地方用的并不多。 和链表一样, 要掌握树的前后序,也只需要记住一句话就好了。那就是如果是前序遍历,那么你可以想象上面的节点都处理好了,怎么处理的不用管。相应地如果是后序遍历,那么你可以想象下面的树都处理好了,怎么处理的不用管。这句话的正确性也是毋庸置疑。 前后序对链表来说比较直观。对于树来说,其实更形象地说应该是自顶向下或者自底向上。自顶向下和自底向上在算法上是不同的,不同的写法有时候对应不同的书写难度。比如 https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/,这种题目就适合通过参数扩展 + 前序来完成。 关于参数扩展的技巧,我们在后面展开。 自顶向下就是在每个递归层级,首先访问节点来计算一些值,并在递归调用函数时将这些值传递到子节点,一般是通过参数传到子树中。 自底向上是另一种常见的递归方法,首先对所有子节点递归地调用函数,然后根据返回值和根节点本身的值得到答案。 关于前后序的思维技巧,可以参考我的这个文章 的前后序部分。 总结下我的经验: 大多数树的题使用后序遍历比较简单,并且大多需要依赖左右子树的返回值。比如 1448. 统计二叉树中好节点的数目 不多的问题需要前序遍历,而前序遍历通常要结合参数扩展技巧。比如 1022. 从根到叶的二进制数之和 如果你能使用参数和节点本身的值来决定什么应该是传递给它子节点的参数,那就用前序遍历。 如果对于树中的任意一个节点,如果你知道它子节点的答案,你能计算出当前节点的答案,那就用后序遍历。 如果遇到二叉搜索树则考虑中序遍历 虚拟节点是的!不仅仅链表有虚拟节点的技巧,树也是一样。关于这点大家可能比较容易忽视。 回忆一下链表的虚拟指针的技巧,我们通常在什么时候才会使用? 其中一种情况是链表的头会被修改。这个时候通常需要一个虚拟指针来做新的头指针,这样就不需要考虑第一个指针的问题了(因为此时第一个指针变成了我们的虚拟指针,而虚拟指针是不用参与题目运算的)。树也是一样,当你需要对树的头节点(在树中我们称之为根节点)进行修改的时候, 就可以考虑使用虚拟指针的技巧了。 另外一种是题目需要返回树中间的某个节点(不是返回根节点)。实际上也可借助虚拟节点。由于我上面提到的指针的操作,实际上,你可以新建一个虚拟头,然后让虚拟头在恰当的时候(刚好指向需要返回的节点)断开连接,这样我们就可以返回虚拟头的 next 就 ok 了。 更多关于虚拟指针的技巧可以参考这个文章 的虚拟头部分。 下面就力扣中的两道题来看一下。 【题目一】814. 二叉树剪枝题目描述: 12345678910111213给定二叉树根结点 root ,此外树的每个结点的值要么是 0,要么是 1。返回移除了所有不包含 1 的子树的原二叉树。( 节点 X 的子树为 X 本身,以及所有 X 的后代。)示例1:输入: [1,null,0,0,1]输出: [1,null,0,null,1]解释:只有红色节点满足条件“所有不包含 1 的子树”。右图为返回的答案。 12345示例2:输入: [1,0,1,0,0,0,1]输出: [1,null,1,null,1] 123示例3:输入: [1,1,0,1,1,0,1,0]输出: [1,1,0,1,1,null,1] 1234说明:给定的二叉树最多有 100 个节点。每个节点的值只会为 0 或 1 。 根据题目描述不难看出, 我们的根节点可能会被整个移除掉。这就是我上面说的根节点被修改的情况。 这个时候,我们只要新建一个虚拟节点当做新的根节点,就不需要考虑这个问题了。 此时的代码是这样的: 123456789var pruneTree = function (root) { function dfs(root) { // do something } ans = new TreeNode(-1); ans.left = root; dfs(ans); return ans.left;}; 接下来,只需要完善 dfs 框架即可。 dfs 框架也很容易,我们只需要将子树和为 0 的节点移除即可,而计算子树和是一个难度为 easy 的题目,只需要后序遍历一次并收集值即可。 计算子树和的代码如下: 123456function dfs(root) { if (!root) return 0; const l = dfs(root.left); const r = dfs(root.right); return root.val + l + r;} 有了上面的铺垫,最终代码就不难写出了。 完整代码(JS): 1234567891011121314var pruneTree = function (root) { function dfs(root) { if (!root) return 0; const l = dfs(root.left); const r = dfs(root.right); if (l == 0) root.left = null; if (r == 0) root.right = null; return root.val + l + r; } ans = new TreeNode(-1); ans.left = root; dfs(ans); return ans.left;}; 【题目一】1325. 删除给定值的叶子节点题目描述: 123456789给你一棵以 root 为根的二叉树和一个整数 target ,请你删除所有值为 target 的 叶子节点 。注意,一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。也就是说,你需要重复此过程直到不能继续删除。 示例 1: 12345678输入:root = [1,2,3,2,null,2,4], target = 2输出:[1,null,3,null,4]解释:上面左边的图中,绿色节点为叶子节点,且它们的值与 target 相同(同为 2 ),它们会被删除,得到中间的图。有一个新的节点变成了叶子节点且它的值与 target 相同,所以将再次进行删除,从而得到最右边的图。示例 2: 12345输入:root = [1,3,3,3,2], target = 3输出:[1,3,null,null,2]示例 3: 1234567891011121314151617181920输入:root = [1,2,null,2,null,2], target = 2输出:[1]解释:每一步都删除一个绿色的叶子节点(值为 2)。示例 4:输入:root = [1,1,1], target = 1输出:[]示例 5:输入:root = [1,2,3], target = 1输出:[1,2,3] 提示:1 <= target <= 1000每一棵树最多有 3000 个节点。每一个节点值的范围是 [1, 1000] 。 和上面题目类似,这道题的根节点也可能被删除,因此这里我们采取和上面题目类似的技巧。 由于题目说明了一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。也就是说,你需要重复此过程直到不能继续删除。 因此这里使用后序遍历会比较容易,因为形象地看上面的描述过程你会发现这是一个自底向上的过程,而自底向上通常用后序遍历。 上面的题目,我们可以根据子节点的返回值决定是否删除子节点。而这道题是根据左右子树是否为空,删除自己,关键字是自己。而树的删除和链表删除类似,树的删除需要父节点,因此这里的技巧和链表类似,记录一下当前节点的父节点即可,并通过参数扩展向下传递。至此,我们的代码大概是: 123456789class Solution: def removeLeafNodes(self, root: TreeNode, target: int) -> TreeNode: # 单链表只有一个 next 指针,而二叉树有两个指针 left 和 right,因此要记录一下当前节点是其父节点的哪个孩子 def dfs(node, parent, is_left=True): # do something ans = TreeNode(-1) ans.left = root dfs(root, ans) return ans.left 有了上面的铺垫,最终代码就不难写出了。 完整代码(Python): 12345678910111213class Solution: def removeLeafNodes(self, root: TreeNode, target: int) -> TreeNode: def dfs(node, parent, is_left=True): if not node: return dfs(node.left, node, True) dfs(node.right, node, False) if node.val == target and parent and not node.left and not node.right: if is_left: parent.left = None else: parent.right = None ans = TreeNode(-1) ans.left = root dfs(root, ans) return ans.left 边界发现自己老是边界考虑不到,首先要知道这是正常的,人类的本能。 大家要克服这种本能, 只有多做,慢慢就能克服。 就像改一个坏习惯一样,除了坚持,一个有用的技巧是奖励和惩罚,我也用过这个技巧。 上面我介绍了树的三种题型。对于不同的题型其实边界考虑的侧重点也是不一样的,下面我们展开聊聊。 搜索类搜索类的题目,树的边界其实比较简单。 90% 以上的题目边界就两种情况。 树的题目绝大多树又是搜索类,你想想掌握这两种情况多重要。 空节点 伪代码: 123def dfs(root): if not root: print('是空节点,你需要返回合适的值') # your code here` 叶子节点 伪代码: 1234def dfs(root): if not root: print('是空节点,你需要返回合适的值') if not root.left and not root.right: print('是叶子节点,你需要返回合适的值')# your code here` 一张图总结一下: 经过这样的处理,后面的代码基本都不需要判空了。 构建类相比于搜索类, 构建就比较麻烦了。我总结了两个常见的边界。 参数扩展的边界 比如 1008 题, 根据前序遍历构造二叉搜索树。我就少考虑的边界。 1234567891011121314151617181920def bstFromPreorder(self, preorder: List[int]) -> TreeNode: def dfs(start, end): if start > end: return None if start == end: return TreeNode(preorder[start]) root = TreeNode(preorder[start]) mid = -1 for i in range(start + 1, end + 1): if preorder[i] > preorder[start]: mid = i break if mid == -1: root.left = dfs(start + 1, end) else: root.left = dfs(start + 1, mid - 1) root.right = dfs(mid, end) return root return dfs(0, len(preorder) - 1) 注意上面的代码没有判断 start == end 的情况,加下面这个判断就好了。 1if start == end: return TreeNode(preorder[start]) 虚拟节点 除了搜索类的技巧可以用于构建类外,也可以考虑用我上面的讲的虚拟节点。 参数扩展大法参数扩展这个技巧非常好用,一旦掌握你会爱不释手。 如果不考虑参数扩展, 一个最简单的 dfs 通常是下面这样: 12def dfs(root): # do something 而有时候,我们需要 dfs 携带更多的有用信息。典型的有以下三种情况: 携带父亲或者爷爷的信息。 1234def dfs(root, parent): if not root: return dfs(root.left, root) dfs(root.right, root) 携带路径信息,可以是路径和或者具体的路径数组等。 路径和: 123456def dfs(root, path_sum): if not root: # 这里可以拿到根到叶子的路径和 return path_sum dfs(root.left, path_sum + root.val) dfs(root.right, path_sum + root.val) 路径: 123456789def dfs(root, path): if not root: # 这里可以拿到根到叶子的路径 return path path.append(root.val) dfs(root.left, path) dfs(root.right, path) # 撤销 path.pop() 学会了这个技巧,大家可以用 面试题 04.12. 求和路径 来练练手。 以上几个模板都很常见,类似的场景还有很多。总之当你需要传递额外信息给子节点(关键字是子节点)的时候,请务必掌握这种技巧。这也解释了为啥参数扩展经常用于前序遍历。 二叉搜索树的搜索题大多数都需要扩展参考,甚至怎么扩展都是固定的。 二叉搜索树的搜索总是将最大值和最小值通过参数传递到左右子树,类似 dfs(root, lower, upper),然后在递归过程更新最大和最小值即可。这里需要注意的是 (lower, upper) 是的一个左右都开放的区间。 比如有一个题783. 二叉搜索树节点最小距离是求二叉搜索树的最小差值的绝对值。当然这道题也可以用我们前面提到的二叉搜索树的中序遍历的结果是一个有序数组这个性质来做。只需要一次遍历,最小差一定出现在相邻的两个节点之间。 这里我用另外一种方法,该方法就是扩展参数大法中的 左右边界法。 12345678910class Solution:def minDiffInBST(self, root): def dfs(node, lower, upper): if not node: return upper - lower left = dfs(node.left, lower, node.val) right = dfs(node.right, node.val, upper) # 要么在左,要么在右,不可能横跨(因为是 BST) return min(left, right) return dfs(root, float('-inf'), float('inf') 其实这个技巧不仅适用二叉搜索树,也可是适用在别的树,比如 1026. 节点与其祖先之间的最大差值,题目大意是:给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。 使用类似上面的套路轻松求解。 12345678class Solution:def maxAncestorDiff(self, root: TreeNode) -> int: def dfs(root, lower, upper): if not root: return upper - lower # 要么在左,要么在右,要么横跨。 return max(dfs(root.left, min(root.val, lower), max(root.val, upper)), dfs(root.right, min(root.val, lower), max(root.val, upper))) return dfs(root, float('inf'), float('-inf')) 返回元组/列表通常,我们的 dfs 函数的返回值是一个单值。而有时候为了方便计算,我们会返回一个数组或者元祖。 对于个数固定情况,我们一般使用元组,当然返回数组也是一样的。 这个技巧和参数扩展有异曲同工之妙,只不过一个作用于函数参数,一个作用于函数返回值。 返回元祖返回元组的情况还算比较常见。比如 865. 具有所有最深节点的最小子树,一个简单的想法是 dfs 返回深度,我们通过比较左右子树的深度来定位答案(最深的节点位置)。 代码: 123456789class Solution: def subtreeWithAllDeepest(self, root: TreeNode) -> int: def dfs(node, d): if not node: return d l_d = dfs(node.left, d + 1) r_d = dfs(node.right, d + 1) if l_d >= r_d: return l_d return r_d return dfs(root, -1) 但是题目要求返回的是树节点的引用啊,这个时候应该考虑返回元祖,即除了返回深度,也要把节点给返回。 12345678910class Solution: def subtreeWithAllDeepest(self, root: TreeNode) -> TreeNode: def dfs(node, d): if not node: return (node, d) l, l_d = dfs(node.left, d + 1) r, r_d = dfs(node.right, d + 1) if l_d == r_d: return (node, l_d) if l_d > r_d: return (l, l_d) return (r, r_d) return dfs(root, -1)[0] 返回数组dfs 返回数组比较少见。即使题目要求返回数组,我们也通常是声明一个数组,在 dfs 过程不断 push,最终返回这个数组。而不会选择返回一个数组。绝大多数情况下,返回数组是用于计算笛卡尔积。因此你需要用到笛卡尔积的时候,考虑使用返回数组的方式。 一般来说,如果需要使用笛卡尔积的情况还是比较容易看出的。另外一个不太准确的技巧是,如果题目有”所有可能“,”所有情况“,可以考虑使用此技巧。 一个典型的题目是 1530.好叶子节点对的数量 题目描述: 123456789给你二叉树的根节点 root 和一个整数 distance 。如果二叉树中两个叶节点之间的 最短路径长度 小于或者等于 distance ,那它们就可以构成一组 好叶子节点对 。返回树中 好叶子节点对的数量 。 示例 1: 12345678 输入:root = [1,2,3,null,4], distance = 3输出:1解释:树的叶节点是 3 和 4 ,它们之间的最短路径的长度是 3 。这是唯一的好叶子节点对。示例 2: 123456789101112131415161718192021222324输入:root = [1,2,3,4,5,6,7], distance = 3输出:2解释:好叶子节点对为 [4,5] 和 [6,7] ,最短路径长度都是 2 。但是叶子节点对 [4,6] 不满足要求,因为它们之间的最短路径长度为 4 。示例 3:输入:root = [7,1,4,6,null,5,3,null,null,null,null,null,2], distance = 3输出:1解释:唯一的好叶子节点对是 [2,5] 。示例 4:输入:root = [100], distance = 1输出:0示例 5:输入:root = [1,1,1], distance = 2输出:1 提示:tree 的节点数在 [1, 2^10] 范围内。每个节点的值都在 [1, 100] 之间。1 <= distance <= 10 上面我们学习了路径的概念,在这道题又用上了。 其实两个叶子节点的最短路径(距离)可以用其最近的公共祖先来辅助计算。即两个叶子节点的最短路径 = 其中一个叶子节点到最近公共祖先的距离 + 另外一个叶子节点到最近公共祖先的距离。 因此我们可以定义 dfs(root),其功能是计算以 root 作为出发点,到其各个叶子节点的距离。 如果其子节点有 8 个叶子节点,那么就返回一个长度为 8 的数组, 数组每一项的值就是其到对应叶子节点的距离。 如果子树的结果计算出来了,那么父节点只需要把子树的每一项加 1 即可。这点不难理解,因为父到各个叶子节点的距离就是父节点到子节点的距离(1) + 子节点到各个叶子节点的距离。 由上面的推导可知需要先计算子树的信息,因此我们选择前序遍历。 完整代码(Python): 12345678910111213141516171819class Solution: def countPairs(self, root: TreeNode, distance: int) -> int: self.ans = 0 def dfs(root): if not root: return [] if not root.left and not root.right: return [0] ls = [l + 1 for l in dfs(root.left)] rs = [r + 1 for r in dfs(root.right)] # 笛卡尔积 for l in ls: for r in rs: if l + r <= distance: self.ans += 1 return ls + rs dfs(root) return self.ans 894. 所有可能的满二叉树 也是一样的套路,大家用上面的知识练下手吧~ 经典题目推荐大家先把本文提到的题目都做一遍,然后用本文学到的知识做一下下面十道练习题,检验一下自己的学习成果吧! 剑指 Offer 55 - I. 二叉树的深度 剑指 Offer 34. 二叉树中和为某一值的路径 101. 对称二叉树 226. 翻转二叉树 543. 二叉树的直径 662. 二叉树最大宽度 971. 翻转二叉树以匹配先序遍历 987. 二叉树的垂序遍历 863. 二叉树中所有距离为 K 的结点 面试题 04.06. 后继者 总结树的题目一种中心点就是遍历,这是搜索问题和修改问题的基础。 而遍历从大的方向分为广度优先遍历和深度优先遍历,这就是我们的两个基本点。两个基本点可以进一步细分,比如广度优先遍历有带层信息的和不带层信息的(其实只要会带层信息的就够了)。深度优先遍历常见的是前序和后序,中序多用于二叉搜索树,因为二叉搜索树的中序遍历是严格递增的数组。 树的题目从大的方向上来看就三种,一种是搜索类,这类题目最多,这种题目牢牢把握开始点,结束点 和 目标即可。构建类型的题目我之前的专题以及讲过了,一句话概括就是根据一种遍历结果确定根节点位置,根据另外一种遍历结果(如果是二叉搜索树就不需要了)确定左右子树。修改类题目不多,这种问题边界需要特殊考虑,这是和搜索问题的本质区别,可以使用虚拟节点技巧。另外搜索问题,如果返回值不是根节点也可以考虑虚拟节点。 树有四个比较重要的对做题帮助很大的概念,分别是完全二叉树,二叉搜索树,路径和距离,这里面相关的题目推荐大家好好做一下,都很经典。 最后我给大家介绍了七种干货技巧,很多技巧都说明了在什么情况下可以使用。好不好用你自己去找几个题目试试就知道了。 以上就是树专题的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 我整理的 1000 多页的电子书已经开发下载了,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"树","slug":"树","permalink":"https://lucifer.ren/blog/categories/树/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"树","slug":"树","permalink":"https://lucifer.ren/blog/tags/树/"}]},{"title":"浏览器事件模型","slug":"browser-event","date":"2020-11-14T16:00:00.000Z","updated":"2023-01-05T12:24:49.905Z","comments":true,"path":"2020/11/15/browser-event/","link":"","permalink":"https://lucifer.ren/blog/2020/11/15/browser-event/","excerpt":"我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢?为什么 NodeJS 也是事件驱动的 ? 两者是一回事么? 实际上不管是浏览器还是 Nodejs 都是事件驱动的,都有自己的事件模型。在这里,我们只讲解浏览器端的事件模型,如果对 Nodejs 事件模型感兴趣的,请期待我的 Nodejs 部分的讲解。","text":"我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢?为什么 NodeJS 也是事件驱动的 ? 两者是一回事么? 实际上不管是浏览器还是 Nodejs 都是事件驱动的,都有自己的事件模型。在这里,我们只讲解浏览器端的事件模型,如果对 Nodejs 事件模型感兴趣的,请期待我的 Nodejs 部分的讲解。 什么是事件驱动事件驱动通俗地来说就是什么都抽象为事件。 一次点击是一个事件 键盘按下是一个事件 一个网络请求成功是一个事件 页面加载是一个事件 页面报错是一个事件 … 浏览器依靠事件来驱动 APP 运行下去,如果没有了事件驱动,那么 APP 会直接从头到尾运行完,然后结束,事件驱动是浏览器的基石。 本篇文章不讲解事件循环的内容,事件循环部分会在本章的其他章节讲解,敬请期待。 一个简单的例子其实现实中的红绿灯就是一种事件,它告诉我们现在是红灯状态,绿灯状态,还是黄灯状态。 我们需要根据这个事件自己去完成一些操作,比如红灯和黄灯我们需要等待,绿灯我们可以过马路。 下面我们来看一个最简单的浏览器端的事件: html 代码: 1<button>Change color</button> js 代码: 12345var btn = document.querySelector(\"button\");btn.onclick = function () { console.log(\"button clicked\");}; 代码很简单,我们在 button 上注册了一个事件,这个事件的 handler 是一个我们定义的匿名函数。当用户点击了这个被注册了事件的 button 的时候,这个我们定义好的匿名函数就会被执行。 如何绑定事件我们有三种方法可以绑定事件,分别是行内绑定,直接赋值,用addEventListener。 内联 这个方法非常不推荐 html 代码: 1<button onclick=\"handleClick()\">Press me</button> 然后在 script 标签内写: 123function handleClick() { console.log(\"button clicked\");} 直接赋值 和我上面举的例子一样: 12345var btn = document.querySelector(\"button\");btn.onclick = function () { console.log(\"button clicked\");}; 这种方法有两个缺点 不能添加多个同类型的 handler 12btn.onclick = functionA;btn.onclick = functionB; 这样只有 functionB 有效,这可以通过addEventListener来解决。 不能控制在哪个阶段来执行,这个会在后面将事件捕获/冒泡的时候讲到。这个同样可以通过addEventListener来解决。 因此 addEventListener 横空出世,这个也是目前推荐的写法。 addEventListener 旧版本的addEventListener第三个参数是 bool,新版版的第三个参数是对象,这样方便之后的扩展,承载更多的功能, 我们来重点介绍一下它。 addEventListener 可以给 Element,Document,Window,甚至 XMLHttpRequest 等绑定事件,当指定的事件发生的时候,绑定的回调函数就会被以某种机制进行执行,这种机制我们稍后就会讲到。 语法: 123target.addEventListener(type, listener[, options]);target.addEventListener(type, listener[, useCapture]);target.addEventListener(type, listener[, useCapture, wantsUntrusted ]); // Gecko/Mozilla only type 是你想要绑定的事件类型,常见的有 click, scroll, touch, mouseover 等,旧版本的第三个参数是 bool,表示是否是捕获阶段,默认是 false,即默认为冒泡阶段。新版本是一个对象,其中有 capture(和上面功能一样),passive 和 once。 once 用来执行是否只执行一次,passive 如果被指定为 true 表示永远不会执行preventDefault(),这在实现丝滑柔顺的滚动的效果中很重要。更多请参考Improving scrolling performance with passive listeners 框架中的事件实际上,我们现在大多数情况都是用框架来写代码,因此上面的情况其实在现实中是非常少见的,我们更多看到的是框架封装好的事件,比如 React 的合成事件,感兴趣的可以看下这几篇文章。 React SyntheticEvent Vue 和 React 的优点分别是什么?两者的最核心差异对比是什么? 虽然我们很少时候会接触到原生的事件,但是了解一下事件对象,事件机制,事件代理等还是很有必要的,因为框架的事件系统至少在这方面还是一致的,这些内容我们接下来就会讲到。 事件对象所有的事件处理函数在被浏览器执行的时候都会带上一个事件对象,举个例子: 12345function handleClick(e) { console.log(e);}btn.addEventListener(\"click\", handleClick); 这个 e 就是事件对象,即event object。 这个对象有一些很有用的属性和方法,下面举几个常用的属性和方法。 属性 target x, y 等位置信息 timeStamp eventPhase … 方法 preventDefault 用于阻止浏览器的默认行为,比如 a 标签会默认进行跳转,form 会默认校验并发送请求到 action 指定的地址等 stopPropagation 用于阻止事件的继续冒泡行为,后面讲事件传播的时候会提到。 … 事件传播前面讲到了事件默认是绑定到冒泡阶段的,如果你显式令 useCapture 为 true,则会绑定到捕获阶段。 事件捕获很有意思,以至于我会经常出事件的题目加上一点事件传播的机制,让候选人进行回答,这很能体现一个人的水平。了解事件的传播机制,对于一些特定问题有着非常大的作用。 一个 Element 上绑定的事件触发了,那么其实会经过三个阶段。 第一个阶段 - 捕获阶段 从最外层即 HTML 标签开始,检查当前元素有没有绑定对应捕获阶段事件,如果有则执行,没有则继续往里面传播,这个过程递归执行直到触达触发这个事件的元素为止。 伪代码: 12345678910111213141516function capture(e, currentElement) { if (currentElement.listners[e.type] !== void 0) { currentElement.listners[e.type].forEach((fn) => fn(e)); } // pass down if (currentElement !== e.target) { // getActiveChild用于获取当前事件传播链路上的子节点 capture(e, getActiveChild(currentElement, e)); } else { bubble(e, currentElement); }}// 这个Event对象由引擎创建capture(new Event(), document.querySelector(\"html\")); 第二个阶段 - 目标阶段 上面已经提到了,这里省略了。 第三个阶段 - 冒泡阶段 从触发这个事件的元素开始,检查当前元素有没有绑定对应冒泡阶段事件,如果有则执行,没有则继续往里面传播,这个过程递归执行直到触达 HTML 为止。 伪代码: 123456789function bubble(e, currentElement) { if (currentElement.listners[e.type] !== void 0) { currentElement.listners[e.type].forEach((fn) => fn(e)); } // returning if (currentElement !== document.querySelector(\"html\")) { bubble(e, currentElement.parent); }} 上述的过程用图来表示为: 如果你不希望事件继续冒泡,可以用之前我提到的stopPropagation。 伪代码: 12345678910111213141516171819function bubble(e, currentElement) { let stopped = false; function cb() { stopped = true; } if (currentElement.listners[e.type] !== void 0) { currentElement.listners[e.type].forEach((fn) => { fn({ ...e, stopPropagation: cb, }); if (stopped) return; }); } // returning if (currentElement !== document.querySelector(\"html\")) { bubble(e, currentElement.parent); }} 事件代理利用上面提到的事件冒泡机制,我们可以选择做一些有趣的东西。 举个例子: 我们有一个如下的列表,我们想在点击对应列表项的时候,输出是点击了哪个元素。 HTML 代码: 123456<ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li></ul> JS 代码: 123document .querySelector(\"ul\") .addEventListener(\"click\", (e) => console.log(e.target.innerHTML)); 在线地址 上面说了addEventListener会默认绑定到冒泡阶段,因此事件会从目标阶段开始,向外层冒泡,到我们绑定了事件的 ul 上,ul 中通过事件对象的 target 属性就能获取到是哪一个元素触发的。 “事件会从目标阶段开始”,并不是说事件没有捕获阶段,而是我们没有绑定捕获阶段,我描述给省略了。 我们只给外层的 ul 绑定了事件处理函数,但是可以看到 li 点击的时候,实际上会打印出对应 li 的内容(1,2,3 或者 4)。 我们无须给每一个 li 绑定事件处理函数,不仅从代码量还是性能上都有一定程度的提升。 这个有趣的东西,我们给了它一个好听的名字“事件代理”。在实际业务中我们会经常使用到这个技巧,这同时也是面试的高频考点。 总结事件其实不是浏览器特有的,和 JS 语言也没有什么关系,这也是我为什么没有将其划分到 JS 部分的原因。很多地方都有事件系统,但是各种事件模型又不太一致。 我们今天讲的是浏览器的事件模型,浏览器基于事件驱动,将很多东西都抽象为事件,比如用户交互,网络请求,页面加载,报错等,可以说事件是浏览器正常运行的基石。 我们在使用的框架都对事件进行了不同程度的封装和处理,除了了解原生的事件和原理,有时候了解一下框架本身对事件的处理也是很有必要的。 当发生一个事件的时候,浏览器会初始化一个事件对象,然后将这个事件对象按照一定的逻辑进行传播,这个逻辑就是事件传播机制。 我们提到了事件传播其实分为三个阶段,按照时间先后顺序分为捕获阶段,目标阶段和冒泡阶段。开发者可以选择监听不同的阶段,从而达到自己想要的效果。 事件对象有很多属性和方法,允许你在事件处理函数中进行读取和操作,比如读取点击的坐标信息,阻止冒泡等。 最后我们通过一个例子,说明了如何利用冒泡机制来实现事件代理。 本文只是一个浏览器事件机制的科普文,并没有也不会涉及到很多细节。希望这篇文章能让你对浏览器时间有更深的理解,如果你对 nodejs 时间模型感兴趣,请期待我的 nodejs 事件模型。 事件循环和事件循环也有千丝万缕的联系,如果有时间,我会出一篇关于时间循环的文章。","categories":[{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/categories/浏览器/"},{"name":"事件","slug":"浏览器/事件","permalink":"https://lucifer.ren/blog/categories/浏览器/事件/"}],"tags":[{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/tags/浏览器/"},{"name":"事件","slug":"事件","permalink":"https://lucifer.ren/blog/tags/事件/"}]},{"title":"用最优雅的方式打开终端","slug":"iterm-window-hotkey","date":"2020-11-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.908Z","comments":true,"path":"2020/11/13/iterm-window-hotkey/","link":"","permalink":"https://lucifer.ren/blog/2020/11/13/iterm-window-hotkey/","excerpt":"如何快速呼出终端先看效果: 视频地址 Gif 太大, 放不了。 放一个 MP4 文件给大家看吧。 接下来就教你随时随地,用最优雅的方式。","text":"如何快速呼出终端先看效果: 视频地址 Gif 太大, 放不了。 放一个 MP4 文件给大家看吧。 接下来就教你随时随地,用最优雅的方式。 工具 iTerm2 步骤 打开 iTerm2 打开 Preference(快捷键 cmd + ,) 几乎所有的应用打开 Preference 都是 cmd + , 切换到 profiles,并点击 +。 即新建一个 profile 起一个名字,设置一下窗口位置浮在当前屏幕上(如图),这样可以随时查看终端。 绑定一个快捷键。切换到 keys 标签,点击最下方的 Config HotKey Window。(如图) 一点后话我的快捷键看起来很复杂, 实际上我把键盘右侧的 Alt 映射成了超级键。 · 先把 右侧的 Alt 映射到了 Caps Lock 然后将 Caps Lock 映射到 cmd + control + option + shift","categories":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/categories/工具/"}],"tags":[{"name":"工具","slug":"工具","permalink":"https://lucifer.ren/blog/tags/工具/"},{"name":"效率","slug":"效率","permalink":"https://lucifer.ren/blog/tags/效率/"},{"name":"命令行","slug":"命令行","permalink":"https://lucifer.ren/blog/tags/命令行/"}]},{"title":"几乎刷完了力扣所有的链表题,我发现了这些东西。。。","slug":"linked-list","date":"2020-11-07T16:00:00.000Z","updated":"2023-01-05T12:24:49.939Z","comments":true,"path":"2020/11/08/linked-list/","link":"","permalink":"https://lucifer.ren/blog/2020/11/08/linked-list/","excerpt":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 大家好,我是 lucifer。今天给大家带来的专题是《链表》。很多人觉得链表是一个很难的专题。实际上,只要你掌握了诀窍,它并没那么难。接下来,我们展开说说。 链表标签在 leetcode 一共有 54 道题。 为了准备这个专题,我花了几天时间将 leetcode 几乎所有的链表题目都刷了一遍。 可以看出,除了六个上锁的,其他我都刷了一遍。而实际上,这六个上锁的也没有什么难度,甚至和其他 48 道题差不多。 通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。","text":"先上下本文的提纲,这个是我用 mindmap 画的一个脑图,之后我会继续完善,将其他专题逐步完善起来。 大家也可以使用 vscode blink-mind 打开源文件查看,里面有一些笔记可以点开查看。源文件可以去我的公众号《力扣加加》回复脑图获取,以后脑图也会持续更新更多内容。vscode 插件地址:https://marketplace.visualstudio.com/items?itemName=awehook.vscode-blink-mind 大家好,我是 lucifer。今天给大家带来的专题是《链表》。很多人觉得链表是一个很难的专题。实际上,只要你掌握了诀窍,它并没那么难。接下来,我们展开说说。 链表标签在 leetcode 一共有 54 道题。 为了准备这个专题,我花了几天时间将 leetcode 几乎所有的链表题目都刷了一遍。 可以看出,除了六个上锁的,其他我都刷了一遍。而实际上,这六个上锁的也没有什么难度,甚至和其他 48 道题差不多。 通过集中刷这些题,我发现了一些有趣的信息,今天就分享给大家。 简介各种数据结构,不管是队列,栈等线性数据结构还是树,图的等非线性数据结构,从根本上底层都是数组和链表。不管你用的是数组还是链表,用的都是计算机内存,物理内存是一个个大小相同的内存单元构成的,如图: (图 1. 物理内存) 而数组和链表虽然用的都是物理内存,都是两者在对物理的使用上是非常不一样的,如图: (图 2. 数组和链表的物理存储图) 不难看出,数组和链表只是使用物理内存的两种方式。 数组是连续的内存空间,通常每一个单位的大小也是固定的,因此可以按下标随机访问。而链表则不一定连续,因此其查找只能依靠别的方式,一般我们是通过一个叫 next 指针来遍历查找。链表其实就是一个结构体。 比如一个可能的单链表的定义可以是: 1234interface ListNode<T> { data: T; next: ListNode<T>;} data 是数据域,存放数据,next 是一个指向下一个节点的指针。 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。 从上面的物理结构图可以看出数组是一块连续的空间,数组的每一项都是紧密相连的,因此如果要执行插入和删除操作就很麻烦。对数组头部的插入和删除时间复杂度都是$O(N)$,而平均复杂度也是$O(N)$,只有对尾部的插入和删除才是$O(1)$。简单来说”数组对查询特别友好,对删除和添加不友好“。为了解决这个问题,就有了链表这种数据结构。链表适合在数据需要有一定顺序,但是又需要进行频繁增删除的场景,具体内容参考后面的《链表的基本操作》小节。 (图 3. 一个典型的链表逻辑表示图) 后面所有的图都是基于逻辑结构,而不是物理结构 链表只有一个后驱节点 next,如果是双向链表还会有一个前驱节点 pre。 有没有想过为啥只有二叉树,而没有一叉树。实际上链表就是特殊的树,即一叉树。 链表的基本操作要想写出链表的题目, 熟悉链表的各种基本操作和复杂度是必须的。 插入插入只需要考虑要插入位置前驱节点和后继节点(双向链表的情况下需要更新后继节点)即可,其他节点不受影响,因此在给定指针的情况下插入的操作时间复杂度为O(1)。这里给定指针中的指针指的是插入位置的前驱节点。 伪代码: 1234temp = 待插入位置的前驱节点.next待插入位置的前驱节点.next = 待插入指针待插入指针.next = temp 如果没有给定指针,我们需要先遍历找到节点,因此最坏情况下时间复杂度为 O(N)。 提示 1: 考虑头尾指针的情况。 提示 2: 新手推荐先画图,再写代码。等熟练之后,自然就不需要画图了。 删除只需要将需要删除的节点的前驱指针的 next 指针修正为其下下个节点即可,注意考虑边界条件。 伪代码: 1待删除位置的前驱节点.next = 待删除位置的前驱节点.next.next 提示 1: 考虑头尾指针的情况。 提示 2: 新手推荐先画图,再写代码。等熟练之后,自然就不需要画图了。 遍历遍历比较简单,直接上伪代码。 迭代伪代码: 12345当前指针 = 头指针while 当前节点不为空 { print(当前节点) 当前指针 = 当前指针.next} 一个前序遍历的递归的伪代码: 12345dfs(cur) { if 当前节点为空 return print(cur.val) return dfs(cur.next)} 链表和数组到底有多大的差异?熟悉我的小伙伴应该经常听到我说过一句话,那就是数组和链表同样作为线性的数组结构,二者在很多方便都是相同的,只在细微的操作和使用场景上有差异而已。而使用场景,很难在题目中直接考察。 实际上,使用场景是可以死记硬背的。 因此,对于我们做题来说,二者的差异通常就只是细微的操作差异。这么说大家可能感受不够强烈,我给大家举几个例子。 数组的遍历: 1234for(int i = 0; i < arr.size();i++) { print(arr[i])} 链表的遍历: 123for (ListNode cur = head; cur != null; cur = cur.next) { print(cur.val)} 是不是很像? 可以看出二者逻辑是一致的,只不过细微操作不一样。比如: 数组是索引 ++ 链表是 cur = cur.next 如果我们需要逆序遍历呢? 123for(int i = arr.size() - 1; i > - 1;i--) { print(arr[i])} 如果是链表,通常需要借助于双向链表。而双向链表在力扣的题目很少,因此大多数你没有办法拿到前驱节点,这也是为啥很多时候会自己记录一个前驱节点 pre 的原因。 123for (ListNode cur = tail; cur != null; cur = cur.pre) { print(cur.val)} 如果往数组末尾添加一个元素就是: 1arr.push(1) 链表的话,很多语言没有内置的数组类型。比如力扣通常使用如下的类来模拟。 1234567public class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; }} 我们是不能直接调用 push 方法的。想一下,如果让你实现这个,你怎么做?你可以先自己想一下,再往下看。 3…2…1 ok,其实很简单。 123// 假设 tail 是链表的尾部节点tail.next = new ListNode('lucifer')tail = tail.next 经过上面两行代码之后, tail 仍然指向尾部节点。是不是很简单,你学会了么? 这有什么用?比如有的题目需要你复制一个新的链表, 你是不是需要开辟一个新的链表头,然后不断拼接(push)复制的节点?这就用上了。 对于数组的底层也是类似的,一个可能的数组 push 底层实现: 12arr.length += 1arr[arr.length - 1] = 'lucifer' 总结一下, 数组和链表逻辑上二者有很多相似之处,不同的只是一些使用场景和操作细节,对于做题来说,我们通常更关注的是操作细节。关于细节,接下来给大家介绍,这一小节主要让大家知道二者在思想和逻辑的神相似。 有些小伙伴做链表题先把链表换成数组,然后用数组做,本人不推荐这种做法,这等于是否认了链表存在的价值,小朋友不要模仿。 链表题难度几何?链表题真的不难。说链表不难是有证据的。就拿 LeetCode 平台来说,处于困难难度的题目只有两个。 其中 第 23 题基本没有什么链表操作,一个常规的“归并排序”即可搞定,而合并两个有序链表是一个简单题。如果你懂得数组的归并排序和合并两个有序链表,应该轻松拿下这道题。 合并两个有序数组也是一个简单题目,二者难度几乎一样。 而对于第 25 题, 相信你看完本节的内容,也可以做出来。 不过,话虽这么说,但是还是有很多小朋友给我说 ”指针绕来绕去就绕晕了“, ”老是死循环“ 。。。。。。链表题目真的那么难么?我们又该如何破解? lucifer 给大家准备了一个口诀 一个原则, 两种题型,三个注意,四个技巧,让你轻松搞定链表题,再也不怕手撕链表。 我们依次来看下这个口诀的内容。 一个原则一个原则就是 画图,尤其是对于新手来说。不管是简单题还是难题一定要画图,这是贯穿链表题目的一个准则。 画图可以减少我们的认知负担,这其实和打草稿,备忘录道理是一样的,将存在脑子里的东西放到纸上。举一个不太恰当的例子就是你的脑子就是 CPU,脑子的记忆就是寄存器。寄存器的容量有限,我们需要把不那么频繁使用的东西放到内存,把寄存器用在真正该用的地方,这个内存就是纸或者电脑平板等一切你可以画图的东西。 画的好看不好看都不重要,能看清就行了。用笔随便勾画一下, 能看出关系就够了。 两个考点我把力扣的链表做了个遍。发现一个有趣的现象,那就是链表的考点很单一。除了设计类题目,其考点无法就两点: 指针的修改 链表的拼接 指针的修改其中指针修改最典型的就是链表反转。其实链表反转不就是修改指针么? 对于数组这种支持随机访问的数据结构来说, 反转很容易, 只需要头尾不断交换即可。 12345678910function reverseArray(arr) { let left = 0; let right = arr.length - 1; while (left < right) { const temp = arr[left]; arr[left++] = arr[right]; arr[right--] = temp; } return arr;} 而对于链表来说,就没那么容易了。力扣关于反转链表的题简直不要太多了。 今天我给大家写了一个最完整的链表反转,以后碰到可以直接用。当然,前提是大家要先理解再去套。 接下来,我要实现的一个反转任意一段链表 1reverse(self, head: ListNode, tail: ListNode)。 其中 head 指的是需要反转的头节点,tail 是需要反转的尾节点。 不难看出,如果 head 是整个链表的头,tail 是整个链表的尾,那就是反转整个链表,否则就是反转局部链表。接下来,我们就来实现它。 首先,我们要做的就是画图。这个我在一个原则部分讲过了。 如下图,是我们需要反转的部分链表: 而我们期望反转之后的长这个样子: 不难看出, 最终返回 tail 即可。 由于链表的递归性,实际上,我们只要反转其中相邻的两个,剩下的采用同样的方法完成即可。 链表是一种递归的数据结构,因此采用递归的思想去考虑往往事半功倍,关于递归思考链表将在后面《三个注意》部分展开。 对于两个节点来说,我们只需要下修改一次指针即可,这好像不难。 1cur.next = pre 就是这一个操作,不仅硬生生有了环,让你死循环。还让不应该一刀两断的它们分道扬镳。 关于分道扬镳这个不难解决, 我们只需要反转前,记录一下下一个节点即可: 1234next = cur.nextcur.next = precur = next 那么环呢? 实际上, 环不用解决。因为如果我们是从前往后遍历,那么实际上,前面的链表已经被反转了,因此上面我的图是错的。正确的图应该是: 至此为止,我们可以写出如下代码: 1234567891011121314# 翻转一个子链表,并返回新的头与尾def reverse(self, head: ListNode, tail: ListNode): cur = head pre = None while cur != tail: # 留下联系方式 next = cur.next # 修改指针 cur.next = pre # 继续往下走 pre = cur cur = next # 反转后的新的头尾节点返回出去 return tail, head 如果你仔细观察,会发现,我们的 tail 实际上是没有被反转的。解决方法很简单,将 tail 后面的节点作为参数传进来呗。 12345678910111213141516class Solution: # 翻转一个子链表,并且返回新的头与尾 def reverse(self, head: ListNode, tail: ListNode, terminal:ListNode): cur = head pre = None while cur != terminal: # 留下联系方式 next = cur.next # 修改指针 cur.next = pre # 继续往下走 pre = cur cur = next # 反转后的新的头尾节点返回出去 return tail, head 相信你对反转链表已经有了一定的了解。后面我们还会对这个问题做更详细的讲解,大家先留个印象就好。 链表的拼接大家有没有发现链表总喜欢穿来穿去(拼接)的?比如反转链表 II,再比如合并有序链表等。 为啥链表总喜欢穿来穿去呢?实际上,这就是链表存在的价值,这就是设计它的初衷呀! 链表的价值就在于其不必要求物理内存的连续性,以及对插入和删除的友好。这在文章开头的链表和数组的物理结构图就能看出来。 因此链表的题目很多拼接的操作。如果上面我讲的链表基本操作你会了,我相信这难不倒你。除了环,边界 等 。。。 ^_^。 这几个问题我们后面再看。 三个注意链表最容易出错的地方就是我们应该注意的地方。链表最容易出的错 90 % 集中在以下三种情况: 出现了环,造成死循环。 分不清边界,导致边界条件出错。 搞不懂递归怎么做 接下来,我们一一来看。 环环的考点有两个: 题目就有可能环,让你判断是否有环,以及环的位置。 题目链表没环,但是被你操作指针整出环了。 这里我们只讨论第二种,而第一种可以用我们后面提到的快慢指针算法。 避免出现环最简单有效的措施就是画图,如果两个或者几个链表节点构成了环,通过图是很容易看出来的。因此一个简单的实操技巧就是先画图,然后对指针的操作都反应在图中。 但是链表那么长,我不可能全部画出来呀。其实完全不用,上面提到了链表是递归的数据结构, 很多链表问题天生具有递归性,比如反转链表,因此仅仅画出一个子结构就可以了。这个知识,我们放在后面的前后序部分讲解。 边界很多人错的是没有考虑边界。一个考虑边界的技巧就是看题目信息。 如果题目的头节点可能被移除,那么考虑使用虚拟节点,这样头节点就变成了中间节点,就不需要为头节点做特殊判断了。 题目让你返回的不是原本的头节点,而是尾部节点或者其他中间节点,这个时候要注意指针的变化。 以上两者部分的具体内容,我们在稍后讲到的虚拟头部分讲解。老规矩,大家留个印象即可。 前后序ok,是时候填坑了。上面提到了链表结构天生具有递归性,那么使用递归的解法或者递归的思维都会对我们解题有帮助。 在 二叉树遍历 部分,我讲了二叉树的三种流行的遍历方法,分别是前序遍历,中序遍历和后序遍历。 前中后序实际上是指的当前节点相对子节点的处理顺序。如果先处理当前节点再处理子节点,那么就是前序。如果先处理左节点,再处理当前节点,最后处理右节点,就是中序遍历。后序遍历自然是最后处理当前节点了。 实际过程中,我们不会这么扣的这么死。比如: 12345def traverse(root): print('pre') traverse(root.left) traverse(root.righ) print('post') 如上代码,我们既在进入左右节点前有逻辑, 又在退出左右节点之后有逻辑。这算什么遍历方式呢?一般意义上,我习惯只看主逻辑的位置,如果你的主逻辑是在后面就是后序遍历,主逻辑在前面就是前序遍历。 这个不是重点,对我们解题帮助不大,对我们解题帮助大的是接下来要讲的内容。 绝大多数的题目都是单链表,而单链表只有一个后继指针。因此只有前序和后序,没有中序遍历。 还是以上面讲的经典的反转链表来说。 如果是前序遍历,我们的代码是这样的: 12345678def dfs(head, pre): if not head: return pre next = head.next # # 主逻辑(改变指针)在后面 head.next = pre dfs(next, head)dfs(head, None) 后续遍历的代码是这样的: 123456789def dfs(head): if not head or not head.next: return head res = dfs(head.next) # 主逻辑(改变指针)在进入后面的节点的后面,也就是递归返回的过程会执行到 head.next.next = head head.next = None return res 可以看出,这两种写法不管是边界,入参,还是代码都不太一样。为什么会有这样的差异呢? 回答这个问题也不难,大家只要记住一个很简单的一句话就好了,那就是如果是前序遍历,那么你可以想象前面的链表都处理好了,怎么处理的不用管。相应地如果是后序遍历,那么你可以想象后面的链表都处理好了,怎么处理的不用管。这句话的正确性也是毋庸置疑。 如下图,是前序遍历的时候,我们应该画的图。大家把注意力集中在中间的框(子结构)就行了,同时注意两点。 前面的已经处理好了 后面的还没处理好 据此,我们不难写出以下递归代码,代码注释很详细,大家看注释就好了。 123456789def dfs(head, pre): if not head: return pre # 留下联系方式(由于后面的都没处理,因此可以通过 head.next 定位到下一个) next = head.next # 主逻辑(改变指针)在进入后面节点的前面(由于前面的都已经处理好了,因此不会有环) head.next = pre dfs(next, head)dfs(head, None) 如果是后序遍历呢?老规矩,秉承我们的一个原则,先画图。 不难看出,我们可以通过 head.next 拿到下一个元素,然后将下一个元素的 next 指向自身来完成反转。 用代码表示就是: 1head.next.next = head 画出图之后,是不是很容易看出图中有一个环? 现在知道画图的好处了吧?就是这么直观,当你很熟练了,就不需要画了,但是在此之前,请不要偷懒。 因此我们需要将 head.next 改为不会造成环的一个值,比如置空。 12345678910def dfs(head): if not head or not head.next: return head # 不需要留联系方式了,因为我们后面已经走过了,不需走了,现在我们要回去了。 res = dfs(head.next) # 主逻辑(改变指针)在进入后面的节点的后面,也就是递归返回的过程会执行到 head.next.next = head # 置空,防止环的产生 head.next = None return res 值得注意的是,前序遍历很容易改造成迭代,因此推荐大家使用前序遍历。我拿上面的迭代和这里的前序遍历给大家对比一下。 那么为什么前序遍历很容易改造成迭代呢?实际上,这句话我说的不准确,准确地说应该是前序遍历容易改成不需要栈的递归,而后续遍历需要借助栈来完成。这也不难理解,由于后续遍历的主逻辑在函数调用栈的弹出过程,而前序遍历则不需要。 这里给大家插播一个写递归的技巧,那就是想象我们已经处理好了一部分数据,并把他们用手挡起来,但是还有一部分等待处理,接下来思考”如何根据已经处理的数据和当前的数据来推导还没有处理的数据“就行了。 四个技巧针对上面的考点和注意点,我总结了四个技巧来应对,这都是在平时做题中非常实用的技巧。 虚拟头来了解虚拟头的意义之前,先给大家做几个小测验。 Q1: 如下代码 ans.next 指向什么? 1234ans = ListNode(1)ans.next = headhead = head.nexthead = head.next A1: 最开始的 head。 Q2:如下代码 ans.next 指向什么? 1234ans = ListNode(1)head = anshead.next = ListNode(3)head.next = ListNode(4) A2: ListNode(4) 似乎也不难,我们继续看一道题。 Q3: 如下代码 ans.next 指向什么? 12345ans = ListNode(1)head = anshead.next = ListNode(3)head = ListNode(2)head.next = ListNode(4) A3: ListNode(3) 如果三道题你都答对了,那么恭喜你,这一部分可以跳过。 如果你没有懂也没关系,我这里简单解释一下你就懂了。 ans.next 指向什么取决于最后切断 ans.next 指向的地方在哪。比如 Q1,ans.next 指向的是 head,我们假设其指向的内存编号为 9527。 之后执行 head = head.next (ans 和 head 被切断联系了),此时的内存图: 我们假设头节点的 next 指针指向的节点的内存地址为 10200 不难看出,ans 没变。 对于第二个例子。一开始和上面例子一样,都是指向 9527。而后执行了: 12head.next = ListNode(3)head.next = ListNode(4) ans 和 head 又同时指向 ListNode(3) 了。如图: head.next = ListNode(4) 也是同理。因此最终的指向 ans.next 是 ListNode(4)。 我们来看最后一个。前半部分和 Q2 是一样的。 123ans = ListNode(1)head = anshead.next = ListNode(3) 按照上面的分析,此时 head 和 ans 的 next 都指向 ListNode(3)。关键是下面两行: 12head = ListNode(2)head.next = ListNode(4) 指向了 head = ListNode(2) 之后, head 和 ans 的关系就被切断了,当前以及之后所有的 head 操作都不会影响到 ans,因此 ans 还指向被切断前的节点,因此 ans.next 输出的是 ListNode(3)。 花了这么大的篇幅讲这个东西的原因就是,指针操作是链表的核心,如果这些基础不懂, 那么就很难做。接下来,我们介绍主角 - 虚拟头。 相信做过链表的小伙伴都听过这么个名字。为什么它这么好用?它的作用无非就两个: 将头节点变成中间节点,简化判断。 通过在合适的时候断开链接,返回链表的中间节点。 我上面提到了链表的三个注意,有一个是边界。头节点是最常见的边界,那如果我们用一个虚拟头指向头节点,虚拟头就是新的头节点了,而虚拟头不是题目给的节点,不参与运算,因此不需要特殊判断,虚拟头就是这个作用。 如果题目需要返回链表中间的某个节点呢?实际上也可借助虚拟节点。由于我上面提到的指针的操作,实际上,你可以新建一个虚拟头,然后让虚拟头在恰当的时候(刚好指向需要返回的节点)断开连接,这样我们就可以返回虚拟头的 next 就 ok 了。25. K 个一组翻转链表 就用到了这个技巧。 不仅仅是链表, 二叉树等也经常用到这个技巧。 比如我让你返回二叉树的最左下方的节点怎么做?我们也可以利用上面提到的技巧。新建一个虚拟节点,虚拟节点 next 指向当前节点,并跟着一起走,在递归到最左下的时候断开链接,最后返回 虚拟节点的 next 指针即可。 快慢指针判断链表是否有环,以及环的入口都是使用快慢指针即可解决。这种题就是不知道不会,知道了就不容易忘。不多说了,大家可以参考我之前的题解 https://github.com/azl397985856/leetcode/issues/274#issuecomment-573985706 。 除了这个,求链表的交点也是快慢指针,算法也是类似的。不这都属于不知道就难,知道了就容易。且下次写不容易想不到或者出错。 这部分大家参考我上面的题解理一下, 写一道题就可以掌握。接下来,我们来看下穿针引线大法。 另外由于链表不支持随机访问,因此如果想要获取数组中间项和倒数第几项等特定元素就需要一些特殊的手段,而这个手段就是快慢指针。比如要找链表中间项就搞两个指针,一个大步走(一次走两步),一个小步走(一次走一步),这样快指针走到头,慢指针刚好在中间。 如果要求链表倒数第 2 个,那就让快指针先走一步,慢指针再走,这样快指针走到头,慢指针刚好在倒数第二个。这个原理不难理解吧?这种技巧属于会了就容易,且不容易忘。不会就很难想出的类型,因此大家学会了拿几道题练一下就可以放下了。 穿针引线这是链表的第二个考点 - 拼接链表。我在 25. K 个一组翻转链表,61. 旋转链表 和 92. 反转链表 II 都用了这个方法。穿针引线是我自己起的一个名字,起名字的好处就是方便记忆。 这个方法通常不是最优解,但是好理解,方便书写,不易出错,推荐新手用。 还是以反转链表为例,只不过这次是反转链表的中间一部分,那我们该怎么做? 反转前面我们已经讲过了,于是我假设链表已经反转好了,那么如何将反转好的链表拼后去呢? 我们想要的效果是这样的: 那怎么达到图上的效果呢?我的做法是从做到右给断点编号。如图有两个断点,共涉及到四个节点。于是我给它们依次编号为 a,b,c,d。 其实 a,d 分别是需要反转的链表部分的前驱和后继(不参与反转),而 b 和 c 是需要反转的部分的头和尾(参与反转)。 因此除了 cur, 多用两个指针 pre 和 next 即可找到 a,b,c,d。 找到后就简单了,直接穿针引线。 12a.next = cb.next = d 这不就好了么?我记得的就有 25 题,61 题 和 92 题都是这么做的,清晰不混乱。 先穿再排后判空这是四个技巧的最后一个技巧了。虽然是最后讲,但并不意味着它不重要。相反,它的实操价值很大。 继续回到上面讲的链表反转题。 1234567891011cur = headpre = Nonewhile cur != tail: # 留下联系方式 next = cur.next # 修改指针 cur.next = pre # 继续往下走 pre = cur cur = next# 反转后的新的头尾节点返回出去 什么时候需要判断 next 是否存在,上面两行代码先写哪个呢? 是这样? 12next = cur.nextcur.next = pre 还是这样? 12cur.next = prenext = cur.next 先穿我给你的建议是:先穿。这里的穿是修改指针,包括反转链表的修改指针和穿针引线的修改指针。先别管顺序,先穿。 再排穿完之后,代码的总数已经确定了,无非就是排列组合让代码没有 bug。 因此第二步考虑顺序,那上面的两行代码哪个在前?应该是先 next = cur.next ,原因在于后一条语句执行后 cur.next 就变了。由于上面代码的作用是反转,那么其实经过 cur.next = pre 之后链表就断开了,后面的都访问不到了,也就是说此时你只能返回头节点这一个节点。 实际上,有假如有十行穿的代码,我们很多时候没有必要全考虑。我们需要考虑的仅仅是被改变 next 指针的部分。比如 cur.next = pre 的 cur 被改了 next。因此下面用到了 cur.next 的地方就要考虑放哪。其他代码不需要考虑。 后判空和上面的原则类似,穿完之后,代码的总数已经确定了,无非就是看看哪行代码会空指针异常。 和上面的技巧一样,我们很多时候没有必要全考虑。我们需要考虑的仅仅是被改变 next 指针的部分。 比如这样的代码 123while cur: cur = cur.next 我们考虑 cur 是否为空呢? 很明显不可能,因为 while 条件保证了,因此不需判空。 那如何是这样的代码呢? 123while cur: next = cur.next n_next = next.next 如上代码有两个 next,第一个不用判空,上面已经讲了。而第二个是需要的,因为 next 可能是 null。如果 next 是 null ,就会引发空指针异常。因此需要修改为类似这样的代码: 1234while cur: next = cur.next if not next: break n_next = next.next 以上就是我们给大家的四个技巧了。相信有了这四个技巧,写链表题就没那么艰难啦~ ^_^ 题目推荐最后推荐几道题给大家,用今天学到的知识解决它们吧~ 21. 合并两个有序链表 82. 删除排序链表中的重复元素 II 83. 删除排序链表中的重复元素 86. 分隔链表 92. 反转链表 II 138. 复制带随机指针的链表 141. 环形链表 142. 环形链表 II 143. 重排链表 148. 排序链表 206. 反转链表 234. 回文链表 总结数组和栈从逻辑上没有大的区别,你看基本操作都是差不多的。如果是单链表,我们无法在 $O(1)$ 的时间拿到前驱节点,这也是为什么我们遍历的时候老是维护一个前驱节点的原因。但是本质原因其实是链表的增删操作都依赖前驱节点。这是链表的基本操作,是链表的特性天生决定的。 可能有的同学有这样的疑问”考点你只讲了指针的修改和链表拼接,难道说链表就只会这些就够了?那我做的题怎么还需要我会前缀和啥的呢?你是不是坑我呢?“ 我前面说了,所有的数据结构底层都是数组和链表中的一种或两种。而我们这里讲的链表指的是考察链表的基本操作的题目。因此如果题目中需要你使用归并排序去合并链表,那其实归并排序这部分已经不再本文的讨论范围了。 实际上,你去力扣或者其他 OJ 翻链表题会发现他们的链表题大都指的是入参是链表,且你需要对链表进行一些操作的题目。再比如树的题目大多数是入参是树,你需要在树上进行搜索的题目。也就是说需要操作树(比如修改树的指针)的题目很少,比如有一道题让你给树增加一个 right 指针,指向同级的右侧指针,如果已经是最右侧了,则指向空。 链表的基本操作就是增删查,牢记链表的基本操作和复杂度是解决问题的基本。有了这些基本还不够,大家要牢记我的口诀”一个原则,两个考点,三个注意,四个技巧“。 做链表的题,要想入门,无它,唯画图尔。能画出图,并根据图进行操作你就入门了,甭管你写的代码有没有 bug 。 而链表的题目核心的考察点只有两个,一个是指针操作,典型的就是反转。另外一个是链表的拼接。这两个既是链表的精髓,也是主要考点。 知道了考点肯定不够,我们写代码哪些地方容易犯错?要注意什么? 这里我列举了三个容易犯错的地方,分别是环,边界和前后序。 其中环指的是节点之间的相互引用,环的题目如果题目本身就有环, 90 % 双指针可以解决,如果本身没有环,那么环就是我们操作指针的时候留下的。如何解决出现环的问题?那就是画图,然后聚焦子结构,忽略其他信息。 除了环,另外一个容易犯错的地方往往是边界的条件, 而边界这块链表头的判断又是一个大头。克服这点,我们需要认真读题,看题目的要求以及返回值,另外一个很有用的技巧是虚拟节点。 如果大家用递归去解链表的题, 一定要注意自己写的是前序还是后序。 如果是前序,那么只思考子结构即可,前面的已经处理好了,怎么处理的,不用管。非要问,那就是同样方法。后面的也不需考虑如何处理,非要问,那就是用同样方法 如果是后续,那么只思考子结构即可,后面的已经处理好了,怎么处理的,不用管。非要问,那就是同样方法。前面的不需考虑如何处理。非要问,那就是用同样方法 如果你想递归和迭代都写, 我推荐你用前序遍历。因为前序遍历容易改成不用栈的递归。 以上就是链表专题的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 我整理的 1000 多页的电子书已经开发下载了,大家可以去我的公众号《力扣加加》后台回复电子书获取。","categories":[{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/categories/链表/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/tags/链表/"}]},{"title":"单调栈解题模板秒杀八道题","slug":"monotone-stack","date":"2020-11-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.609Z","comments":true,"path":"2020/11/03/monotone-stack/","link":"","permalink":"https://lucifer.ren/blog/2020/11/03/monotone-stack/","excerpt":"顾名思义, 单调栈是一种栈。因此要学单调栈,首先要彻底搞懂栈。","text":"顾名思义, 单调栈是一种栈。因此要学单调栈,首先要彻底搞懂栈。 栈是什么? 栈是一种受限的数据结构, 体现在只允许新的内容从一个方向插入或删除,这个方向我们叫栈顶,而从其他位置获取内容是不被允许的 栈最显著的特征就是 LIFO(Last In, First Out - 后进先出) 举个例子: 栈就像是一个放书本的抽屉,进栈的操作就好比是想抽屉里放一本书,新进去的书永远在最上层,而退栈则相当于从里往外拿书本,永远是从最上层开始拿,所以拿出来的永远是最后进去的哪一个 栈的常用操作 进栈 - push - 将元素放置到栈顶 退栈 - pop - 将栈顶元素弹出 栈顶 - top - 得到栈顶元素的值 是否空栈 - isEmpty - 判断栈内是否有元素 栈的常用操作时间复杂度由于栈只在尾部操作就行了,我们用数组进行模拟的话,可以很容易达到 O(1)的时间复杂度。当然也可以用链表实现,即链式栈。 进栈 - O(1) 出栈 - O(1) 应用 函数调用栈 浏览器前进后退 匹配括号 单调栈用来寻找下一个更大(更小)元素 题目推荐 394. 字符串解码 946. 验证栈序列 1381. 设计一个支持增量操作的栈 单调栈又是什么?单调栈是一种特殊的栈。栈本来就是一种受限的数据结构了,单调栈在此基础上又受限了一次(受限++)。 单调栈要求栈中的元素是单调递增或者单调递减的。 是否严格递减或递减可以根据实际情况来。 这里我用 [a,b,c] 表示一个栈。 其中 左侧为栈底,右侧为栈顶。单调增还是单调减取决于出栈顺序。如果出栈的元素是单调增的,那就是单调递增栈,如果出栈的元素是单调减的,那就是单调递减栈。 比如: [1,2,3,4] 就是一个单调递减栈(因为此时的出栈顺序是 4,3,2,1。下同,不再赘述) [3,2,1] 就是一个单调递增栈 [1,3,2] 就不是一个合法的单调栈 那这个限制有什么用呢?这个限制(特性)能够解决什么用的问题呢? 适用场景单调栈适合的题目是求解下一个大于 xxx或者下一个小于 xxx这种题目。所有当你有这种需求的时候,就应该想到单调栈。 那么为什么单调栈适合求解下一个大于 xxx或者下一个小于 xxx这种题目?原因很简单,我这里通过一个例子给大家讲解一下。 这里举的例子是单调递减栈 比如我们需要依次将数组 [1,3,4,5,2,9,6] 压入单调栈。 首先压入 1,此时的栈为:[1] 继续压入 3,此时的栈为:[1,3] 继续压入 4,此时的栈为:[1,3,4] 继续压入 5,此时的栈为:[1,3,4,5] 如果继续压入 2,此时的栈为:[1,3,4,5,2] 不满足单调递减栈的特性, 因此需要调整。如何调整?由于栈只有 pop 操作,因此我们只好不断 pop,直到满足单调递减为止。 上面其实我们并没有压入 2,而是先 pop,pop 到压入 2 依然可以保持单调递减再 压入 2,此时的栈为:[1,2] 继续压入 9,此时的栈为:[1,2,9] 如果继续压入 6,则不满足单调递减栈的特性, 我们故技重施,不断 pop,直到满足单调递减为止。此时的栈为:[1,2,6] 注意这里的栈仍然是非空的,如果有的题目需要用到所有数组的信息,那么很有可能因没有考虑边界而不能通过所有的测试用例。 这里介绍一个技巧 - 哨兵法,这个技巧经常用在单调栈的算法中。 我们可以在数组前后分别添加一个无限大的数(或者无限小的数)来充当哨兵,起到简化逻辑的作用 对于上面的例子,我可以在原数组 [1,3,4,5,2,9,6] 的右侧添加一个小于数组中最小值的项即可,比如 -1。此时的数组是 [1,3,4,5,2,9,6,-1]。这种技巧可以简化代码逻辑,大家尽量掌握。 上面的例子如果你明白了,就不难理解为啥单调栈适合求解下一个大于 xxx或者下一个小于 xxx这种题目了。比如上面的例子,我们就可以很容易地求出在其之后第一个小于其本身的位置。比如 3 的索引是 1,小于 3 的第一个索引是 4,2 的索引 4,小于 2 的第一个索引是 0,但是其在 2 的索引 4 之后,因此不符合条件,也就是不存在在 2 之后第一个小于 2 本身的位置。 上面的例子,我们在第 6 步开始 pop,第一个被 pop 出来的是 5,因此 5 之后的第一个小于 5 的索引就是 4。同理被 pop 出来的 3,4,5 也都是 4。 如果用 ans 来表示在其之后第一个小于其本身的位置,ans[i] 表示 arr[i] 之后第一个小于 arr[i] 的位置, ans[i] 为 -1 表示这样的位置不存在,比如前文提到的 2。那么此时的 ans 是 [-1,4,4,4,-1,-1,-1]。 第 8 步,我们又开始 pop 了。此时 pop 出来的是 9,因此 9 之后第一个小于 9 的索引就是 6。 这个算法的过程用一句话总结就是,如果压栈之后仍然可以保持单调性,那么直接压。否则先弹出栈的元素,直到压入之后可以保持单调性。这个算法的原理用一句话总结就是,被弹出的元素都是大于当前元素的,并且由于栈是单调减的,因此在其之后小于其本身的最近的就是当前元素了 下面给大家推荐几道题,大家趁着知识还在脑子来,赶紧去刷一下吧~ 伪代码上面的算法可以用如下的伪代码表示,同时这是一个通用的算法模板,大家遇到单调栈的题目可以直接套。 建议大家用自己熟悉的编程语言实现一遍,以后改改符号基本就能用。 12345678910class Solution: def monostoneStack(self, arr: List[int]) -> List[int]: stack = [] ans = 定义一个长度和 arr 一样长的数组,并初始化为 -1 循环 i in arr: while stack and arr[i] > arr[栈顶元素]: peek = 弹出栈顶元素 ans[peek] = i - peek stack.append(i) return ans 复杂度分析 时间复杂度:由于 arr 的元素最多只会入栈,出栈一次,因此时间复杂度仍然是 $O(N)$,其中 N 为数组长度。 空间复杂度:由于使用了栈, 并且栈的长度最大是和 arr 长度一致,因此空间复杂度是 $O(N)$,其中 N 为数组长度。 代码这里提高两种编程语言的单调栈模板供大家参考。 Python3: 12345678910class Solution: def monostoneStack(self, T: List[int]) -> List[int]: stack = [] ans = [0] * len(T) for i in range(len(T)): while stack and T[i] > T[stack[-1]]: peek = stack.pop(-1) ans[peek] = i - peek stack.append(i) return ans JS: 12345678910111213var monostoneStack = function (T) { let stack = []; let result = []; for (let i = 0; i < T.length; i++) { result[i] = 0; while (stack.length > 0 && T[stack[stack.length - 1]] < T[i]) { let peek = stack.pop(); result[peek] = i - peek; } stack.push(i); } return result;}; 单调栈的特性由于单调栈的特性可知, 单调栈适合求解下一个更大的或者下一个更小的数。而实际上,也适合求上一个更大的或者更小的。 因为上一个其实就是 pop 出来后,栈顶的数。下一个其实就是导致你被 pop 出来的那个数。 知道了这一点,大家可以使用 2104. 子数组范围和 练习一下。 题目推荐下面几个题帮助你理解单调栈, 并让你明白什么时候可以用单调栈进行算法优化。 42. 接雨水 84. 柱状图中最大的矩形 739.每日温度 去除重复字母 移掉 K 位数字 下一个更大元素 I 最短无序连续子数组 股票价格跨度 总结单调栈本质就是栈, 栈本身就是一种受限的数据结构。其受限指的是只能在一端进行操作。而单调栈在栈的基础上进一步受限,即要求栈中的元素始终保持单调性。 由于栈中都是单调的,因此其天生适合解决在其之后第一个小于其本身的位置的题目。大家如果遇到题目需要找在其之后第一个小于其本身的位置的题目,就可是考虑使用单调栈。 单调栈的写法相对比较固定,大家可以自己参考我的伪代码自己总结一份模板,以后直接套用可以大大提高做题效率和容错率。","categories":[{"name":"栈","slug":"栈","permalink":"https://lucifer.ren/blog/categories/栈/"}],"tags":[{"name":"栈","slug":"栈","permalink":"https://lucifer.ren/blog/tags/栈/"},{"name":"单调栈","slug":"单调栈","permalink":"https://lucifer.ren/blog/tags/单调栈/"}]},{"title":"前端测试最佳实践(持续更新,建议收藏)","slug":"fe-test-best-practice","date":"2020-11-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.734Z","comments":true,"path":"2020/11/02/fe-test-best-practice/","link":"","permalink":"https://lucifer.ren/blog/2020/11/02/fe-test-best-practice/","excerpt":"最近公司在推行单元测试,但是一些同事对于单元测试只是了解,甚至不怎么了解。因此推动单元测试的阻碍是有的,这种阻碍除了人的层面,还有基础设施的层面。希望通过本文,一方面加深大家对前端测试最佳实践的认知,另一方面可以作为手册,在日常开发中做参考。本文也会不断更新,期待你的参与。 如果大家对前端测试不太清楚,可以先看下文末我写的科普短文。如果你已经对前端测试有所了解,并且希望对前端测试有更深入的了解,以及对如何写出更好的单元测试有兴趣的话,那就让我们开始吧。","text":"最近公司在推行单元测试,但是一些同事对于单元测试只是了解,甚至不怎么了解。因此推动单元测试的阻碍是有的,这种阻碍除了人的层面,还有基础设施的层面。希望通过本文,一方面加深大家对前端测试最佳实践的认知,另一方面可以作为手册,在日常开发中做参考。本文也会不断更新,期待你的参与。 如果大家对前端测试不太清楚,可以先看下文末我写的科普短文。如果你已经对前端测试有所了解,并且希望对前端测试有更深入的了解,以及对如何写出更好的单元测试有兴趣的话,那就让我们开始吧。 写易于测试的代码(Writing test-friendly code)这是一个非常宽泛的话题,本文试图从几个具体的切入点来阐述这个庞大且模糊的话题。 纯函数(Pure Function)关于纯函数可以参考之前我写的一篇函数式教程中的入门篇。 简单来说,纯函数就是数学中的函数。有两个好处: 断言容易了。 (可推导性) 我可以多次,顺序无关地执行测试用例。 (无副作用) 我举一个例子,这是一个稍微高级一点的技巧。不过你一旦理解了其意图,就会发现其思想是多么的简单。 12345678const app = { name: `lucifer's site` start(html) { document.querySelector('#app').innerHTM = html; }}app.start(<div>inner</div>); 上面代码如果要测试,首先你要在 node 环境模拟 document。 如果换一种写法呢? 12345678const app = { name: `lucifer's site` start(querySelector, html) { querySelector('#app').innerHTM = html; }}app.start(document.querySelector, <div>inner</div>); 这样模拟 querySelector 就会变得容易起来。eg: 123// .test.jsimport app from \"./app\";app.start(() => <div id=\"app\">lucifer</div>, <div>inner</div>); 如果你熟悉这种看成方法的话,可能知道它的名字控制反转,英文名 IoC。 单一职责(Single Responsibility Principle)如果一个函数承担了一个以上的职责。那么对我们测试有什么影响呢? 如果对于一个函数 f,其功能有 A 和 B。 A 的输入我们计作 ia,输出计作 oa。 B 的输入我们计作 ib,输出计作 ob。 那么 f 的圈复杂度会增加很多,具体来说。 如果 A 功能和 B 功能相关的话,其测试用例的长度增长是笛卡尔积。 如果 A 功能和 B 功能无关的话,其测试用例的长度增长是线性增长。 eg: 123456function math(a, b, operator) { if (operator === \"+\") return a + b; if (operator === \"-\") return a - b; if (operator === \"*\") return a * b; if (operator === \"/\") return a / b;} 如上代码有四个功能,并且四个功能互相独立。测试用例增长是线性的,也就说将其拆分为四个函数之后,测试用例的数量不变,但是单一函数的圈复杂度降低了,虽然总的软件复杂度并没有降低。 如果四个功能相互耦合的话,后果会更严重。这种情况,拆分多个功能块已经无法解决问题了。这个时候需要对功能进行再次拆解,直到子功能块相互独立。 写清晰直白的测试描述(Wrting Deadly Simply Description)这里我给一个简单的判断标准。 当这个测试报错的时候, 其他人能够只看报错信息,就知道出了什么问题。 比如这样写是好的: 12345describe(`math -> add`, () => { it(\"3 + 2 should equal to 5\", () => { expect(3 + 2).to.be.equal(5); });}); 而这样是不好的: 12345describe(`math -> add`, () => { it(\"add two numbers\", () => { expect(3 + 2).to.be.equal(5); });}); 我举的例子大家可能不屑一顾, 但是当你以我的标准去衡量的时候会发现很多用例都不合格。 逻辑覆盖率(Logic Coverage)很多人关注的是单元测试的物理覆盖率,比如行覆盖率,文件覆盖率等,而大家往往会忽略逻辑覆盖率。 eg: 1234567891011// a.jsexport default (a, b) => a / b// a.test.jsimport divide './a.js'describe(`math -> divide`, () => { it(\"2 / 2 should be 1\", () => { expect(divide(2, 2)).to.be(1); });}); 如上物理覆盖率可以达到 100%,但是很明显逻辑覆盖率却不可以。因为它连最简单的被除数不能为 0 都没包括。 一个更格式的例子,应该是: 123456789101112131415161718192021// a.jsexport default (a, b) => { if (b === 0 or b === -0) throw new Error('dividend should not be zero!') if (Number(a) !== a || Number(b)=== b) throw new Error(`divisor and dividend should be number,but got ${a, b}`) return a / b}// a.test.jsimport divide './a.js'describe(`math -> divide`, () => { it(\"when dividend it zero, there should throw an corresponding eror\", () => { expect(divide(3, 0)).toThrowError(/dividend should not be zero/); }); it(\"when dividend it zero, there should throw an corresponding eror\", () => { expect(divide(3, 'f')).toThrowError(/divisor and dividend should be number/); }); it(\"2 / 2 should be 1\", () => { expect(divide(2, 2)).to.be(1); });}); 逻辑的严密性是双向的,一方面他让你的测试用例更严密,更无懈可击。另一方面你的测试用例越严密, 就越驱使你写出更严密的代码。如上 divide 方法就是我根据测试用例反馈的结果后添加上去的。 然后我上面的测试逻辑上还是很不严密,比如: 没有考虑大数的溢出。 没有考虑无限循环小数。 这么一个简单的除法就有这么多 edge cases,如果是我们实际的业务的话,情况会更加复杂。因此写好测试从来都不是一件简单的事情。 给测试增加 lint(Add Linting)测试代码也是需要 lint 的。除了源码的一些 lint 规则,测试应该要加入一些独特的规则。 比如,你的测试代码只是把代码跑了一遍,没有进行任何断言。亦或者是直接断言expect(true.to.be(true)),都是不应该被允许的。 比如,断言的时候使用非全等,这也不好的实践。 再比如,使用toBeNull()断言,而不是: 12345expect(null).toBe(null);expect(null).toEqual(null);expect(null).toStrictEqual(null); … 类似的例子还有很多,总之测试代码也是需要 lint 的 ,并且相比于被测试代码,其应该有额外的特殊规则,来避免测试代码的腐烂问题。 CI本地测试(Local CI)可以仅对修改的文件进行测试,eg: 1jest -o 分阶段测试(Tags)我们可以按照一定分类标准对测试用例进行分类。 举个例子,我按照测试是否有 IO 将用例分为 IO 类型和 非 IO 类型。那么我就可以在提交的时候只执行非 IO 类型,这样反馈更快。等到我推送到远程的时候执行一次全量操作。 eg: 1234567describe(`\"face swiping\" -> alipay #io`, () => { it(\"it should go to http://www.alipay.com/identify when user choose alipay\", () => { // simulate click // do heavy io // expect });}); 我们可以这么做 1jest -t = \"#io\"; 同样,我可以按照其他纬度对用例进行切分,比如各种业务纬度。这在业务达到一定规模之后,收益非常明显。eg: 1jest -t = "[#io|#cold|#biz]"; 如上会仅测试有io,cold,biz 三个标签中的一个或者多个的用例。 文件夹和文件名本身也是一种 tag,合理利用可以减少很多工作。 框架相关(Framework)大家问的比较多的问题是如何测试视图,以及如何测试特定的某一种框架下的代码。 Vue一个典型的 Vue 项目可能有如下文件类型: html vue js ts json css 图片,音视频等媒体资源 如何对他们进行测试呢?JS 和 TS 我们暂时讨论,这个和框架相关性不大。而我们这里关心框架相关的 vue 文件和视图相关的文件。而json,图片,音视频等媒体资源是没有必要测试的。 那么如何测试 html,vue 和 css 文件呢?而大多数情况, 大家应用都是 CSR 的,html 只是一个傀儡文件,没有测试的价值。css 的话,如果要测试,只有两种情况,一种是对 CSSOM 进行测试,另外一种是对渲染树的内容进行测试。而一般大家都会对渲染树进行测试。为什么呢?留给大家来思考,欢迎文章后留言讨论。因此本文主要讨论 vue 文件,以及渲染树的测试。 实际上, vue 文件会导出一个 vue 的构造函数,并且合适的时候完成实例化和挂载的过程。而其真正渲染到中的时候,会把 template 标签,style 标签内容一并带过去,当然这中间有一些复杂逻辑存在,这不是本文重点,故不做延伸。 那么,对基于 vue 框架的应用测试主要关注一点,渲染树本身。 其实你用别的框架,或者不用框架也是一样的。 不同的是,vue 是一种基于数据驱动的框架。 1(props) => view; 因此我们是不是只要测试不同的 props 组合,是否展示我们期望的 view 就可以了? 是也不是。 我们先假定”是“。那么我们的问题转化为: 如何组合合适的 props 如何断言 view 是否正确渲染 对于第一个问题,这个是组件设计的时候应该考虑的事情。对于第二个问题,答案是 vue-test-utils。 vue-test-utils 本身就是解决这个问题的,如果我将一个 app 看成是组件的有机体(组件以及组件之间的通信协作),并将组件看成函数的话。那么vue-test-utils 的核心功能就是: 帮你执行这些函数。 改变函数内部的状态。 触发函数之间的通信。 。。。 vue-test-utils 的 wrapper 同时完成了上面两件事setProps 和 assert。vue-test-utils 还帮你做了很多事情, 比如组件嵌套(类似函数调用栈)如何测试,怎么 mock props,router 等。 一句话来说,就像是一双无形的手,帮你操作 app 的初始化, 挂载,更新,卸载等,并且直接或者间接提供断言机制。 更多可以参考 https://vue-test-utils.vuejs.org/ 以上内容基于一个事实 我们只要测试不同的 props 组合,是否展示我们期望的 view 就可以。然而, vue 虽然将其抽象为函数,但是要注意这个函数和我上文讲到的纯函数相差甚远,就连以函数式友好闻名的 React 也做不到这一点。 也就是说,你还需要考虑副作用。从这一点上来看,这是和我上文提到的最佳实践背离的。但是真正地将副作用全部抽离开的框架不怎么流行,比如 cyclejs, elm。因此我们必须接受这个事实。我们虽然无法避免这种事情的发生,但是我们可以限制其在我们可控制的范围,典型的技巧就是沙箱机制,这同样超出了本文的论述范围,故不做引申。 ReactTODO 其他(Others)Make it Red, Make it Green其实这就是测试驱动开发的本质。 先写用例,甭管飘红不飘红,先把测试用例写好,定义好问题边界。 然后一个个将红色的变成绿色。 再结合上面我提到的技巧,做持续集成。在你打字的时候可以执行的测试用例有哪些,在你提交到本地仓库的时候可以执行的用例有哪些。 参考(Reference) 两年前写的前端测试短文 eslint-plugin-jest","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"测试","slug":"前端/测试","permalink":"https://lucifer.ren/blog/categories/前端/测试/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"测试","slug":"测试","permalink":"https://lucifer.ren/blog/tags/测试/"},{"name":"单元测试","slug":"单元测试","permalink":"https://lucifer.ren/blog/tags/单元测试/"},{"name":"vue","slug":"vue","permalink":"https://lucifer.ren/blog/tags/vue/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(第二期)","slug":"91-algo-2","date":"2020-10-18T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/10/19/91-algo-2/","link":"","permalink":"https://lucifer.ren/blog/2020/10/19/91-algo-2/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。 活动时间2020-11-01 至 2021-1-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少参与一次打卡 违反上述条件的人员会被强制清退 课程大纲 第一期部分公开的讲义: 【91 算法-基础篇】05.双指针 动态规划问题为什么要画表格? 二期会对题目和讲义进行再次加工,质量会更改, 敬请期待~ 基础篇(30 天) 数组,队列,栈 链表 树与递归 哈希表 双指针 进阶篇(30 天) 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 … 专题篇(31 天) 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 … 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定私有仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 本来计划做一个网站, 后面有一些意外情况, 暂时还是用 Github 私有仓库好了。 第二天会对前一天的题目进行讲解。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书,科学上网兑换码等 连续打卡七天可以获得补签卡一张哦 冲鸭报名开始时间待定。 采用 微信群的方式进行,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 收费标准: 前 50 人免费 51 - 100 收费 5 元 101 - 500 收费 10 元 想要参与的小伙伴加我,发红包拉你进群。 微信号:DevelopeEngineer 需要注意的是,不管你是第几个进群,都需要先发红包才可以进群。只不过你进群之后发现不到 50 人, 可以联系我返现 10 元。大于 50 小于 100 可以找我返现 5 元。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"TypeScript 练习题(第二弹)","slug":"ts-exercises-2","date":"2020-10-12T16:00:00.000Z","updated":"2023-01-07T12:34:34.463Z","comments":true,"path":"2020/10/13/ts-exercises-2/","link":"","permalink":"https://lucifer.ren/blog/2020/10/13/ts-exercises-2/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(第一弹) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(第一弹) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言本文涉及的题目一共十六道,全部都可以在 typescript-exercises 上在线提交。 可以和标准答案进行对比。 并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 想重置进度,清空缓存,无痕模式或者换浏览器都可以。 题目中涉及到的知识点我基本也都在之前的文章中提到了,如果你没有看过,强烈建议先完成前面的教程,然后将上面的题目自己做一遍之后再看本文。另外一定要按照顺序读, 因此前面的题目都是后面的铺垫。 为了不让文章太过于冗长, 本篇文章分两次发布, 一次 8 道题,一共十五道。每道题都有思路,前置知识以及代码。 这次给大家带来的是后 6 道 其中有一道题需要大家有函数式编程的知识, 如果大家不知道会比较难以解释。 为了避免内容太过分散,将这道题从我的题解中移除,故只有 6 道。 题目九题目描述123456789101112131415161718192021222324Intro: PowerUsers idea was bad. Once those users got extended permissions, they started bullying others and we lost a lot of great users. As a response we spent all the remaining money on the marketing and got even more users. We need to start preparing to move everything to a real database. For now we just do some mocks. The server API format was decided to be the following: In case of success: { status: 'success', data: RESPONSE_DATA } In case of error: { status: 'error', error: ERROR_MESSAGE } The API engineer started creating types for this API and quickly figured out that the amount of types needed to be created is too big.Exercise: Remove UsersApiResponse and AdminsApiResponse types and use generic type ApiResponse in order to specify API response formats for each of the functions. 题目的大概意思是:之前都是写死的数据, 现在数据需要从接口拿,请你定义这个接口的类型。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" },];export type ApiResponse<T> = unknown;type AdminsApiResponse = | { status: \"success\"; data: Admin[]; } | { status: \"error\"; error: string; };export function requestAdmins(callback: (response: AdminsApiResponse) => void) { callback({ status: \"success\", data: admins, });}type UsersApiResponse = | { status: \"success\"; data: User[]; } | { status: \"error\"; error: string; };export function requestUsers(callback: (response: UsersApiResponse) => void) { callback({ status: \"success\", data: users, });}export function requestCurrentServerTime( callback: (response: unknown) => void) { callback({ status: \"success\", data: Date.now(), });}export function requestCoffeeMachineQueueLength( callback: (response: unknown) => void) { callback({ status: \"error\", error: \"Numeric value has exceeded Number.MAX_SAFE_INTEGER.\", });}function logPerson(person: Person) { console.log( ` - ${person.name}, ${person.age}, ${ person.type === \"admin\" ? person.role : person.occupation }` );}function startTheApp(callback: (error: Error | null) => void) { requestAdmins((adminsResponse) => { console.log(\"Admins:\"); if (adminsResponse.status === \"success\") { adminsResponse.data.forEach(logPerson); } else { return callback(new Error(adminsResponse.error)); } console.log(); requestUsers((usersResponse) => { console.log(\"Users:\"); if (usersResponse.status === \"success\") { usersResponse.data.forEach(logPerson); } else { return callback(new Error(usersResponse.error)); } console.log(); requestCurrentServerTime((serverTimeResponse) => { console.log(\"Server time:\"); if (serverTimeResponse.status === \"success\") { console.log( ` ${new Date(serverTimeResponse.data).toLocaleString()}` ); } else { return callback(new Error(serverTimeResponse.error)); } console.log(); requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse) => { console.log(\"Coffee machine queue length:\"); if (coffeeMachineQueueLengthResponse.status === \"success\") { console.log(` ${coffeeMachineQueueLengthResponse.data}`); } else { return callback(new Error(coffeeMachineQueueLengthResponse.error)); } callback(null); }); }); }); });}startTheApp((e: Error | null) => { console.log(); if (e) { console.log( `Error: \"${e.message}\", but it's fine, sometimes errors are inevitable.` ); } else { console.log(\"Success!\"); }}); 前置知识 泛型 回调函数 思路我们还是直接看报错。 很明显这个报错的原因是类型是 unknown, 因此我们只有将 unknown 改成正确的类型即可。 换句话说, 就是把这种地方改成正确类型即可。 题目描述说了, 这个 response 其实是从后端返回的。 而后端返回的数据有固定的格式。比如获取用户列表接口: 123456789type UsersApiResponse = | { status: \"success\"; data: User[]; } | { status: \"error\"; error: string; }; 其他接口也是类似, 不同的是 data 的类型。因此我们考虑使用泛型封装,将 data 的类型作为参数即可。 从本质上来说, 就是从后端取的数据有两种大的可能, 一种是错误, 一种是成功。两者在同一接口同一时刻只会出现一个,且必须出现一个。 而成功的情况又会随着接口不同从而可能产生不同的类型。 这是明显的使用 或逻辑关系 和泛型进行类型定义的强烈信号。我们可以使用泛型做如下改造: 123456789export type ApiResponse<T> = | { status: \"success\"; data: T; } | { status: \"error\"; error: string; }; 那么上面的 UsersApiResponse 就可以变成: 1type UsersApiResponse = ApiResponse<User[]>; 不懂的同学建议看下我之前的文章:- 你不知道的 TypeScript 泛型(万字长文,建议收藏) 用同样的套路把其他后端返回加上类型即可。 代码核心代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445export type ApiResponse<T> = | { status: \"success\"; data: T; } | { status: \"error\"; error: string; };export function requestAdmins( callback: (response: ApiResponse<Admin[]>) => void) { callback({ status: \"success\", data: admins, });}export function requestUsers( callback: (response: ApiResponse<User[]>) => void) { callback({ status: \"success\", data: users, });}export function requestCurrentServerTime( callback: (response: ApiResponse<number>) => void) { callback({ status: \"success\", data: Date.now(), });}export function requestCoffeeMachineQueueLength( callback: (response: ApiResponse<number>) => void) { callback({ status: \"error\", error: \"Numeric value has exceeded Number.MAX_SAFE_INTEGER.\", });} 题目十题目描述1234567891011121314151617181920212223242526272829303132Intro: We have asynchronous functions now, advanced technology. This makes us a tech startup officially now. But one of the consultants spoiled our dreams about inevitable future IT leadership. He said that callback-based asynchronicity is not popular anymore and everyone should use Promises. He promised that if we switch to Promises, this would bring promising results.Exercise: We don't want to reimplement all the data-requesting functions. Let's decorate the old callback-based functions with the new Promise-compatible result. The final function should return a Promise which would resolve with the final data directly (i.e. users or admins) or would reject with an error (or type Error). The function should be named promisify.Higher difficulty bonus exercise: Create a function promisifyAll which accepts an object with functions and returns a new object where each of the function is promisified. Rewrite api creation accordingly: const api = promisifyAll(oldApi); 题目大意是:前面用的是基于 callback 形式的代码, 他们对代码进行了重构,改造成了 Promise,让你对基于 Promise 的接口进行类型定义。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" },];export type ApiResponse<T> = | { status: \"success\"; data: T; } | { status: \"error\"; error: string; };export function promisify(arg: unknown): unknown { return null;}const oldApi = { requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) { callback({ status: \"success\", data: admins, }); }, requestUsers(callback: (response: ApiResponse<User[]>) => void) { callback({ status: \"success\", data: users, }); }, requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) { callback({ status: \"success\", data: Date.now(), }); }, requestCoffeeMachineQueueLength( callback: (response: ApiResponse<number>) => void ) { callback({ status: \"error\", error: \"Numeric value has exceeded Number.MAX_SAFE_INTEGER.\", }); },};export const api = { requestAdmins: promisify(oldApi.requestAdmins), requestUsers: promisify(oldApi.requestUsers), requestCurrentServerTime: promisify(oldApi.requestCurrentServerTime), requestCoffeeMachineQueueLength: promisify( oldApi.requestCoffeeMachineQueueLength ),};function logPerson(person: Person) { console.log( ` - ${person.name}, ${person.age}, ${ person.type === \"admin\" ? person.role : person.occupation }` );}async function startTheApp() { console.log(\"Admins:\"); (await api.requestAdmins()).forEach(logPerson); console.log(); console.log(\"Users:\"); (await api.requestUsers()).forEach(logPerson); console.log(); console.log(\"Server time:\"); console.log( ` ${new Date(await api.requestCurrentServerTime()).toLocaleString()}` ); console.log(); console.log(\"Coffee machine queue length:\"); console.log(` ${await api.requestCoffeeMachineQueueLength()}`);}startTheApp().then( () => { console.log(\"Success!\"); }, (e: Error) => { console.log( `Error: \"${e.message}\", but it's fine, sometimes errors are inevitable.` ); }); 前置知识 Promise promisify 泛型 高阶函数 思路题目给了一个 promisefy, 并且类型都是 unknown,不难看出, 它就是想让我们改造 promisefy 使其不报错, 并能正确推导类型。 123export function promisify(arg: unknown): unknown { return null;} 我们先不考虑这个类型怎么写,先把 promiify 实现一下再说。这需要你有一点高阶函数和 promise 的知识。由于这不是本文的重点,因此不赘述。 123456789export function promisify(fn) { return () => new Promise((resolve, reject) => { fn((response) => { if (response.status === \"success\") resolve(response.data); else reject(response.error); }); });} 接下来,我们需要给其增加类型签名。 这个 fn 实际上是一个函数,并且又接受一个 callback 作为参数。 因此大概是这个样子: 1((something) = void) => void 这里的 something 实际上我们在上一节已经解决了,直接套用即可。代码: 1(callback: (response: ApiResponse<T>) => void) => void 整体代码大概是: 12345export function promisify<T>( fn: (callback: (response: ApiResponse<T>) => void) => void): () => Promise<T> { // 上面的实现} 代码核心代码: 1234567891011export function promisify<T>( fn: (callback: (response: ApiResponse<T>) => void) => void): () => Promise<T> { return () => new Promise((resolve, reject) => { fn((response) => { if (response.status === \"success\") resolve(response.data); else reject(response.error); }); });} 第十一题题目描述12345678910111213141516171819Intro: In order to engage users in the communication with each other we have decided to decorate usernames in various ways. A brief search led us to a library called "str-utils". Bad thing is that it lacks TypeScript declarations.Exercise: Check str-utils module implementation at: node_modules/str-utils/index.js node_modules/str-utils/README.md Provide type declaration for that module in: declarations/str-utils/index.d.ts Try to avoid duplicates of type declarations, use type aliases. 题目的意思是他们用到了一个库 str-utils,这个库的人又没给我们写类型定义,于是我们不得不去自己写(好真实的例子啊)。 其实就是让我们实现以下函数的类型签名: 1234567import { strReverse, strToLower, strToUpper, strRandomize, strInvertCase,} from \"str-utils\"; 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081// declarations/str-utils/index.d.jsdeclare module \"str-utils\" { // export const ... // export function ...}// index.tsimport { strReverse, strToLower, strToUpper, strRandomize, strInvertCase,} from \"str-utils\";interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\" }, { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\" }, { type: \"admin\", name: \"Superwoman\", age: 28, role: \"Customer support\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\" }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\" }, { type: \"user\", name: \"Inspector Gadget\", age: 31, occupation: \"Undercover\" },];const isAdmin = (person: Person): person is Admin => person.type === \"admin\";const isUser = (person: Person): person is User => person.type === \"user\";export const nameDecorators = [ strReverse, strToLower, strToUpper, strRandomize, strInvertCase,];function logPerson(person: Person) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } const randomNameDecorator = nameDecorators[Math.round(Math.random() * (nameDecorators.length - 1))]; const name = randomNameDecorator(person.name); console.log(` - ${name}, ${person.age}, ${additionalInformation}`);}([] as Person[]).concat(users, admins).forEach(logPerson);// In case if you are stuck:// https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules 前置知识 如何给缺乏类型定义的第三方库定义类型 思路这个题目的考点就是如何给缺乏类型定义的第三方库定义类型。 这个时候我们只要新建一个文件然后加入以下代码即可。 12345declare module \"str-utils\" { // 在这里定义类型 // export const ... // export function ...} 其中 str-utils 是那个可恶的没有类型定义的库的名字。 有了这个知识,我们的代码就简单了。 代码123456789declare module \"str-utils\" { // export const ... // export function ... export function strReverse(s: string): string; export function strToLower(s: string): string; export function strToUpper(s: string): string; export function strRandomize(s: string): string; export function strInvertCase(s: string): string;} 第十二题题目描述12345678910111213141516171819202122232425262728293031323334Intro: We have so many users and admins in the database! CEO's father Jeff says that we are a BigData startup now. We have no idea what it means, but Jeff says that we need to do some statistics and analytics. We've ran a questionnaire within the team to figure out what do we know about statistics. The only person who filled it was our coffee machine maintainer. The answers were: * Maximums * Minumums * Medians * Averages We found a piece of code on stackoverflow and compiled it into a module `stats`. The bad thing is that it lacks type declarations.Exercise: Check stats module implementation at: node_modules/stats/index.js node_modules/stats/README.md Provide type declaration for that module in: declarations/stats/index.d.tsHigher difficulty bonus exercise: Avoid duplicates of type declarations. 题目大概意思是又来了一个库,这个库又没有写定义,我们又要自己写。 (真实++) 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142// declartions/stats/index.d.tsdeclare module \"stats\" { export function getMaxIndex(input: unknown, comparator: unknown): unknown;}// index.tsimport { getMaxIndex, getMaxElement, getMinIndex, getMinElement, getMedianIndex, getMedianElement, getAverageValue,} from \"stats\";interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\" }, { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\" }, { type: \"admin\", name: \"Superwoman\", age: 28, role: \"Customer support\" },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\" }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\" }, { type: \"user\", name: \"Inspector Gadget\", age: 31, occupation: \"Undercover\" },];function logUser(user: User | null) { if (!user) { console.log(\" - none\"); return; } const pos = users.indexOf(user) + 1; console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`);}function logAdmin(admin: Admin | null) { if (!admin) { console.log(\" - none\"); return; } const pos = admins.indexOf(admin) + 1; console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`);}const compareUsers = (a: User, b: User) => a.age - b.age;const compareAdmins = (a: Admin, b: Admin) => a.age - b.age;const colorizeIndex = (value: number) => String(value + 1);export { getMaxIndex, getMaxElement, getMinIndex, getMinElement, getMedianIndex, getMedianElement, getAverageValue,};console.log(\"Youngest user:\");logUser(getMinElement(users, compareUsers));console.log( ` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register`);console.log();console.log(\"Median user:\");logUser(getMedianElement(users, compareUsers));console.log( ` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register`);console.log();console.log(\"Oldest user:\");logUser(getMaxElement(users, compareUsers));console.log( ` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register`);console.log();console.log(\"Average user age:\");console.log( ` - ${String(getAverageValue(users, ({ age }: User) => age))} years`);console.log();console.log(\"Youngest admin:\");logAdmin(getMinElement(admins, compareAdmins));console.log( ` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register`);console.log();console.log(\"Median admin:\");logAdmin(getMedianElement(admins, compareAdmins));console.log( ` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register`);console.log();console.log(\"Oldest admin:\");logAdmin(getMaxElement(admins, compareAdmins));console.log( ` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register`);console.log();console.log(\"Average admin age:\");console.log( ` - ${String(getAverageValue(admins, ({ age }: Admin) => age))} years`); 前置知识 泛型 高阶函数 如何给缺乏类型定义的第三方库定义类型 思路和上面的思路类似。 唯一的不同的是这道题的需要实现的几个方法支持不同的入参类型。 123456789import { getMaxIndex, getMaxElement, getMinIndex, getMinElement, getMedianIndex, getMedianElement, getAverageValue,} from \"stats\"; 因此,我们考虑使用泛型来定义。 知道了这个, 代码就不难写。 这是最最基本的泛型, 比我们前面写的还简单。 代码123456789101112131415161718192021222324252627282930declare module \"stats\" { export function getMaxIndex<T>( input: T[], comparator: (a: T, b: T) => number ): number; export function getMaxElement<T>( input: T[], comparator: (a: T, b: T) => number ): T; export function getMinElement<T>( input: T[], comparator: (a: T, b: T) => number ): T; export function getMedianIndex<T>( input: T[], comparator: (a: T, b: T) => number ): number; export function getMedianElement<T>( input: T[], comparator: (a: T, b: T) => number ): T; export function getAverageValue<T>( input: T[], getValue: (a: T) => number ): number; export function getMinIndex<T>( input: T[], comparator: (a: T, b: T) => number ): number;} 第十三题题目描述123456789101112131415161718192021222324Intro: The next logical step for us is to provide more precise registration date for our users and admins. We've approximately made up dates for each user and admin and used a library called "date-wizard" in order to pretty-format the dates. Unfortunately, type declarations which came with "date-wizard" library were incomplete. 1. DateDetails interface is missing time related fields such as hours, minutes and seconds. 2. Function "pad" is exported but not declared.Exercise: Check date-wizard module implementation at: node_modules/date-wizard/index.js node_modules/date-wizard/index.d.ts Extend type declaration of that module in: module-augmentations/date-wizard/index.ts 题目大概意思是又来了一个库,这个库又没有写定义,我们又要自己写。 (真实+++++++++++++) 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146// module-augmentations/data-wizard/index.d.ts// This enables module augmentation mode.import \"date-wizard\";declare module \"date-wizard\" { // Add your module extensions here.}// index.tsimport * as dateWizard from \"date-wizard\";import \"./module-augmentations/date-wizard\";interface User { type: \"user\"; name: string; age: number; occupation: string; registered: Date;}interface Admin { type: \"admin\"; name: string; age: number; role: string; registered: Date;}type Person = User | Admin;const admins: Admin[] = [ { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\", registered: new Date(\"2016-06-01T16:23:13\"), }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\", registered: new Date(\"2017-02-11T12:12:11\"), }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\", registered: new Date(\"2018-01-05T11:02:30\"), }, { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\", registered: new Date(\"2018-08-12T10:01:24\"), }, { type: \"admin\", name: \"Superwoman\", age: 28, role: \"Customer support\", registered: new Date(\"2019-03-25T07:51:05\"), },];const users: User[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", registered: new Date(\"2016-02-15T09:25:13\"), }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", registered: new Date(\"2016-03-23T12:47:03\"), }, { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\", registered: new Date(\"2017-02-19T17:22:56\"), }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\", registered: new Date(\"2018-02-25T19:44:28\"), }, { type: \"user\", name: \"Inspector Gadget\", age: 31, occupation: \"Undercover\", registered: new Date(\"2019-03-25T09:29:12\"), },];const isAdmin = (person: Person): person is Admin => person.type === \"admin\";const isUser = (person: Person): person is User => person.type === \"user\";function logPerson(person: Person, index: number) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } let registeredAt = dateWizard( person.registered, \"{date}.{month}.{year} {hours}:{minutes}\" ); let num = `#${dateWizard.pad(index + 1)}`; console.log( ` - ${num}: ${person.name}, ${person.age}, ${additionalInformation}, ${registeredAt}` );}export { dateWizard };console.log(\"All users:\");([] as Person[]).concat(users, admins).forEach(logPerson);console.log();console.log(\"Early birds:\");([] as Person[]) .concat(users, admins) .filter((person) => dateWizard.dateDetails(person.registered).hours < 10) .forEach(logPerson);// In case if you are stuck:// https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules// https://www.typescriptlang.org/docs/handbook/declaration-merging.html 前置知识 interface 或 type 声明自定义类型 如何给缺乏类型定义的第三方库定义类型 思路和上面两道题思路一样, 不用多说了吧? 代码1234567891011121314151617// This enables module augmentation mode.import \"date-wizard\";declare module \"date-wizard\" { // Add your module extensions here. function dateWizard(date: string, format: string): string; function pad(s: number): string; interface DateDetails { year: number; month: number; date: number; hours: number; minutes: number; seconds: number; } function dateDetails(date: Date): DateDetails;} 第十四题需要大家有函数式编程的知识, 如果大家不知道会比较难以解释。 为了避免内容太过分散,将这道题从我的题解中移除。 对函数式编程感兴趣的,也可是看下我之前写的文章 函数式编程系列教程。 第十五题题目描述123456789101112131415161718192021222324Intro: Our attempt to Open Source didn't work quite as expected. It turned out there were already many existing functional JS libraries. All the remaining developers left the company as well. It seems that they are joining a very ambitious startup which re-invented a juicer and raised millions of dollars. Too bad we cannot compete with this kind of financing even though we believe our idea is great. It's time to shine for the last time and publish our new invention: object-constructor as our CTO named it. A small library which helps manipulating an object.Exercise: Here is a library which helps manipulating objects. We tried to write type annotations and we failed. Please help! 题目大概意思是函数式编程他们 hold 不住,于是又准备切换到面向对象编程。 于是你需要补充类型定义使得代码不报错。 题目内置代码123456789101112131415161718192021export class ObjectManipulator { constructor(protected obj) {} public set(key, value) { return new ObjectManipulator({ ...this.obj, [key]: value }); } public get(key) { return this.obj[key]; } public delete(key) { const newObj = { ...this.obj }; delete newObj[key]; return new ObjectManipulator(newObj); } public getObject() { return this.obj; }} 前置知识 泛型 Omit 泛型 ES6 class keyof 使用 extends 进行泛型约束 联合类型 思路这道题难度颇高,比前面的泛型题目都要难。 也是本系列的压轴题,我们重点讲一下。 首先题目有五个报错位置, 报错信息都是隐式使用了 any , 因此我们的思路就是将五个地方显式声明类型即可。 从它的名字 ObjectManipulator 以及 api 可以看出, 它应该可以存储任何对象,因此使用泛型定义就不难想到。 你也可是把这个 ObjectManipulator 想象成抽象包包。 你的期望是限量款包包拍照的时候用,普通包包和闺蜜逛街的时候用,优衣库送的包包逛超市的时候用等等。 ObjectManipulator 是一个抽象的包包概念,不是具体的包, 比如当你买一个 LV 的包包的时候就是 ObjectManipulator<LVBag>。这样当你往 LV 里放超市买的水果的时候就可以报错:你怎么可以用 LV 包包装这样东西呢?你应该用 ta 装*。 当然这个例子很不严谨, 这个只是帮助大家快速理解而已,切莫较真。 理解了题意,我们就可以开始写了。 我们先改第一个错 - 构造函数 constructor, 这个错比较简单。 123456export class ObjectManipulator<T> { constructor(protected obj: T) { this.obj = obj; } ...} 这个时候经过 ObjectManipulator 实例化产生的对象的 this.obj 都是 T 类型,其中 T 是泛型。因此 getObject 的错也不难改,返回值写 T 就行。 123456export class ObjectManipulator<T> { ... public getObject(): T { return this.obj; }} 剩下的 get,set 和 delete 思路有点类似。 先拿 get 来说: 1234567export class ObjectManipulator<T> { ... public get(key) { return this.obj[key]; } ...} 这个怎么写类型呢? key 理论上可是是任何值,返回值理论上也可以是任何值。但是一旦类型 T 确定了, 那么实际上 key 和返回值就不是任意值了。 比如: 12type A = ObjectManipulator<{ name: string; age: number }>;const a: A = new ObjectManipulator({ name: \"\", age: 17 }); 如上代码中的 A 是 ObjectManipulator 传入具体类型 { name: string; age: number } 产生的新的类型。 我这里用的是行内类型, 实际项目建议使用 interface 或者 type 定义类型。 之后我们模拟一些操作: 123456a.set(\"name\", \"脑洞前端\");a.get(\"name\");a.get(\"name123\"); // 期望报错a.set(\"name123\", \"脑洞\");a.delete(\"name123\"); // 期望报错a.delete(\"name\"); 实际上,我可能期望的是其中一些行为可以借助 TypeScript 的类型分析直接报错。 简单来说,我的期望是 get 和 delete 不在 T 中的 key 都报错。 当然你的真实项目也可以不认同我的观点, 比如 get 一个不在 T 中定义的 key 也可以,但是我还是推荐你这么做。 知道了这个, 再结合我之前有关泛型的文章就不难写出来。 其中 get 和 delete 的代码: 1234567891011export class ObjectManipulator<T> { public get<K extends keyof T>(key: K): T[K] { return this.obj[key]; } public delete<K extends keyof T>(key: K): ObjectManipulator<Omit<T, K>> { const newObj = { ...this.obj }; delete newObj[key]; return new ObjectManipulator(newObj); }} 最后是 set,其实一开始我的 set 是这么写的。 12345678export class ObjectManipulator<T> { public set<K extends keyof T, V>(key: K, value: V): ObjectManipulator<T> { return new ObjectManipulator({ ...this.obj, [key]: value, }) as ObjectManipulator<T & { [k in K]: V }>; }} 但是无奈没有通过官方的测试用例。 实际项目我其实更推荐我上面的这种写法。下面是我为了通过所有的测试用例写的方法。 经过分析, 我发现它期望的是 set 中的 key 可以不是 T 中的。这一点从官方给的测试用例就可以看出来。 因此我将代码改成 K 放宽到任意 string,返回值做了一个联合类型。代码: 12345678910111213export class ObjectManipulator<T> { ... public set<K extends string, V>( key: K, value: V ): ObjectManipulator<T & { [k in K]: V }> { return new ObjectManipulator({ ...this.obj, [key]: value, }) as ObjectManipulator<T & { [k in K]: V }>; } ...} 终于通过了所有的测试用例。 代码12345678910111213141516171819202122232425262728export class ObjectManipulator<T> { constructor(protected obj: T) { this.obj = obj; } public set<K extends string, V>( key: K, value: V ): ObjectManipulator<T & { [k in K]: V }> { return new ObjectManipulator({ ...this.obj, [key]: value, }) as ObjectManipulator<T & { [k in K]: V }>; } public get<K extends keyof T>(key: K): T[K] { return this.obj[key]; } public delete<K extends keyof T>(key: K): ObjectManipulator<Omit<T, K>> { const newObj = { ...this.obj }; delete newObj[key]; return new ObjectManipulator(newObj); } public getObject(): T { return this.obj; }} 总结以上就是给大家带来的题目解析。 这六道题的考点有,按照我个人理解的重要程度划分为: type 和 interface 的基本操作(必须掌握) 如何给缺乏类型定义的第三方库定义类型(必须掌握) 联合类型 和 交叉类型(强烈建议掌握) 类型断言和类型收缩(强烈建议掌握) 泛型和常见内置泛型(强烈建议掌握) 高阶函数的类型定义(强烈建议掌握) 最后祝愿大家告别 anyscript,成为 TypeScript 魔法师。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"西法喊你参加模拟面试~","slug":"mock-interview","date":"2020-10-06T16:00:00.000Z","updated":"2023-01-05T12:24:50.072Z","comments":true,"path":"2020/10/07/mock-interview/","link":"","permalink":"https://lucifer.ren/blog/2020/10/07/mock-interview/","excerpt":"很多粉丝向西法我反应:做了很多题。看到新的题目还是不会, 看了题解之后又觉得自己会,但是自己写又写不出来。 这个现象实际上很常见, 破局的最有效方法就是多做题。 是真正地做出来,而不是看了会了,要自己从零 coding。 我之前讲过对于新手的建议是按 tag 刷, 对于老手或者马上要面试的我建议随机刷。 今天我在补充一句,那就是不管大家用哪种方式刷,建议大家都通过模拟面试或者竞赛的形式刷。 时间条件允许的可以参加周赛,不允许的则可以模拟面试。","text":"很多粉丝向西法我反应:做了很多题。看到新的题目还是不会, 看了题解之后又觉得自己会,但是自己写又写不出来。 这个现象实际上很常见, 破局的最有效方法就是多做题。 是真正地做出来,而不是看了会了,要自己从零 coding。 我之前讲过对于新手的建议是按 tag 刷, 对于老手或者马上要面试的我建议随机刷。 今天我在补充一句,那就是不管大家用哪种方式刷,建议大家都通过模拟面试或者竞赛的形式刷。 时间条件允许的可以参加周赛,不允许的则可以模拟面试。 如果你是新手, 可以按照 tag 自定义模拟面试, 否则可以随机或者针对公司模拟面试。 今天做了力扣几套模拟面试的题目,入口在力扣主页上方的面试导航。如果没有 plus 会员的话,可以随机模拟面试,题目随机抽取。如果你有会员则可以选择特定公司的真题进行模拟。由于有时间限制, 如果你在准备面试的话, 提前一个月每天做一套题,锻炼自己的解题能力。 如果你没有 plus ,还想白嫖真题模拟面试也不是不可以。 你可以找个知道哪些题是哪个公司的人,然后自定义模拟面试。 知道哪些题是哪个公司的人,网上有一些好心人贡献,将某一个公司的题目做成一个集合供大家免费查看。我的力扣刷题插件也做了一部分, 目前还不完全,不嫌弃的也可以用我的刷题插件看。 插件获取方式: 关注公众号力扣加加,回复插件即可。 另外国际版的力扣有一个讨论专区,里面的题目质量和可信度都很高。 地址:https://leetcode.com/discuss/interview-question?currentPage=1&orderBy=hot&query= 要求你在限定时间解决三道题目,难度是不确定的,有可能两道简单,也可能两道困难。 模拟面试中, 大家也可以练习一些可耻但是管用的小技巧。 比如打表: 如上是一个经典的打表解法,如果没有人工判卷,算是一个可耻但有用的技巧。 其他鸡贼技巧,西法有时间再给大家整理。 最后,之后的模拟面试考虑使用力扣的模拟面试形式进行。如果想参加我的模拟面试的,可以加我的模拟面试群,然后群里预约即可。加群方式,关注公众号力扣加加,回复模拟面试。","categories":[{"name":"模拟面试","slug":"模拟面试","permalink":"https://lucifer.ren/blog/categories/模拟面试/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"模拟面试","slug":"模拟面试","permalink":"https://lucifer.ren/blog/tags/模拟面试/"}]},{"title":"你不知道的前端异常处理(万字长文,建议收藏)","slug":"error-catch","date":"2020-10-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.726Z","comments":true,"path":"2020/10/02/error-catch/","link":"","permalink":"https://lucifer.ren/blog/2020/10/02/error-catch/","excerpt":"除了调试,处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道,就好像我们天天和 Bug 打交道一样。因此正确认识异常,并作出合适的异常处理就显得很重要了。 我们先尝试抛开前端这个限定条件,来看下更广泛意义上程序的报错以及异常处理。不管是什么语言,都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常,并针对其做相应的异常处理。 然而,很多人对异常的处理方式是事后修补,即某个异常发生的时候,增加对应的条件判断,这真的是一种非常低效的开发方式,非常不推荐大家这么做。那么究竟如何正确处理异常呢?由于不同语言有不同的特性,因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就前端异常进行详细阐述,但是读者也可以稍加修改延伸到其他各个领域。 本文讨论的异常指的是软件异常,而非硬件异常。","text":"除了调试,处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道,就好像我们天天和 Bug 打交道一样。因此正确认识异常,并作出合适的异常处理就显得很重要了。 我们先尝试抛开前端这个限定条件,来看下更广泛意义上程序的报错以及异常处理。不管是什么语言,都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常,并针对其做相应的异常处理。 然而,很多人对异常的处理方式是事后修补,即某个异常发生的时候,增加对应的条件判断,这真的是一种非常低效的开发方式,非常不推荐大家这么做。那么究竟如何正确处理异常呢?由于不同语言有不同的特性,因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就前端异常进行详细阐述,但是读者也可以稍加修改延伸到其他各个领域。 本文讨论的异常指的是软件异常,而非硬件异常。 什么是异常用直白的话来解释异常的话,就是程序发生了意想不到的情况,这种情况影响到了程序的正确运行。 从根本上来说,异常就是一个数据结构,其保存了异常发生的相关信息,比如错误码,错误信息等。以 JS 中的标准内置对象 Error 为例,其标准属性有 name 和 message。然而不同的浏览器厂商有自己的自定义属性,这些属性并不通用。比如 Mozilla 浏览器就增加了 filename 和 stack 等属性。 值得注意的是错误只有被抛出,才会产生异常,不被抛出的错误不会产生异常。比如: 123456function t() { console.log(\"start\"); new Error(); console.log(\"end\");}t(); (动画演示) 这段代码不会产生任何的异常,控制台也不会有任何错误输出。 异常的分类按照产生异常时程序是否正在运行,我们可以将错误分为编译时异常和运行时异常。 编译时异常指的是源代码在编译成可执行代码之前产生的异常。而运行时异常指的是可执行代码被装载到内存中执行之后产生的异常。 编译时异常我们知道 TS 最终会被编译成 JS,从而在 JS Runtime中执行。既然存在编译,就有可能编译失败,就会有编译时异常。 比如我使用 TS 写出了如下代码: 1const s: string = 123; 这很明显是错误的代码, 我给 s 声明了 string 类型,但是却给它赋值 number。 当我使用 tsc(typescript 编译工具,全称是 typescript compiler)尝试编译这个文件的时候会有异常抛出: 12345678tsc a.tsa.ts:1:7 - error TS2322: Type '123' is not assignable to type 'string'.1 const s: string = 123; ~Found 1 error. 这个异常就是编译时异常,因为我的代码还没有执行。 然而并不是你用了 TS 才存在编译时异常,JS 同样有编译时异常。有的人可能会问 JS 不是解释性语言么?是边解释边执行,没有编译环节,怎么会有编译时异常? 别急,我举个例子你就明白了。如下代码: 123456function t() { console.log('start') await sa console.log('end')}t() 上面的代码由于存在语法错误,不会编译通过,因此并不会打印start,侧面证明了这是一个编译时异常。尽管 JS 是解释语言,也依然存在编译阶段,这是必然的,因此自然也会有编译异常。 总的来说,编译异常可以在代码被编译成最终代码前被发现,因此对我们的伤害更小。接下来,看一下令人心生畏惧的运行时异常。 运行时异常相信大家对运行时异常非常熟悉。这恐怕是广大前端碰到最多的异常类型了。众所周知的 NPE(Null Pointer Exception) 就是运行时异常。 将上面的例子稍加改造,得到下面代码: 123456function t() { console.log(\"start\"); throw 1; console.log(\"end\");}t(); (动画演示) 注意 end 没有打印,并且 t 没有弹出栈。实际上 t 最终还是会被弹出的,只不过和普通的返回不一样。 如上,则会打印出start。由于异常是在代码运行过程中抛出的,因此这个异常属于运行时异常。相对于编译时异常,这种异常更加难以发现。上面的例子可能比较简单,但是如果我的异常是隐藏在某一个流程控制语句(比如 if else)里面呢?程序就可能在客户的电脑走入那个抛出异常的 if 语句,而在你的电脑走入另一条。这就是著名的 《在我电脑上好好的》 事件。 异常的传播异常的传播和我之前写的浏览器事件模型有很大的相似性。只不过那个是作用在 DOM 这样的数据结构,这个则是作用在函数调用栈这种数据结构,并且事件传播存在捕获阶段,异常传播是没有的。不同 C 语言,JS 中异常传播是自动的,不需要程序员手动地一层层传递。如果一个异常没有被 catch,它会沿着函数调用栈一层层传播直到栈空。 异常处理中有两个关键词,它们是throw(抛出异常) 和 catch(处理异常)。 当一个异常被抛出的时候,异常的传播就开始了。异常会不断传播直到遇到第一个 catch。 如果程序员没有手动 catch,那么一般而言程序会抛出类似unCaughtError,表示发生了一个异常,并且这个异常没有被程序中的任何 catch 语言处理。未被捕获的异常通常会被打印在控制台上,里面有详细的堆栈信息,从而帮助程序员快速排查问题。实际上我们的程序的目标是避免 unCaughtError这种异常,而不是一般性的异常。 一点小前提由于 JS 的 Error 对象没有 code 属性,只能根据 message 来呈现,不是很方便。我这里进行了简单的扩展,后面很多地方我用的都是自己扩展的 Error ,而不是原生 JS Error ,不再赘述。 123456oldError = Error;Error = function ({ code, message, fileName, lineNumber }) { error = new oldError(message, fileName, lineNumber); error.code = code; return error;}; 手动抛出 or 自动抛出异常既可以由程序员自己手动抛出,也可以由程序自动抛出。 1throw new Error(`I'm Exception`); (手动抛出的例子) 12a = null;a.toString(); // Thrown: TypeError: Cannot read property 'toString' of null (程序自动抛出的例子) 自动抛出异常很好理解,毕竟我们哪个程序员没有看到过程序自动抛出的异常呢? “这个异常突然就跳出来!吓我一跳!”,某不知名程序员如是说。 那什么时候应该手动抛出异常呢? 一个指导原则就是你已经预知到程序不能正确进行下去了。比如我们要实现除法,首先我们要考虑的是被除数为 0 的情况。当被除数为 0 的时候,我们应该怎么办呢?是抛出异常,还是 return 一个特殊值?答案是都可以,你自己能区分就行,这没有一个严格的参考标准。 我们先来看下抛出异常,告诉调用者你的输入,我处理不了这种情况。 12345678910111213141516171819function divide(a, b) { a = +a; b = +b; // 转化成数字 if (!b) { // 匹配 +0, -0, NaN throw new Error({ code: 1, message: \"Invalid dividend \" + b, }); } if (Number.isNaN(a)) { // 匹配 NaN throw new Error({ code: 2, message: \"Invalid divisor \" + a, }); } return a / b;} 上面代码会在两种情况下抛出异常,告诉调用者你的输入我处理不了。由于这两个异常都是程序员自动手动抛出的,因此是可预知的异常。 刚才说了,我们也可以通过返回值来区分异常输入。我们来看下返回值输入是什么,以及和异常有什么关系。 异常 or 返回如果是基于异常形式(遇到不能处理的输入就抛出异常)。当别的代码调用divide的时候,需要自己 catch。 12345678910111213function t() { try { divide(\"foo\", \"bar\"); } catch (err) { if (err.code === 1) { return console.log(\"被除数必须是除0之外的数\"); } if (err.code === 2) { return console.log(\"除数必须是数字\"); } throw new Error(\"不可预知的错误\"); }} 然而就像上面我说的那样,divide 函数设计的时候,也完全可以不用异常,而是使用返回值来区分。 12345678910111213141516171819function divide(a, b) { a = +a; b = +b; // 转化成数字 if (!b) { // 匹配 +0, -0, NaN return new Error({ code: 1, message: \"Invalid dividend \" + b, }); } if (Number.isNaN(a)) { // 匹配 NaN return new Error({ code: 2, message: \"Invalid divisor \" + a, }); } return a / b;} 当然,我们使用方式也要作出相应改变。 1234567891011function t() { const res = divide(\"foo\", \"bar\"); if (res.code === 1) { return console.log(\"被除数必须是除0之外的数\"); } if (res.code === 2) { return console.log(\"除数必须是数字\"); } return new Error(\"不可预知的错误\");} 这种函数设计方式和抛出异常的设计方式从功能上说都是一样的,只是告诉调用方的方式不同。如果你选择第二种方式,而不是抛出异常,那么实际上需要调用方书写额外的代码,用来区分正常情况和异常情况,这并不是一种良好的编程习惯。 然而在 Go 等返回值可以为复数的语言中,我们无需使用上面蹩脚的方式,而是可以: 1234res, err := divide(\"foo\", \"bar\");if err != nil { log.Fatal(err)} 这是和 Java 和 JS 等语言使用的 try catch 不一样的的地方,Go 是通过 panic recover defer 机制来进行异常处理的。感兴趣的可以去看看 Go 源码关于错误测试部分 可能大家对 Go 不太熟悉。没关系,我们来继续看下 shell。实际上 shell 也是通过返回值来处理异常的,我们可以通过 $? 拿到上一个命令的返回值,这本质上也是一种调用栈的传播行为,而且是通过返回值而不是捕获来处理异常的。 作为函数返回值处理和 try catch 一样,这是语言的设计者和开发者共同决定的一件事情。 上面提到了异常传播是作用在函数调用栈上的。当一个异常发生的时候,其会沿着函数调用栈逐层返回,直到第一个 catch 语句。当然 catch 语句内部仍然可以触发异常(自动或者手动)。如果 catch 语句内部发生了异常,也一样会沿着其函数调用栈继续执行上述逻辑,专业术语是 stack unwinding。 实际上并不是所有的语言都会进行 stack unwinding,这个我们会在接下来的《运行时异常可以恢复么?》部分讲解。 伪代码来描述一下: 12345678function bubble(error, fn) { if (fn.hasCatchBlock()) { runCatchCode(error); } if (callstack.isNotEmpty()) { bubble(error, callstack.pop()); }} 从我的伪代码可以看出所谓的 stack unwinding 其实就是 callstack.pop() 这就是异常传播的一切!仅此而已。 异常的处理我们已经了解来异常的传播方式了。那么接下来的问题是,我们应该如何在这个传播过程中处理异常呢? 我们来看一个简单的例子: 12345678910function a() { b();}function b() { c();}function c() { throw new Error(\"an error occured\");}a(); 我们将上面的代码放到 chrome 中执行, 会在控制台显示如下输出: 我们可以清楚地看出函数的调用关系。即错误是在 c 中发生的,而 c 是 b 调用的,b 是 a 调用的。这个函数调用栈是为了方便开发者定位问题而存在的。 上面的代码,我们并没有 catch 错误,因此上面才会有uncaught Error。 那么如果我们 catch ,会发生什么样的变化呢?catch 的位置会对结果产生什么样的影响?在 a ,b,c 中 catch 的效果是一样的么? 我们来分别看下: 1234567891011121314function a() { b();}function b() { c();}function c() { try { throw new Error(\"an error occured\"); } catch (err) { console.log(err); }}a(); (在 c 中 catch) 我们将上面的代码放到 chrome 中执行, 会在控制台显示如下输出: 可以看出,此时已经没有uncaught Error啦,仅仅在控制台显示了标准输出,而非错误输出(因为我用的是 console.log,而不是 console.error)。然而更重要是的是,如果我们没有 catch,那么后面的同步代码将不会执行。 比如在 c 的 throw 下面增加一行代码,这行代码是无法被执行的,无论这个错误有没有被捕获。 12345678function c() { try { throw new Error(\"an error occured\"); console.log(\"will never run\"); } catch (err) { console.log(err); }} 我们将 catch 移动到 b 中试试看。 123456789101112131415function a() { b();}function b() { try { c(); } catch (err) { console.log(err); }}function c() { throw new Error(\"an error occured\");}a(); (在 b 中 catch) 在这个例子中,和上面在 c 中捕获没有什么本质不同。其实放到 a 中捕获也是一样,这里不再贴代码了,感兴趣的自己试下。 既然处于函数调用栈顶部的函数报错, 其函数调用栈下方的任意函数都可以进行捕获,并且效果没有本质不同。那么问题来了,我到底应该在哪里进行错误处理呢? 答案是责任链模式。我们先来简单介绍一下责任链模式,不过细节不会在这里展开。 假如 lucifer 要请假。 如果请假天数小于等于 1 天,则主管同意即可 如果请假大于 1 天,但是小于等于三天,则需要 CTO 同意。 如果请假天数大于三天,则需要老板同意。 这就是一个典型的责任链模式。谁有责任干什么事情是确定的,不要做自己能力范围之外的事情。比如主管不要去同意大于 1 天的审批。 举个例子,假设我们的应用有三个异常处理类,它们分别是:用户输入错误,网络错误 和 类型错误。如下代码,当代码执行的时候会报错一个用户输入异常。这个异常没有被 C 捕获,会 unwind stack 到 b,而 b 中 catch 到这个错误之后,通过查看 code 值判断其可以被处理,于是打印I can handle this。 123456789101112131415161718192021222324252627282930function a() { try { b(); } catch (err) { if (err.code === \"NETWORK_ERROR\") { return console.log(\"I can handle this\"); } // can't handle, pass it down throw err; }}function b() { try { c(); } catch (err) { if (err.code === \"INPUT_ERROR\") { return console.log(\"I can handle this\"); } // can't handle, pass it down throw err; }}function c() { throw new Error({ code: \"INPUT_ERROR\", message: \"an error occured\", });}a(); 而如果 c 中抛出的是别的异常,比如网络异常,那么 b 是无法处理的,虽然 b catch 住了,但是由于你无法处理,因此一个好的做法是继续抛出异常,而不是吞没异常。不要畏惧错误,抛出它。只有没有被捕获的异常才是可怕的,如果一个错误可以被捕获并得到正确处理,它就不可怕。 举个例子: 12345678910111213141516171819202122232425262728function a() { try { b(); } catch (err) { if (err.code === \"NETWORK_ERROR\") { return console.log(\"I can handle this\"); } // can't handle, pass it down throw err; }}function b() { try { c(); } catch (err) { if (err.code === \"INPUT_ERROR\") { return console.log(\"I can handle this\"); } }}function c() { throw new Error({ code: \"NETWORK_ERROR\", message: \"an error occured\", });}a(); 如上代码不会有任何异常被抛出,它被完全吞没了,这对我们调试问题简直是灾难。因此切记不要吞没你不能处理的异常。正确的做法应该是上面讲的那种只 catch 你可以处理的异常,而将你不能处理的异常 throw 出来,这就是责任链模式的典型应用。 这只是一个简单的例子,就足以绕半天。实际业务肯定比这个复杂多得多。因此异常处理绝对不是一件容易的事情。 如果说谁来处理是一件困难的事情,那么在异步中决定谁来处理异常就是难上加难,我们来看下。 同步与异步同步异步一直是前端难以跨越的坎,对于异常处理也是一样。以 NodeJS 中用的比较多的读取文件 API 为例。它有两个版本,一个是异步,一个是同步。同步读取仅仅应该被用在没了这个文件无法进行下去的时候。比如读取一个配置文件。而不应该在比如浏览器中读取用户磁盘上的一个图片等,这样会造成主线程阻塞,导致浏览器卡死。 1234// 同步读取文件fs.readFileSync();// 异步读取文件fs.readFile(); 当我们试图同步读取一个不存在的文件的时候,会抛出以下异常: 1234567891011fs.readFileSync('something-not-exist.lucifer');console.log('脑洞前端');Thrown:Error: ENOENT: no such file or directory, open 'something-not-exist.lucifer' at Object.openSync (fs.js:446:3) at Object.readFileSync (fs.js:348:35) { errno: -2, syscall: 'open', code: 'ENOENT', path: 'something-not-exist.lucifer'} 并且脑洞前端是不会被打印出来的。这个比较好理解,我们上面已经解释过了。 而如果以异步方式的话: 123456789101112fs.readFile('something-not-exist.lucifer', (err, data) => {if(err) {throw err}});console.log('lucifer')luciferundefinedThrown:[Error: ENOENT: no such file or directory, open 'something-not-exist.lucifer'] { errno: -2, code: 'ENOENT', syscall: 'open', path: 'something-not-exist.lucifer'}> 脑洞前端是会被打印出来的。 其本质在于 fs.readFile 的函数调用已经成功,并从调用栈返回并执行到下一行的console.log('lucifer')。因此错误发生的时候,调用栈是空的,这一点可以从上面的错误堆栈信息中看出来。 不明白为什么调用栈是空的同学可以看下我之前写的《一文看懂浏览器事件循环》 而 try catch 的作用仅仅是捕获当前调用栈的错误(上面异常传播部分已经讲过了)。因此异步的错误是无法捕获的,比如; 123456789try { fs.readFile(\"something-not-exist.lucifer\", (err, data) => { if (err) { throw err; } });} catch (err) { console.log(\"catching an error\");} 上面的 catching an error 不会被打印。因为错误抛出的时候, 调用栈中不包含这个 catch 语句,而仅仅在执行fs.readFile的时候才会。 如果我们换成同步读取文件的例子看看: 12345try { fs.readFileSync(\"something-not-exist.lucifer\");} catch (err) { console.log(\"catching an error\");} 上面的代码会打印 catching an error。因为读取文件被同步发起,文件返回之前线程会被挂起,当线程恢复执行的时候, fs.readFileSync 仍然在函数调用栈中,因此 fs.readFileSync 产生的异常会冒泡到 catch 语句。 简单来说就是异步产生的错误不能用 try catch 捕获,而要使用回调捕获。 可能有人会问了,我见过用 try catch 捕获异步异常啊。 比如: 123456789101112131415rejectIn = (ms) => new Promise((_, r) => { setTimeout(() => { r(1); }, ms); });async function t() { try { await rejectIn(0); } catch (err) { console.log(\"catching an error\", err); }}t(); 本质上这只是一个语法糖,是 Promise.prototype.catch 的一个语法糖而已。而这一语法糖能够成立的原因在于其用了 Promise 这种包装类型。如果你不用包装类型,比如上面的 fs.readFile 不用 Promise 等包装类型包装,打死都不能用 try catch 捕获。 而如果我们使用 babel 转义下,会发现 try catch 不见了,变成了 switch case 语句。这就是 try catch “可以捕获异步异常”的原因,仅此而已,没有更多。 (babel 转义结果) 我使用的 babel 转义环境都记录在这里,大家可以直接点开链接查看. 虽然浏览器并不像 babel 转义这般实现,但是至少我们明白了一点。目前的 try catch 的作用机制是无法捕获异步异常的。 异步的错误处理推荐使用容器包装,比如 Promise。然后使用 catch 进行处理。实际上 Promise 的 catch 和 try catch 的 catch 有很多相似的地方,大家可以类比过去。 和同步处理一样,很多原则都是通用的。比如异步也不要去吞没异常。下面的代码是不好的,因为它吞没了它不能处理的异常。 12p = Promise.reject(1);p.catch(() => {}); 更合适的做法的应该是类似这种: 1234567p = Promise.reject(1);p.catch((err) => { if (err == 1) { return console.log(\"I can handle this\"); } throw err;}); 彻底消除运行时异常可能么?我个人对目前前端现状最为头疼的一点是:大家过分依赖运行时,而严重忽略编译时。我见过很多程序,你如果不运行,根本不知道程序是怎么走的,每个变量的 shape 是什么。怪不得处处都可以看到 console.log。我相信你一定对此感同身受。也许你就是那个写出这种代码的人,也许你是给别人擦屁股的人。为什么会这样? 就是因为大家太依赖运行时。TS 的出现很大程度上改善了这一点,前提是你用的是 typescript,而不是 anyscript。其实 eslint 以及 stylint 对此也有贡献,毕竟它们都是静态分析工具。 我强烈建议将异常保留在编译时,而不是运行时。不妨极端一点来看:假如所有的异常都在编译时发生,而一定不会在运行时发生。那么我们是不是就可以信心满满地对应用进行重构啦? 幸运的是,我们能够做到。只不过如果当前语言做不到的话,则需要对现有的语言体系进行改造。这种改造成本真的很大。不仅仅是 API,编程模型也发生了翻天覆地的变化,不然函数式也不会这么多年没有得到普及了。 不熟悉函数编程的可以看看我之前写的函数式编程入门篇。 如果才能彻底消除异常呢?在回答这个问题之前,我们先来看下一门号称没有运行时异常的语言 elm。elm 是一门可以编译为 JS 的函数式编程语言,其封装了诸如网络 IO 等副作用,是一种声明式可推导的语言。 有趣的是,elm 也有异常处理。 elm 中关于异常处理(Error Handling)部分有两个小节的内容,分别是:Maybe 和 Result。elm 之所以没有运行时异常的一个原因就是它们。 一句话概括“为什么 elm 没有异常”的话,那就是elm 把异常看作数据(data)。 举个简单的例子: 12345678maybeResolveOrNot = (ms) => setTimeout(() => { if (Math.random() > 0.5) { console.log(\"ok\"); } else { throw new Error(\"error\"); } }); 上面的代码有一半的可能报错。那么在 elm 中就不允许这样的情况发生。所有的可能发生异常的代码都会被强制包装一层容器,这个容器在这里是 Maybe。 在其他函数式编程语言名字可能有所不同,但是意义相同。实际上,不仅仅是异常,正常的数据也会被包装到容器中,你需要通过容器的接口来获取数据。如果难以理解的话,你可以将其简单理解为 Promsie(但并不完全等价)。 Maybe 可能返回正常的数据 data,也可能会生成一个错误 error。某一个时刻只能是其中一个,并且只有运行的时候,我们才真正知道它是什么。从这一点来看,有点像薛定谔的猫。 不过 Maybe 已经完全考虑到异常的存在,一切都在它的掌握之中。所有的异常都能够在编译时推导出来。当然要想推导出这些东西,你需要对整个编程模型做一定的封装会抽象,比如 DOM 就不能直接用了,而是需要一个中间层。 再来看下一个更普遍的例子 NPE: 1null.toString(); elm 也不会发生。原因也很简单,因为 null 也会被包装起来,当你通过这个包装类型就行访问的时候,容器有能力避免这种情况,因此就可以不会发生异常。当然这里有一个很重要的前提就是可推导,而这正是函数式编程语言的特性。这部分内容超出了本文的讨论范围,不再这里说了。 运行时异常可以恢复么?最后要讨论的一个主题是运行时异常是否可以恢复。先来解释一下,什么是运行时异常的恢复。 还是用上面的例子: 123456function t() { console.log(\"start\"); throw 1; console.log(\"end\");}t(); 这个我们已经知道了, end 是不会打印的。 尽管你这么写也是无济于事: 12345678910function t() { try { console.log(\"start\"); throw 1; console.log(\"end\"); } catch (err) { console.log(\"relax, I can handle this\"); }}t(); 如果我想让它打印呢?我想让程序面对异常可以自己 recover 怎么办?我已经捕获这个错误, 并且我确信我可以处理,让流程继续走下去吧!如果有能力做到这个,这个就是运行时异常恢复。 遗憾地告诉你,据我所知,目前没有任何一个引擎能够做到这一点。 这个例子过于简单, 只能帮助我们理解什么是运行时异常恢复,但是不足以让我们看出这有什么用? 我们来看一个更加复杂的例子,我们这里直接使用上面实现过的函数divide。 1234567891011121314function t() { try { const res = divide(\"foo\", \"bar\"); alert(`you got ${res}`); } catch (err) { if (err.code === 1) { return console.log(\"被除数必须是除0之外的数\"); } if (err.code === 2) { return console.log(\"除数必须是数字\"); } throw new Error(\"不可预知的错误\"); }} 如上代码,会进入 catch ,而不会 alert。因此对于用户来说, 应用程序是没有任何响应的。这是不可接受的。 要吐槽一点的是这种事情真的是挺常见的,只不过大家用的不是 alert 罢了。 如果我们的代码在进入 catch 之后还能够继续返回出错位置继续执行就好了。 如何实现异常中断的恢复呢?我刚刚说了:据我所知,目前没有任何一个引擎能够做到异常恢复。那么我就来发明一个新的语法解决这个问题。 12345678910function t() { try { const res = divide(\"foo\", \"bar\"); alert(`you got ${res}`); } catch (err) { console.log(\"releax, I can handle this\"); resume - 1; }}t(); 上面的 resume 是我定义的一个关键字,功能是如果遇到异常,则返回到异常发生的地方,然后给当前发生异常的函数一个返回值 -1,并使得后续代码能够正常运行,不受影响。这其实是一种 fallback。 这绝对是一个超前的理念。当然挑战也非常大,对现有的体系冲击很大,很多东西都要改。我希望社区可以考虑把这个东西加到标准。 最佳实践通过前面的学习,你已经知道了异常是什么,异常是怎么产生的,以及如何正确处理异常(同步和异步)。接下来,我们谈一下异常处理的最佳实践。 我们平时开发一个应用。 如果站在生产者和消费者的角度来看的话。当我们使用别人封装的框架,库,模块,甚至是函数的时候,我们就是消费者。而当我们写的东西被他人使用的时候,我们就是生产者。 实际上,就算是生产者内部也会有多个模块构成,多个模块之间也会有生产者和消费者的再次身份转化。不过为了简单起见,本文不考虑这种关系。这里的生产者指的就是给他人使用的功能,是纯粹的生产者。 从这个角度出发,来看下异常处理的最佳实践。 作为消费者当作为消费者的时候,我们关心的是使用的功能是否会抛出异常,如果是,他们有哪些异常。比如: 123456import foo from \"lucifer\";try { foo.bar();} catch (err) { // 有哪些异常?} 当然,理论上 foo.bar 可能产生任何异常,而不管它的 API 是这么写的。但是我们关心的是可预期的异常。因此你一定希望这个时候有一个 API 文档,详细列举了这个 API 可能产生的异常有哪些。 比如这个 foo.bar 4 种可能的异常 分别是 A,B,C 和 D。其中 A 和 B 是我可以处理的,而 C 和 D 是我不能处理的。那么我应该: 123456789101112import foo from \"lucifer\";try { foo.bar();} catch (err) { if (err.code === \"A\") { return console.log(\"A happened\"); } if (err.code === \"B\") { return console.log(\"B happened\"); } throw err;} 可以看出,不管是 C 和 D,还是 API 中没有列举的各种可能异常,我们的做法都是直接抛出。 作为生产者如果你作为生产者,你要做的就是提供上面提到的详细的 API,告诉消费者你的可能错误有哪些。这样消费者就可以在 catch 中进行相应判断,处理异常情况。 你可以提供类似上图的错误表,让大家可以很快知道可能存在的可预知异常有哪些。不得不吐槽一句,在这一方面很多框架,库做的都很差。希望大家可以重视起来,努力维护良好的前端开发大环境。 总结本文很长,如果你能耐心看完,你真得给可以给自己鼓个掌 👏👏👏。 我从什么是异常,以及异常的分类,让大家正确认识异常,简单来说异常就是一种数据结构而已。 接着,我又讲到了异常的传播和处理。这两个部分是紧密联系的。异常的传播和事件传播没有本质不同,主要不同是数据结构不同,思想是类似的。具体来说异常会从发生错误的调用处,沿着调用栈回退,直到第一个 catch 语句或者栈为空。如果栈为空都没有碰到一个 catch,则会抛出uncaught Error。 需要特别注意的是异步的异常处理,不过你如果对我讲的原理了解了,这都不是事。 然后,我提出了两个脑洞问题: 彻底消除运行时异常可能么? 运行时异常可以恢复么? 这两个问题非常值得研究,但由于篇幅原因,我这里只是给你讲个轮廓而已。如果你对这两个话题感兴趣,可以和我交流。 最后,我提到了前端异常处理的最佳实践。大家通过两种角色(生产者和消费者)的转换,认识一下不同决定关注点以及承担责任的不同。具体来说提到了 明确声明可能的异常以及 处理你应该处理的,不要吞没你不能处理的异常。当然这个最佳实践仍然是轮廓性的。如果大家想要一份 前端最佳实践 checklist,可以给我留言。留言人数较多的话,我考虑专门写一个前端最佳实践 checklist 类型的文章。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"异常处理","slug":"异常处理","permalink":"https://lucifer.ren/blog/tags/异常处理/"}]},{"title":"【西法带你学算法】一次搞定前缀和","slug":"atMostK","date":"2020-09-26T16:00:00.000Z","updated":"2023-01-07T12:29:14.687Z","comments":true,"path":"2020/09/27/atMostK/","link":"","permalink":"https://lucifer.ren/blog/2020/09/27/atMostK/","excerpt":"本文是前缀和专题第一篇。系列目录如下: 一维前缀和(本文) 二维前缀和 我花了几天时间,从力扣中精选了五道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 467. 环绕字符串中唯一的子字符串(中等) 795. 区间子数组个数(中等) 904. 水果成篮(中等) 992. K 个不同整数的子数组(困难) 1109. 航班预订统计(中等) 前四道题都是滑动窗口的子类型,我们知道滑动窗口适合在题目要求连续的情况下使用, 而前缀和也是如此。二者在连续问题中,对于优化时间复杂度有着很重要的意义。 因此如果一道题你可以用暴力解决出来,而且题目恰好有连续的限制, 那么滑动窗口和前缀和等技巧就应该被想到。 除了这几道题, 还有很多题目都是类似的套路, 大家可以在学习过程中进行体会。今天我们就来一起学习一下。","text":"本文是前缀和专题第一篇。系列目录如下: 一维前缀和(本文) 二维前缀和 我花了几天时间,从力扣中精选了五道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。 467. 环绕字符串中唯一的子字符串(中等) 795. 区间子数组个数(中等) 904. 水果成篮(中等) 992. K 个不同整数的子数组(困难) 1109. 航班预订统计(中等) 前四道题都是滑动窗口的子类型,我们知道滑动窗口适合在题目要求连续的情况下使用, 而前缀和也是如此。二者在连续问题中,对于优化时间复杂度有着很重要的意义。 因此如果一道题你可以用暴力解决出来,而且题目恰好有连续的限制, 那么滑动窗口和前缀和等技巧就应该被想到。 除了这几道题, 还有很多题目都是类似的套路, 大家可以在学习过程中进行体会。今天我们就来一起学习一下。 前菜我们从一个简单的问题入手,识别一下这种题的基本形式和套路,为之后的四道题打基础。当你了解了这个套路之后, 之后做这种题就可以直接套。 需要注意的是这四道题的前置知识都是 滑动窗口, 不熟悉的同学可以先看下我之前写的 滑动窗口专题(思路 + 模板) 母题 0有 N 个的正整数放到数组 A 里,现在要求一个新的数组 B,新数组的第 i 个数 B[i]是原数组 A 第 0 到第 i 个数的和。 这道题可以使用前缀和来解决。 前缀和是一种重要的预处理,能大大降低查询的时间复杂度。我们可以简单理解前缀和为“数列的前 n 项的和”。 这个概念其实很容易理解,即一个数组中,第 n 位存储的是数组前 n 个数字的和。 例如,对 [1,2,3,4,5,6] 来说,其前缀和就是 pre=[1,3,6,10,15,21]。 我们可以使用公式 pre[𝑖]=pre[𝑖−1]+nums[𝑖]得到每一位前缀和的值,从而通过前缀和进行相应的计算和解题。如果想得到某一个连续区间[l,r]的和则可以通过 pre[r] - pre[l-1] 取得,不过这里 l 需要 > 0。我们可以加一个特殊判断,如果 l = 0,区间 [l,r] 的和就是 pre[r]。 我们也可以在前缀和首项前面添加一个 0 省去这种特殊判断,算是一个小技巧吧。 其实前缀和的概念很简单,但困难的是如何在题目中使用前缀和以及如何使用前缀和的关系来进行解题。 母题 1如果让你求一个数组的连续子数组总个数,你会如何求?其中连续指的是数组的索引连续。 比如 [1,3,4],其连续子数组有:[1], [3], [4], [1,3], [3,4] , [1,3,4],你需要返回 6。 一种思路是总的连续子数组个数等于:以索引为 0 结尾的子数组个数 + 以索引为 1 结尾的子数组个数 + … + 以索引为 n - 1 结尾的子数组个数,这无疑是完备的。 同时利用母题 0 的前缀和思路, 边遍历边求和。 参考代码(JS): 123456789function countSubArray(nums) { let ans = 0; let pre = 0; for (_ in nums) { pre += 1; ans += pre; } return ans;} 复杂度分析 时间复杂度:$O(N)$,其中 N 为数组长度。 空间复杂度:$O(1)$ 而由于以索引为 i 结尾的子数组个数就是 i + 1,因此这道题可以直接用等差数列求和公式 (1 + n) * n / 2,其中 n 数组长度。 母题 2我继续修改下题目, 如果让你求一个数组相邻差为 1 连续子数组的总个数呢?(如果只有一个数,那么我们也认为其实一个相邻差为 1 连续子数组)其实就是索引差 1 的同时,值也差 1。 和上面思路类似,无非就是增加差值的判断。 参考代码(JS): 1234567891011121314function countSubArray(nums) { let ans = 1; let pre = 1; for (let i = 1; i < nums.length; i++) { if (nums[i] - nums[i - 1] == 1) { pre += 1; } else { pre = 1; } ans += pre; } return ans;} 复杂度分析 时间复杂度:$O(N)$,其中 N 为数组长度。 空间复杂度:$O(1)$ 如果我值差只要大于 1 就行呢?其实改下符号就行了,这不就是求上升子序列个数么?这里不再继续赘述, 大家可以自己试试。 母题 3我们继续扩展。 如果我让你求出不大于 k 的子数组的个数呢?不大于 k 指的是子数组的全部元素都不大于 k。 比如 [1,3,4] 子数组有 [1], [3], [4], [1,3], [3,4] , [1,3,4],不大于 3 的子数组有 [1], [3], [1,3] ,那么 [1,3,4] 不大于 3 的子数组个数就是 3。 实现函数 atMostK(k, nums)。 参考代码(JS): 1234567891011121314function countSubArray(k, nums) { let ans = 0; let pre = 0; for (let i = 0; i < nums.length; i++) { if (nums[i] <= k) { pre += 1; } else { pre = 0; } ans += pre; } return ans;} 复杂度分析 时间复杂度:$O(N)$,其中 N 为数组长度。 空间复杂度:$O(1)$ 母题 4如果我让你求出子数组最大值刚好是 k 的子数组的个数呢? 比如 [1,3,4] 子数组有 [1], [3], [4], [1,3], [3,4] , [1,3,4],子数组最大值刚好是 3 的子数组有 [3], [1,3] ,那么 [1,3,4] 子数组最大值刚好是 3 的子数组个数就是 2。实现函数 exactK(k, nums)。 实际上是 exactK 可以直接利用 atMostK,即 atMostK(k) - atMostK(k - 1),原因见下方母题 5 部分。 母题 5如果我让你求出子数组最大值刚好是 介于 k1 和 k2 的子数组的个数呢?实现函数 betweenK(k1, k2, nums)。 实际上是 betweenK 可以直接利用 atMostK,即 atMostK(k1, nums) - atMostK(k2 - 1, nums),其中 k1 > k2。前提是值是离散的, 比如上面我出的题都是整数。 因此我可以直接 减 1,因为 1 是两个整数最小的间隔。 如上,小于等于 10 的区域减去 小于 5 的区域就是 大于等于 5 且小于等于 10 的区域。 注意我说的是小于 5, 不是小于等于 5。 由于整数是离散的,最小间隔是 1。因此小于 5 在这里就等价于 小于等于 4。这就是 betweenK(k1, k2, nums) = atMostK(k1) - atMostK(k2 - 1) 的原因。 因此不难看出 exactK 其实就是 betweenK 的特殊形式。 当 k1 == k2 的时候, betweenK 等价于 exactK。 因此 atMostK 就是灵魂方法,一定要掌握,不明白建议多看几遍。 前缀和的使用场景上面是前缀和的基本概念以及简单使用。这里,我们总结一下前缀和的常见使用场景。大家碰到类似场景,不妨思考一下是否可通过前缀和以空间换时间的方式解决。 差分(后面我们要讲的 1109. 航班预订统计 就使用了这个技巧) 前缀和与二分。如果 nums 是一个正整数数组,那么其前缀和一定是单调递增的,有时候可以利用这个性质做二分。 求区间内的 1 的个数。比如给你一个数组 nums,让你求任意区间的 1 的个数。那么就可以先预处理,比如将 1 以外的数字预处理为 0,然后做前缀和,最后做差求区间和,这样区间和就是 1 的个数。(比如 1871. 跳跃游戏 VII 就利用了这个技巧) 区间值计数。上面是对 nums 区间本身进行统计。如果我想求 nums 的值(注意是值,不是索引)在 [lower,upper] 之间的数有多少,怎么求呢?我们可以开辟一个与 nums 值域大小等大的数组,并对值进行计数(即统计 nums 的值的出现频率),接下来就和普通前缀和一样了。(比如 1862. 向下取整数对和 就利用了这个技巧) 区间值计数扩展。上面的区间值计数没有对索引进行限制,那如果我加了索引的限制呢?比如我想求索引在 [l,r] 并且值在 [lower,upper]的值的个数如何求?我们可以先做一个二维前缀和 pre[i][j] 表示 前 i 项 j 的出现次数(显然 pre 的规模为数组长度乘以数组值域大小),接下来依次枚举 [lower,upper]的所有数 cur,并利用 pre[r][cur] - pre[l-1][cur],将结果进行累加即可。(比如1906. 查询差绝对值的最小值 就使用了这个技巧) 有了上面的铺垫, 我们来看下第一道题。 467. 环绕字符串中唯一的子字符串(中等)题目描述123456789101112131415161718192021222324252627把字符串 s 看作是“abcdefghijklmnopqrstuvwxyz”的无限环绕字符串,所以 s 看起来是这样的:"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....". 现在我们有了另一个字符串 p 。你需要的是找出 s 中有多少个唯一的 p 的非空子串,尤其是当你的输入是字符串 p ,你需要输出字符串 s 中 p 的不同的非空子串的数目。 注意: p 仅由小写的英文字母组成,p 的大小可能超过 10000。 示例 1:输入: "a"输出: 1解释: 字符串 S 中只有一个"a"子字符。 示例 2:输入: "cac"输出: 2解释: 字符串 S 中的字符串“cac”只有两个子串“a”、“c”。. 示例 3:输入: "zab"输出: 6解释: 在字符串 S 中有六个子串“z”、“a”、“b”、“za”、“ab”、“zab”。. 前置知识 滑动窗口 思路题目是让我们找 p 在 s 中出现的非空子串数目,而 s 是固定的一个无限循环字符串。由于 p 的数据范围是 10^5 ,因此暴力找出所有子串就需要 10^10 次操作了,应该会超时。而且题目很多信息都没用到,肯定不对。 仔细看下题目发现,这不就是母题 2 的变种么?话不多说, 直接上代码,看看有多像。 为了减少判断, 我这里用了一个黑科技, p 前面加了个 ^。 123456789101112class Solution: def findSubstringInWraproundString(self, p: str) -> int: p = '^' + p w = 1 ans = 0 for i in range(1,len(p)): if ord(p[i])-ord(p[i-1]) == 1 or ord(p[i])-ord(p[i-1]) == -25: w += 1 else: w = 1 ans += w return ans 如上代码是有问题。 比如 cac会被计算为 3,实际上应该是 2。根本原因在于 c 被错误地计算了两次。因此一个简单的思路就是用 set 记录一下访问过的子字符串即可。比如: 123456{ c, abc, ab, abcd} 而由于 set 中的元素一定是连续的,因此上面的数据也可以用 hashmap 存: 12345{ c: 3 d: 4 b: 1} 含义是: 以 b 结尾的子串最大长度为 1,也就是 b。 以 c 结尾的子串最大长度为 3,也就是 abc。 以 d 结尾的子串最大长度为 4,也就是 abcd。 至于 c ,是没有必要存的。我们可以通过母题 2 的方式算出来。 具体算法: 定义一个 len_mapper。key 是 字母, value 是 长度。 含义是以 key 结尾的最长连续子串的长度。 关键字是:最长 用一个变量 w 记录连续子串的长度,遍历过程根据 w 的值更新 len_mapper 返回 len_mapper 中所有 value 的和。 比如: abc,此时的 len_mapper 为: 12345{ c: 3 b: 2 a: 1} 再比如:abcab,此时的 len_mapper 依旧。 再比如: abcazabc,此时的 len_mapper: 123456{ c: 4 b: 3 a: 2 z: 1} 这就得到了去重的目的。这种算法是不重不漏的,因为最长的连续子串一定是包含了比它短的连续子串,这个思想和 1297. 子串的最大出现次数 剪枝的方法有异曲同工之妙。 代码(Python)123456789101112class Solution: def findSubstringInWraproundString(self, p: str) -> int: p = '^' + p len_mapper = collections.defaultdict(lambda: 0) w = 1 for i in range(1,len(p)): if ord(p[i])-ord(p[i-1]) == 1 or ord(p[i])-ord(p[i-1]) == -25: w += 1 else: w = 1 len_mapper[p[i]] = max(len_mapper[p[i]], w) return sum(len_mapper.values()) 复杂度分析 时间复杂度:$O(N)$,其中 $N$ 为字符串 p 的长度。 空间复杂度:由于最多存储 26 个字母, 因此空间实际上是常数,故空间复杂度为 $O(1)$。 795. 区间子数组个数(中等)题目描述12345678910111213141516给定一个元素都是正整数的数组 A ,正整数 L 以及 R (L <= R)。求连续、非空且其中最大元素满足大于等于 L 小于等于 R 的子数组个数。例如 :输入:A = [2, 1, 4, 3]L = 2R = 3输出: 3解释: 满足条件的子数组: [2], [2, 1], [3].注意:L, R 和 A[i] 都是整数,范围在 [0, 10^9]。数组 A 的长度范围在[1, 50000]。 前置知识 滑动窗口 思路由母题 5,我们知道 betweenK 可以直接利用 atMostK,即 atMostK(k1) - atMostK(k2 - 1),其中 k1 > k2。 由母题 2,我们知道如何求满足一定条件(这里是元素都小于等于 R)子数组的个数。 这两个结合一下, 就可以解决。 代码(Python) 代码是不是很像 1234567891011class Solution: def numSubarrayBoundedMax(self, A: List[int], L: int, R: int) -> int: def notGreater(R): ans = cnt = 0 for a in A: if a <= R: cnt += 1 else: cnt = 0 ans += cnt return ans return notGreater(R) - notGreater(L - 1) _复杂度分析_ 时间复杂度:$O(N)$,其中 $N$ 为数组长度。 空间复杂度:$O(1)$。 904. 水果成篮(中等)题目描述123456789101112131415161718192021222324252627282930313233343536373839404142在一排树中,第 i 棵树产生 tree[i] 型的水果。你可以从你选择的任何树开始,然后重复执行以下步骤:把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。移动到当前树右侧的下一棵树。如果右边没有树,就停下来。请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。用这个程序你能收集的水果树的最大总量是多少? 示例 1:输入:[1,2,1]输出:3解释:我们可以收集 [1,2,1]。示例 2:输入:[0,1,2,2]输出:3解释:我们可以收集 [1,2,2]如果我们从第一棵树开始,我们将只能收集到 [0, 1]。示例 3:输入:[1,2,3,2,2]输出:4解释:我们可以收集 [2,3,2,2]如果我们从第一棵树开始,我们将只能收集到 [1, 2]。示例 4:输入:[3,3,3,1,2,1,1,2,3,3,4]输出:5解释:我们可以收集 [1,2,1,1,2]如果我们从第一棵树或第八棵树开始,我们将只能收集到 4 棵水果树。 提示:1 <= tree.length <= 400000 <= tree[i] < tree.length 前置知识 滑动窗口 思路题目花里胡哨的。我们来抽象一下,就是给你一个数组, 让你选定一个子数组, 这个子数组最多只有两种数字,这个选定的子数组最大可以是多少。 这不就和母题 3 一样么?只不过 k 变成了固定值 2。另外由于题目要求整个窗口最多两种数字,我们用哈希表存一下不就好了吗? set 是不行了的。 因此我们不但需要知道几个数字在窗口, 我们还要知道每个数字出现的次数,这样才可以使用滑动窗口优化时间复杂度。 代码(Python)12345678910111213141516class Solution: def totalFruit(self, tree: List[int]) -> int: def atMostK(k, nums): i = ans = 0 win = defaultdict(lambda: 0) for j in range(len(nums)): if win[nums[j]] == 0: k -= 1 win[nums[j]] += 1 while k < 0: win[nums[i]] -= 1 if win[nums[i]] == 0: k += 1 i += 1 ans = max(ans, j - i + 1) return ans return atMostK(2, tree) 复杂度分析 时间复杂度:$O(N)$,其中 $N$ 为数组长度。 空间复杂度:$O(k)$。 992. K 个不同整数的子数组(困难)题目描述12345678910111213141516171819202122232425给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定独立的子数组为好子数组。(例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。)返回 A 中好子数组的数目。 示例 1:输入:A = [1,2,1,2,3], K = 2输出:7解释:恰好由 2 个不同整数组成的子数组:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].示例 2:输入:A = [1,2,1,3,4], K = 3输出:3解释:恰好由 3 个不同整数组成的子数组:[1,2,1,3], [2,1,3], [1,3,4]. 提示:1 <= A.length <= 200001 <= A[i] <= A.length1 <= K <= A.length 前置知识 滑动窗口 思路由母题 5,知:exactK = atMostK(k) - atMostK(k - 1), 因此答案便呼之欲出了。其他部分和上面的题目 904. 水果成篮 一样。 实际上和所有的滑动窗口题目都差不多。 代码(Python)123456789101112131415161718class Solution: def subarraysWithKDistinct(self, A, K): return self.atMostK(A, K) - self.atMostK(A, K - 1) def atMostK(self, A, K): counter = collections.Counter() res = i = 0 for j in range(len(A)): if counter[A[j]] == 0: K -= 1 counter[A[j]] += 1 while K < 0: counter[A[i]] -= 1 if counter[A[i]] == 0: K += 1 i += 1 res += j - i + 1 return res 复杂度分析 时间复杂度:$O(N)$,中 $N$ 为数组长度。 空间复杂度:$O(k)$。 1109. 航班预订统计(中等)题目描述123456789101112131415161718192021这里有 n 个航班,它们分别从 1 到 n 进行编号。我们这儿有一份航班预订表,表中第 i 条预订记录 bookings[i] = [i, j, k] 意味着我们在从 i 到 j 的每个航班上预订了 k 个座位。请你返回一个长度为 n 的数组 answer,按航班编号顺序返回每个航班上预订的座位数。示例:输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5输出:[10,55,45,25,25]提示:1 <= bookings.length <= 200001 <= bookings[i][0] <= bookings[i][1] <= n <= 200001 <= bookings[i][2] <= 10000 前置知识 前缀和 思路这道题的题目描述不是很清楚。我简单分析一下题目: [i, j, k] 其实代表的是 第 i 站上来了 k 个人, 一直到 第 j 站都在飞机上,到第 j + 1 就不在飞机上了。所以第 i 站到第 j 站的每一站都会因此多 k 个人。 理解了题目只会不难写出下面的代码。 123456789class Solution: def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]: counter = [0] * n for i, j, k in bookings: while i <= j: counter[i - 1] += k i += 1 return counter 如上的代码复杂度太高,无法通过全部的测试用例。 注意到里层的 while 循环是连续的数组全部加上一个数字,不难想到可以利用母题 0 的前缀和思路优化。 一种思路就是在 i 的位置 + k, 然后利用前缀和的技巧给 i 到 n 的元素都加上 k。但是题目需要加的是一个区间, j + 1 及其之后的元素会被多加一个 k。一个简单的技巧就是给 j + 1 的元素减去 k,这样正负就可以抵消。 拼车 是这道题的换皮题, 思路一模一样。 代码(Python)12345678910class Solution: def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]: counter = [0] * (n + 1) for i, j, k in bookings: counter[i - 1] += k if j < n: counter[j] -= k for i in range(n + 1): counter[i] += counter[i - 1] return counter[:-1] 复杂度分析 时间复杂度:$O(N)$,中 $N$ 为数组长度。 空间复杂度:$O(N)$。 总结这几道题都是滑动窗口和前缀和的思路。力扣类似的题目还真不少,大家只有多留心,就会发现这个套路。 前缀和的技巧以及滑动窗口的技巧都比较固定,且有模板可套。 难点就在于我怎么才能想到可以用这个技巧呢? 我这里总结了两点: 找关键字。比如题目中有连续,就应该条件反射想到滑动窗口和前缀和。比如题目求最大最小就想到动态规划和贪心等等。想到之后,就可以和题目信息对比快速排除错误的算法,找到可行解。这个思考的时间会随着你的题感增加而降低。 先写出暴力解,然后找暴力解的瓶颈, 根据瓶颈就很容易知道应该用什么数据结构和算法去优化。 最后推荐几道类似的题目, 供大家练习,一定要自己写出来才行哦。 303. 区域和检索 - 数组不可变 1171. 从链表中删去总和值为零的连续节点 1186.删除一次得到子数组最大和 1310. 子数组异或查询 1371. 每个元音包含偶数次的最长子字符串 1402. 做菜顺序 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。 更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K star 啦。 大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"},{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/tags/经验分享/"},{"name":"困难","slug":"困难","permalink":"https://lucifer.ren/blog/tags/困难/"},{"name":"中等","slug":"中等","permalink":"https://lucifer.ren/blog/tags/中等/"},{"name":"子数组","slug":"子数组","permalink":"https://lucifer.ren/blog/tags/子数组/"},{"name":"k 问题","slug":"k-问题","permalink":"https://lucifer.ren/blog/tags/k-问题/"}]},{"title":"TypeScript 练习题","slug":"ts-exercises","date":"2020-09-26T16:00:00.000Z","updated":"2023-01-07T12:34:34.505Z","comments":true,"path":"2020/09/27/ts-exercises/","link":"","permalink":"https://lucifer.ren/blog/2020/09/27/ts-exercises/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(就是本文) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript TypeScript 类型系统 types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题(就是本文) 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言本文涉及的题目一共十五道,全部都可以在 typescript-exercises 上在线提交。 可以和标准答案进行对比。 并且由于使用了浏览器缓存, 因此无需登录的情况下也可以保证关掉页面,你的答题进度也会保留。 想重置进度,清空缓存,无痕模式或者换浏览器都可以。 题目中涉及到的知识点我基本也都在之前的文章中提到了,如果你没有看过,强烈建议先完成前面的教程,然后将上面的题目自己做一遍之后再看本文。另外一定要按照顺序读, 因此前面的题目都是后面的铺垫。 为了不让文章太过于冗长, 本篇文章分两次发布, 这一次是 8 道题,一共十五道。每道题都有思路,前置知识以及代码。 题目一题目描述12345678910111213Intro: We are starting a small community of users. For performance reasons we have decided to store all users right in the code. This way we can provide our developers with more user-interaction opportunities. With user-related data, at least. All the GDPR-related issues we will solved some other day. This would be the base for our future experiments during these exercises.Exercise: Given the data, define the interface "User" and use it accordingly. 题目的大概意思是让你定义一个类型 User, 使得代码可以正常运行。 题目内置代码123456789101112131415161718192021export type User = unknown;export const users: unknown[] = [ { name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", },];export function logPerson(user: unknown) { console.log(` - ${user.name}, ${user.age}`);}console.log(\"Users:\");users.forEach(logPerson); 前置知识 interface 或 type 声明自定义类型 思路这道题比较简单, 我们只有定义一个 User 类即可。从 users 数组中不难看出, User 中有三个属性 name ,age 和 occupation,类型分别为 string, number 和 string。因此直接使用 type 或者 interface 定义自定义类型即可。 代码核心代码: 12345export type User = { name: string; age: number; occupation: string;}; 题目二题目描述123456789101112131415Intro: All 2 users liked the idea of the community. We should go forward and introduce some order. We are in Germany after all. Let's add a couple of admins. Initially we only had users in the in-memory database. After introducing Admins, we need to fix the types so that everything works well together.Exercise: Type "Person" is missing, please define it and use it in persons array and logPerson function in order to fix all the TS errors. 题目大意是补充 Person 类, 使得代码不报错。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142interface User { name: string; age: number; occupation: string;}interface Admin { name: string; age: number; role: string;}export type Person = unknown;export const persons: User[] /* <- Person[] */ = [ { name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { name: \"Jane Doe\", age: 32, role: \"Administrator\", }, { name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", }, { name: \"Bruce Willis\", age: 64, role: \"World saver\", },];export function logPerson(user: User) { console.log(` - ${user.name}, ${user.age}`);}persons.forEach(logPerson); 前置知识 联合类型 思路我们直接从报错入手。 不难发现 persons 数组既有 User 又有 Admin。 因此 person 的函数签名应该是两者的联合类型。而题目又让我们补充 Person,于是代码将 Person 定义为 Admin 和 User 的联合类型就不难想到。 代码核心代码: 1export type Person = User | Admin; 这个时候, persons 数组使用的过程只能用 User 和 Admin 的共有属性, 也就是 name 和 age,这点后面的题目也会提到。 因此如果你使用了 role 或者 occupation 就会报错。怎么解决呢? 我们继续看下一题。 第三题题目描述12345678910111213Intro: Since we already have some of the additional information about our users, it's a good idea to output it in a nice way.Exercise: Fix type errors in logPerson function. logPerson function should accept both User and Admin and should output relevant information according to the input: occupation for User and role for Admin. 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748interface User { name: string; age: number; occupation: string;}interface Admin { name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { name: \"Jane Doe\", age: 32, role: \"Administrator\", }, { name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", }, { name: \"Bruce Willis\", age: 64, role: \"World saver\", },];export function logPerson(person: Person) { let additionalInformation: string; if (person.role) { additionalInformation = person.role; } else { additionalInformation = person.occupation; } console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);}persons.forEach(logPerson); 前置知识 类型断言 类型收敛 in 操作符 思路关于类型收敛, 我在 TypeScript 类型系统 做了很详情的讨论。 上面代码报错的原因前面已经讲过了, 那么如何解决呢?由于 person 可能是 User ,也可能是 Admin 类型,而 TypeScript 没有足够的信息确定具体是哪一种。因此你使用 User 或者 Admin 特有的属性就会报错了。 因此解决方案的基本思想就是告诉 TypeScript person 当前是 Admin 还是 User 类型。有多种方式可以解决这个问题。 将 person 断言为准确的类型。 就是告诉 TypeScript ”交给我吧, person 就是 xxx 类型,有错就我的锅“。 代码: 12345if ((<Admin>person).role) { additionalInformation = (<Admin>person).role;} else { additionalInformation = (<User>person).occupation;} 另外一种方式是使用类型收缩,比如 is , in, typeof , instanceof 等。使得 Typescript 能够 Get 到当前的类型。”哦, person 上有 role 属性啊,那它就是 Admin 类型,有问题我 Typescript 的锅“ 这里我们使用 in 操作符,写起来也很简单。 推荐哪种不用我多说了吧 ? 代码1234567if (\"role\" in person) { // person 会被自动推导为 Admin additionalInformation = person.role;} else { // Person 会被自动推导为 User additionalInformation = person.occupation;} 第四题题目描述123456789101112Intro: As we introduced "type" to both User and Admin it's now easier to distinguish between them. Once object type checking logic was extracted into separate functions isUser and isAdmin - logPerson function got new type errors.Exercise: Figure out how to help TypeScript understand types in this situation and apply necessary fixes. 大概意思还是让你改代码, 使得 Typescript 能理解(不报错)。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" },];export function isAdmin(person: Person) { return person.type === \"admin\";}export function isUser(person: Person) { return person.type === \"user\";}export function logPerson(person: Person) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);}console.log(\"Admins:\");persons.filter(isAdmin).forEach(logPerson);console.log();console.log(\"Users:\");persons.filter(isUser).forEach(logPerson); 前置知识 类型收敛 is 操作符 思路我们仍然从报错入手。 实际上还是 person 的类型问题, 没有被收缩到正确的类型。看题目的代码,期望效果应该是如果进入 isAdmin 内部,那么 person 就是 Admin 类型,同理进入 isUser 内部,那么 person 就是 User 类型。 继续看下 isAdmin 和 isUser 的实现: 1234567export function isAdmin(person: Person) { return person.type === \"admin\";}export function isUser(person: Person) { return person.type === \"user\";} 这里我们期望的效果是如果 isAdmin 函数返回 true ,那么 person 就应该被收敛为 Admin,isUser 同理。 这里就需要用到 is 操作符。 上文提到了类型收敛常见的操作符是 is , in, typeof , instanceof 代码1234567export function isAdmin(person: Person): person is Admin { return person.type === \"admin\";}export function isUser(person: Person): person is User { return person.type === \"user\";} 这样当 isAdmin 返回 true, 那么 person 变量就会被推导成 Admin 类型,而不是联合类型, 也就是类型发生了收缩。 不难看出,这样的类型断言会直接影响到调用 isAdmin 或 isUser 的函数的入参的类型。 第五题题目描述123456789101112131415161718Intro: Time to filter the data! In order to be flexible we filter users using a number of criteria and return only those matching all of the criteria. We don't need Admins yet, we only filter Users.Exercise: Without duplicating type structures, modify filterUsers function definition so that we can pass only those criteria which are needed, and not the whole User information as it is required now according to typing.Higher difficulty bonus exercise: Exclude "type" from filter criterias. 大概意思是让你改 filterUsers, 但要注意 DRY(Don’t Repeat Yourself)。 题目内置代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\", }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\", }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\", }, { type: \"user\", name: \"Wilson\", age: 23, occupation: \"Ball\", }, { type: \"admin\", name: \"Agent Smith\", age: 23, role: \"Administrator\", },];export const isAdmin = (person: Person): person is Admin => person.type === \"admin\";export const isUser = (person: Person): person is User => person.type === \"user\";export function logPerson(person: Person) { let additionalInformation = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);}export function filterUsers(persons: Person[], criteria: User): User[] { return persons.filter(isUser).filter((user) => { const criteriaKeys = Object.keys(criteria) as (keyof User)[]; return criteriaKeys.every((fieldName) => { return user[fieldName] === criteria[fieldName]; }); });}console.log(\"Users of age 23:\");filterUsers(persons, { age: 23,}).forEach(logPerson); 前置知识 泛型 Partial 泛型 思路老规矩, 从报错入手。 大概意思是 { age: 23 } 不完整,缺失了部分 key。而题目实际上的想法应该是想根据部分内容对人员进行检错。比如可以根据 age 查, 也可以根据 name 查,也可以同时根据 age 和 name 查等,这和我们平时的搜索逻辑是一致的。 直接用 Partial 泛型即可解决, 不懂的可以看下我的文章你不知道的 TypeScript 泛型(万字长文,建议收藏)。 代码123export function filterUsers(persons: Person[], criteria: Partial<User>): User[] { ...} 第六题题目描述123456789101112131415161718192021222324Intro: Filtering requirements have grown. We need to be able to filter any kind of Persons.Exercise: Fix typing for the filterPersons so that it can filter users and return User[] when personType='user' and return Admin[] when personType='admin'. Also filterPersons should accept partial User/Admin type according to the personType. `criteria` argument should behave according to the `personType` argument value. `type` field is not allowed in the `criteria` field.Higher difficulty bonus exercise: Implement a function `getObjectKeys()` which returns more convenient result for any argument given, so that you don't need to cast it. let criteriaKeys = Object.keys(criteria) as (keyof User)[]; --> let criteriaKeys = getObjectKeys(criteria); 大概意思是让你改 filterUsers, 但要注意 DRY(Don’t Repeat Yourself)。并且可以根据 personType 的不同,返回不同的类型。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}export type Person = User | Admin;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"user\", name: \"Wilson\", age: 23, occupation: \"Ball\" }, { type: \"admin\", name: \"Agent Smith\", age: 23, role: \"Anti-virus engineer\" },];export function logPerson(person: Person) { console.log( ` - ${person.name}, ${person.age}, ${ person.type === \"admin\" ? person.role : person.occupation }` );}export function filterPersons( persons: Person[], personType: string, criteria: unknown): unknown[] { return persons .filter((person) => person.type === personType) .filter((person) => { let criteriaKeys = Object.keys(criteria) as (keyof Person)[]; return criteriaKeys.every((fieldName) => { return person[fieldName] === criteria[fieldName]; }); });}export const usersOfAge23 = filterPersons(persons, \"user\", { age: 23 });export const adminsOfAge23 = filterPersons(persons, \"admin\", { age: 23 });console.log(\"Users of age 23:\");usersOfAge23.forEach(logPerson);console.log();console.log(\"Admins of age 23:\");adminsOfAge23.forEach(logPerson); 前置知识 泛型 Partial 泛型 函数重载 思路题目描述也懒得看了, 直接看报错。 报错信息提示我们没有找到合适的函数重载。 因此我的思路就是补上合适的重载即可。关于函数重载,我的系列教程不涉及,大家可以看下官网资料。 重载之后,不同的情况调用返回值就可以对应不同的类型。本题中就是: 如果 personType 是 admin,就会返回 Admin 数组。 如果 personType 是 user,就会返回 User 数组。 如果 personType 是其他 string,就会返回 Person 数组。 代码12345export function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Person>): Admin[]export function filterPersons(persons: Person[], personType: 'user', criteria: Partial<Person>): User[]export function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): Person[] { ...} 第七题题目描述123456789101112131415161718192021Intro: Filtering was completely removed from the project. It turned out that this feature was just not needed for the end-user and we spent a lot of time just because our office manager told us to do so. Next time we should instead listen to the product management. Anyway we have a new plan. CEO's friend Nick told us that if we randomly swap user names from time to time in the community, it would be very funny and the project would definitely succeed!Exercise: Implement swap which receives 2 persons and returns them in the reverse order. The function itself is already there, actually. We just need to provide it with proper types. Also this function shouldn't necessarily be limited to just Person types, lets type it so that it works with any two types specified. 题目大概意思是让你修改 swap 函数,使得不报错。 并且,我希望这个函数可以适用于任意两个变量,不管其类型一样不一样, 也不管二者类型是什么。 题目内置代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}function logUser(user: User) { const pos = users.indexOf(user) + 1; console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`);}function logAdmin(admin: Admin) { const pos = admins.indexOf(admin) + 1; console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`);}const admins: Admin[] = [ { type: \"admin\", name: \"Will Bruces\", age: 30, role: \"Overseer\", }, { type: \"admin\", name: \"Steve\", age: 40, role: \"Steve\", },];const users: User[] = [ { type: \"user\", name: \"Moses\", age: 70, occupation: \"Desert guide\", }, { type: \"user\", name: \"Superman\", age: 28, occupation: \"Ordinary person\", },];export function swap(v1, v2) { return [v2, v1];}function test1() { console.log(\"test1:\"); const [secondUser, firstAdmin] = swap(admins[0], users[1]); logUser(secondUser); logAdmin(firstAdmin);}function test2() { console.log(\"test2:\"); const [secondAdmin, firstUser] = swap(users[0], admins[1]); logAdmin(secondAdmin); logUser(firstUser);}function test3() { console.log(\"test3:\"); const [secondUser, firstUser] = swap(users[0], users[1]); logUser(secondUser); logUser(firstUser);}function test4() { console.log(\"test4:\"); const [firstAdmin, secondAdmin] = swap(admins[1], admins[0]); logAdmin(firstAdmin); logAdmin(secondAdmin);}function test5() { console.log(\"test5:\"); const [stringValue, numericValue] = swap(123, \"Hello World\"); console.log(` - String: ${stringValue}`); console.log(` - Numeric: ${numericValue}`);}[test1, test2, test3, test4, test5].forEach((test) => test()); 前置知识 泛型 思路题目废话很多, 直接忽略看报错。 这个其实我在 你不知道的 TypeScript 泛型(万字长文,建议收藏) 里也讲过了,直接看代码。 代码123export function swap<U, T>(v1: T, v2: U): [U, T] { return [v2, v1];} 第八题题目描述1234567891011121314Intro: Project grew and we ended up in a situation with some users starting to have more influence. Therefore, we decided to create a new person type called PowerUser which is supposed to combine everything User and Admin have.Exercise: Define type PowerUser which should have all fields from both User and Admin (except for type), and also have type 'powerUser' without duplicating all the fields in the code. 题目大概意思是定义一个类型 PowerUser, 里面包含 User 和 Admin 的所有属性, 并且有一个字段是固定的 type: ‘powerUser’。 题目内置代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475interface User { type: \"user\"; name: string; age: number; occupation: string;}interface Admin { type: \"admin\"; name: string; age: number; role: string;}type PowerUser = unknown;export type Person = User | Admin | PowerUser;export const persons: Person[] = [ { type: \"user\", name: \"Max Mustermann\", age: 25, occupation: \"Chimney sweep\", }, { type: \"admin\", name: \"Jane Doe\", age: 32, role: \"Administrator\" }, { type: \"user\", name: \"Kate Müller\", age: 23, occupation: \"Astronaut\" }, { type: \"admin\", name: \"Bruce Willis\", age: 64, role: \"World saver\" }, { type: \"powerUser\", name: \"Nikki Stone\", age: 45, role: \"Moderator\", occupation: \"Cat groomer\", },];function isAdmin(person: Person): person is Admin { return person.type === \"admin\";}function isUser(person: Person): person is User { return person.type === \"user\";}function isPowerUser(person: Person): person is PowerUser { return person.type === \"powerUser\";}export function logPerson(person: Person) { let additionalInformation: string = \"\"; if (isAdmin(person)) { additionalInformation = person.role; } if (isUser(person)) { additionalInformation = person.occupation; } if (isPowerUser(person)) { additionalInformation = `${person.role}, ${person.occupation}`; } console.log(`${person.name}, ${person.age}, ${additionalInformation}`);}console.log(\"Admins:\");persons.filter(isAdmin).forEach(logPerson);console.log();console.log(\"Users:\");persons.filter(isUser).forEach(logPerson);console.log();console.log(\"Power users:\");persons.filter(isPowerUser).forEach(logPerson); 前置知识 集合操作(交叉类型) & 操作符 泛型 Omit 泛型 思路从题目信息不难看出,就是让我们实现 PowerUser。 有前面的分析不难得出我们只需要: 合并 User 和 Admin 的属性即可。 借助 & 操作符可以实现。即 User & Admin。 增加特有的属性 type: powerUser。 首先去掉上一步合并的 type 属性, 然后继续和 { type: “powerUser” } 交叉即可。 增加 { type: “powerUser” } 之前使用内置泛型 Omit 将原本的 type 删掉即可。 代码1type PowerUser = Omit<User & Admin, \"type\"> & { type: \"powerUser\" }; 总结以上就是给大家带来的题目解析。 这八道题的考点有,按照我个人理解的重要程度划分为: type 和 interface 的基本操作(必须掌握) 联合类型 和 交叉类型(强烈建议掌握) 类型断言和类型收缩(强烈建议掌握) 泛型和常见内置泛型(强烈建议掌握) 函数重载(推荐掌握) 最后祝愿大家告别 anyscript,成为 TypeScript 魔法师。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"字节跳动的算法面试题是什么难度?(第二弹)","slug":"byte-dance-algo-ex-2017","date":"2020-09-05T16:00:00.000Z","updated":"2023-01-07T12:20:39.954Z","comments":true,"path":"2020/09/06/byte-dance-algo-ex-2017/","link":"","permalink":"https://lucifer.ren/blog/2020/09/06/byte-dance-algo-ex-2017/","excerpt":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary ​","text":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary ​ 这套题一共 11 道题, 三道编程题, 八道问答题。本次给大家带来的就是这三道编程题。更多精彩内容,请期待我的搞定算法面试专栏。 其中有一道题《异或》我没有通过所有的测试用例, 小伙伴可以找找茬,第一个找到并在公众号力扣加加留言的小伙伴奖励现金红包 10 元。 1. 头条校招题目描述123456789101112131415161718192021222324252627头条的 2017 校招开始了!为了这次校招,我们组织了一个规模宏大的出题团队,每个出题人都出了一些有趣的题目,而我们现在想把这些题目组合成若干场考试出来,在选题之前,我们对题目进行了盲审,并定出了每道题的难度系统。一场考试包含 3 道开放性题目,假设他们的难度从小到大分别为 a,b,c,我们希望这 3 道题能满足下列条件:a<=b<=cb-a<=10c-b<=10所有出题人一共出了 n 道开放性题目。现在我们想把这 n 道题分布到若干场考试中(1 场或多场,每道题都必须使用且只能用一次),然而由于上述条件的限制,可能有一些考试没法凑够 3 道题,因此出题人就需要多出一些适当难度的题目来让每场考试都达到要求,然而我们出题已经出得很累了,你能计算出我们最少还需要再出几道题吗?输入描述:输入的第一行包含一个整数 n,表示目前已经出好的题目数量。第二行给出每道题目的难度系数 d1,d2,...,dn。数据范围对于 30%的数据,1 ≤ n,di ≤ 5;对于 100%的数据,1 ≤ n ≤ 10^5,1 ≤ di ≤ 100。在样例中,一种可行的方案是添加 2 个难度分别为 20 和 50 的题目,这样可以组合成两场考试:(20 20 23)和(35,40,50)。输出描述:输出只包括一行,即所求的答案。示例 1输入420 35 23 40输出2 思路这道题看起来很复杂, 你需要考虑很多的情况。,属于那种没有技术含量,但是考验编程能力的题目,需要思维足够严密。这种模拟的题目,就是题目让我干什么我干什么。 类似之前写的囚徒房间问题,约瑟夫环也是模拟,只不过模拟之后需要你剪枝优化。 这道题的情况其实很多, 我们需要考虑每一套题中的难度情况, 而不需要考虑不同套题的难度情况。题目要求我们满足:a<=b<=c b-a<=10 c-b<=10,也就是题目难度从小到大排序之后,相邻的难度不能大于 10 。 因此我们的思路就是先排序,之后从小到大遍历,如果满足相邻的难度不大于 10 ,则继续。如果不满足, 我们就只能让字节的老师出一道题使得满足条件。 由于只需要比较同一套题目的难度,因此我的想法就是比较同一套题目的第二个和第一个,以及第三个和第二个的 diff。 如果 diff 小于 10,什么都不做,继续。 如果 diff 大于 10,我们必须补充题目。 这里有几个点需要注意。 对于第二题来说: 比如 1 30 40 这样的难度。 我可以在 1,30 之间加一个 21,这样 1,21,30 就可以组成一一套。 比如 1 50 60 这样的难度。 我可以在 1,50 之间加 21, 41 才可以组成一套,自身(50)是无论如何都没办法组到这套题中的。 不难看出, 第二道题的临界点是 diff = 20 。 小于等于 20 都可以将自身组到套题,增加一道即可,否则需要增加两个,并且自身不能组到当前套题。 对于第三题来说: 比如 1 20 40。 我可以在 20,40 之间加一个 30,这样 1,20,30 就可以组成一一套,自身(40)是无法组到这套题的。 比如 1 20 60。 也是一样的,我可以在 20,60 之间加一个 30,自身(60)同样是没办法组到这套题中的。 不难看出, 第三道题的临界点是 diff = 10 。 小于等于 10 都可以将自身组到套题,否则需要增加一个,并且自身不能组到当前套题。 这就是所有的情况了。 有的同学比较好奇,我是怎么思考的。 我是怎么保障不重不漏的。 实际上,这道题就是一个决策树, 我画个决策树出来你就明白了。 图中红色边框表示自身可以组成套题的一部分, 我也用文字进行了说明。#2 代表第二题, #3 代表第三题。 从图中可以看出, 我已经考虑了所有情况。如果你能够像我一样画出这个决策图,我想你也不会漏的。当然我的解法并不一定是最优的,不过确实是一个非常好用,具有普适性的思维框架。 需要特别注意的是,由于需要凑整, 因此你需要使得题目的总数是 3 的倍数向上取整。 代码12345678910111213141516171819202122232425n = int(input())nums = list(map(int, input().split()))cnt = 0cur = 1nums.sort()for i in range(1, n): if cur == 3: cur = 1 continue diff = nums[i] - nums[i - 1] if diff <= 10: cur += 1 if 10 < diff <= 20: if cur == 1: cur = 3 if cur == 2: cur = 1 cnt += 1 if diff > 20: if cur == 1: cnt += 2 if cur == 2: cnt += 1 cur = 1print(cnt + 3 - cur) 复杂度分析 时间复杂度:由于使用了排序, 因此时间复杂度为 $O(NlogN)$。(假设使用了基于比较的排序) 空间复杂度:$O(1)$ 2. 异或题目描述12345678910111213141516171819202122给定整数 m 以及 n 各数字 A1,A2,..An,将数列 A 中所有元素两两异或,共能得到 n(n-1)/2 个结果,请求出这些结果中大于 m 的有多少个。输入描述:第一行包含两个整数 n,m.第二行给出 n 个整数 A1,A2,...,An。数据范围对于 30%的数据,1 <= n, m <= 1000对于 100%的数据,1 <= n, m, Ai <= 10^5输出描述:输出仅包括一行,即所求的答案输入例子 1:3 106 5 10输出例子 1:2 前置知识 异或运算的性质 如何高效比较两个数的大小(从高位到低位) 首先普及一下前置知识。 第一个是异或运算: 异或的性质:两个数字异或的结果 a^b 是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是如果同一位的数字相同则为 0,不同则为 1 异或的规律: 任何数和本身异或则为 0 任何数和 0 异或是本身 异或运算满足交换律,即: a ^ b ^ c = a ^ c ^ b 同时建议大家去看下我总结的几道位运算的经典题目。 位运算系列 其次要知道一个常识, 即比较两个数的大小, 我们是从高位到低位比较,这样才比较高效。 比如: 1231234561234 这三个数比较大小, 为了方便我们先补 0 ,使得大家的位数保持一致。 123012304561234 先比较第一位,1 比较 0 大, 因此 1234 最大。再比较第二位, 4 比 1 大, 因此 456 大于 123,后面位不需要比较了。这其实就是剪枝的思想。 有了这两个前提,我们来试下暴力法解决这道题。 思路暴力法就是枚举 $N^2 / 2$ 中组合, 让其两两按位异或,将得到的结果和 m 进行比较, 如果比 m 大, 则计数器 + 1, 最后返回计数器的值即可。 暴力的方法就如同题目描述的那样, 复杂度为 $N^2$。 一定过不了所有的测试用例, 不过大家实在没有好的解法的情况可以兜底。不管是牛客笔试还是实际的面试都是可行的。 接下来,让我们来分析一下暴力为什么低效,以及如何选取数据结构和算法能够使得这个过程变得高效。 记住这句话, 几乎所有的优化都是基于这种思维产生的,除非你开启了上帝模式,直接看了答案。 只不过等你熟悉了之后,这个思维过程会非常短, 以至于变成条件反射, 你感觉不到有这个过程, 这就是有了题感。 其实我刚才说的第二个前置知识就是我们优化的关键之一。 我举个例子, 比如 3 和 5 按位异或。 3 的二进制是 011, 5 的二进制是 101, 12011101 按照我前面讲的异或知识, 不难得出其异或结构就是 110。 上面我进行了三次异或: 第一次是最高位的 0 和 1 的异或, 结果为 1。 第二次是次高位的 1 和 0 的异或, 结果为 1。 第三次是最低位的 1 和 1 的异或, 结果为 0。 那如何 m 是 1 呢? 我们有必要进行三次异或么? 实际上进行第一次异或的时候已经知道了一定比 m(m 是 1) 大。因为第一次异或的结构导致其最高位为 1,也就是说其最小也不过是 100,也就是 4,一定是大于 1 的。这就是剪枝, 这就是算法优化的关键。 看出我一步一步的思维过程了么?所有的算法优化都需要经过类似的过程。 因此我的算法就是从高位开始两两异或,并且异或的结果和 m 对应的二进制位比较大小。 如果比 m 对应的二进制位大或者小,我们提前退出即可。 如果相等,我们继续往低位移动重复这个过程。 这虽然已经剪枝了,但是极端情况下,性能还是很差。比如: 123m: 1111a: 1010b: 0101 a,b 表示两个数,我们比较到最后才发现,其异或的值和 m 相等。因此极端情况,算法效率没有得到改进。 这里我想到了一点,就是如果一个数 a 的前缀和另外一个数 b 的前缀是一样的,那么 c 和 a 或者 c 和 b 的异或的结构前缀部分一定也是一样的。比如: 123a: 111000b: 111101c: 101011 a 和 b 有共同的前缀 111,c 和 a 异或过了,当再次和 b 异或的时候,实际上前三位是没有必要进行的,这也是重复的部分。这就是算法可以优化的部分, 这就是剪枝。 分析算法,找到算法的瓶颈部分,然后选取合适的数据结构和算法来优化到。 这句话很重要, 请务必记住。 在这里,我们用的就是剪枝技术,关于剪枝,91 天学算法也有详细的介绍。 回到前面讲到的算法瓶颈, 多个数是有共同前缀的, 前缀部分就是我们浪费的运算次数, 说到前缀大家应该可以想到前缀树。如果不熟悉前缀树的话,看下我的这个前缀树专题,里面的题全部手写一遍就差不多了。 因此一种想法就是建立一个前缀树, 树的根就是最高的位。 由于题目要求异或, 我们知道异或是二进制的位运算, 因此这棵树要存二进制才比较好。 反手看了一眼数据范围:m, n<=10^5 。 10^5 = 2 ^ x,我们的目标是求出 满足条件的 x 的 ceil(向上取整),因此 x 应该是 17。 树的每一个节点存储的是:n 个数中,从根节点到当前节点形成的前缀有多少个是一样的,即多少个数的前缀是一样的。这样可以剪枝,提前退出的时候,就直接取出来用了。比如异或的结果是 1, m 当前二进制位是 0 ,那么这个前缀有 10 个,我都不需要比较了, 计数器直接 + 10 。 我用 17 直接复杂度过高,目前仅仅通过了 70 % - 80 % 测试用例, 希望大家可以帮我找找毛病,我猜测是语言的锅。 代码12345678910111213141516171819202122232425262728293031323334353637class TreeNode: def __init__(self): self.cnt = 1 self.children = [None] * 2def solve(num, i, cur): if cur == None or i == -1: return 0 bit = (num >> i) & 1 mbit = (m >> i) & 1 if bit == 0 and mbit == 0: return (cur.children[1].cnt if cur.children[1] else 0) + solve(num, i - 1, cur.children[0]) if bit == 1 and mbit == 0: return (cur.children[0].cnt if cur.children[0] else 0) + solve(num, i - 1, cur.children[1]) if bit == 0 and mbit == 1: return solve(num, i - 1, cur.children[1]) if bit == 1 and mbit == 1: return solve(num, i - 1, cur.children[0])def preprocess(nums, root): for num in nums: cur = root for i in range(16, -1, -1): bit = (num >> i) & 1 if cur.children[bit]: cur.children[bit].cnt += 1 else: cur.children[bit] = TreeNode() cur = cur.children[bit]n, m = map(int, input().split())nums = list(map(int, input().split()))root = TreeNode()preprocess(nums, root)ans = 0for num in nums: ans += solve(num, 16, root)print(ans // 2) 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 3. 字典序题目描述1234567891011121314151617181920212223给定整数 n 和 m, 将 1 到 n 的这 n 个整数按字典序排列之后, 求其中的第 m 个数。对于 n=11, m=4, 按字典序排列依次为 1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9, 因此第 4 个数是 2.对于 n=200, m=25, 按字典序排列依次为 1 10 100 101 102 103 104 105 106 107 108 109 11 110 111 112 113 114 115 116 117 118 119 12 120 121 122 123 124 125 126 127 128 129 13 130 131 132 133 134 135 136 137 138 139 14 140 141 142 143 144 145 146 147 148 149 15 150 151 152 153 154 155 156 157 158 159 16 160 161 162 163 164 165 166 167 168 169 17 170 171 172 173 174 175 176 177 178 179 18 180 181 182 183 184 185 186 187 188 189 19 190 191 192 193 194 195 196 197 198 199 2 20 200 21 22 23 24 25 26 27 28 29 3 30 31 32 33 34 35 36 37 38 39 4 40 41 42 43 44 45 46 47 48 49 5 50 51 52 53 54 55 56 57 58 59 6 60 61 62 63 64 65 66 67 68 69 7 70 71 72 73 74 75 76 77 78 79 8 80 81 82 83 84 85 86 87 88 89 9 90 91 92 93 94 95 96 97 98 99 因此第 25 个数是 120…输入描述:输入仅包含两个整数 n 和 m。数据范围:对于 20%的数据, 1 <= m <= n <= 5 ;对于 80%的数据, 1 <= m <= n <= 10^7 ;对于 100%的数据, 1 <= m <= n <= 10^18.输出描述:输出仅包括一行, 即所求排列中的第 m 个数字.示例 1输入11 4输出2 前置知识 十叉树 完全十叉树 计算完全十叉树的节点个数 字典树 思路和上面题目思路一样, 先从暴力解法开始,尝试打开思路。 暴力兜底的思路是直接生成一个长度为 n 的数组, 排序,选第 m 个即可。代码: 1234n, m = map(int, input().split())nums = [str(i) for i in range(1, n + 1)]print(sorted(nums)[m - 1]) 复杂度分析 时间复杂度:取决于排序算法, 不妨认为是 $O(NlogN)$ 空间复杂度: $O(N)$ 这种算法可以 pass 50 % case。 上面算法低效的原因是开辟了 N 的空间,并对整 N 个 元素进行了排序。 一种简单的优化方法是将排序换成堆,利用堆的特性求第 k 大的数, 这样时间复杂度可以减低到 $mlogN$。 我们继续优化。实际上,你如果把字典序的排序结构画出来, 可以发现他本质就是一个十叉树,并且是一个完全十叉树。 接下来,我带你继续分析。 如图, 红色表示根节点。节点表示一个十进制数, 树的路径存储真正的数字,比如图上的 100,109 等。 这不就是上面讲的前缀树么? 如图黄色部分, 表示字典序的顺序,注意箭头的方向。因此本质上,求字典序第 m 个数, 就是求这棵树的前序遍历的第 m 个节点。 因此一种优化思路就是构建一颗这样的树,然后去遍历。 构建的复杂度是 $O(N)$,遍历的复杂度是 $O(M)$。因此这种算法的复杂度可以达到 $O(max(m, n))$ ,由于 n >= m,因此就是 $O(N)$。 实际上, 这样的优化算法依然是无法 AC 全部测试用例的,会超内存限制。 因此我们的思路只能是不使用 N 的空间去构造树。想想也知道, 由于 N 最大可能为 10^18,一个数按照 4 字节来算, 那么这就有 400000000 字节,大约是 381 M,这是不能接受的。 上面提到这道题就是一个完全十叉树的前序遍历,问题转化为求完全十叉树的前序遍历的第 m 个数。 十叉树和二叉树没有本质不同, 我在二叉树专题部分, 也提到了 N 叉树都可以用二叉树来表示。 对于一个节点来说,第 m 个节点: 要么就是它本身 要么其孩子节点中 要么在其兄弟节点 要么在兄弟节点的孩子节点中 究竟在上面的四个部分的哪,取决于其孩子节点的个数。 count > m ,m 在其孩子节点中,我们需要深入到子节点。 count <= m ,m 不在自身和孩子节点, 我们应该跳过所有孩子节点,直接到兄弟节点。 这本质就是一个递归的过程。 需要注意的是,我们并不会真正的在树上走,因此上面提到的深入到子节点, 以及 跳过所有孩子节点,直接到兄弟节点如何操作呢? 你仔细观察会发现: 如果当前节点的前缀是 x ,那么其第一个子节点(就是最小的子节点)是 x * 10,第二个就是 x * 10 + 1,以此类推。因此: 深入到子节点就是 x * 10。 跳过所有孩子节点,直接到兄弟节点就是 x + 1。 ok,铺垫地差不多了。 接下来,我们的重点是如何计算给定节点的孩子节点的个数。 这个过程和完全二叉树计算节点个数并无二致,这个算法的时间复杂度应该是 $O(logN*logN)$。 如果不会的同学,可以参考力扣原题: 222. 完全二叉树的节点个数 ,这是一个难度为中等的题目。 因此这道题本身被划分为 hard,一点都不为过。 这里简单说下,计算给定节点的孩子节点的个数的思路, 我的 91 天学算法里出过这道题。 一种简单但非最优的思路是分别计算左右子树的深度。 如果当前节点的左右子树高度相同,那么左子树是一个满二叉树,右子树是一个完全二叉树。 否则(左边的高度大于右边),那么左子树是一个完全二叉树,右子树是一个满二叉树。 如果是满二叉树,当前节点数 是 2 ^ depth,而对于完全二叉树,我们继续递归即可。 123456789101112131415class Solution: def countNodes(self, root): if not root: return 0 ld = self.getDepth(root.left) rd = self.getDepth(root.right) if ld == rd: return 2 ** ld + self.countNodes(root.right) else: return 2 ** rd + self.countNodes(root.left) def getDepth(self, root): if not root: return 0 return 1 + self.getDepth(root.left) 复杂度分析 时间复杂度:$O(logN * log N)$ 空间复杂度:$O(logN)$ 而这道题, 我们可以更简单和高效。 比如我们要计算 1 号节点的子节点个数。 它的孩子节点个数是 。。。 它的孙子节点个数是 。。。 。。。 全部加起来即可。 它的孩子节点个数是 20 - 10 = 10 。 也就是它的右边的兄弟节点的第一个子节点 减去 它的第一个子节点。 由于是完全十叉树,而不是满十叉树 。因此你需要考虑边界情况,比如题目的 n 是 15。 那么 1 的子节点个数就不是 20 - 10 = 10 了, 而是 15 - 10 + 1 = 16。 其他也是类似的过程, 我们只要: Go deeper and do the same thing 或者: Move to next neighbor and do the same thing 不断重复,直到 m 降低到 0 。 代码12345678910111213141516171819202122def count(c1, c2, n): steps = 0 while c1 <= n: steps += min(n + 1, c2) - c1 c1 *= 10 c2 *= 10 return stepsdef findKthNumber(n: int, k: int) -> int: cur = 1 k = k - 1 while k > 0: steps = count(cur, cur + 1, n) if steps <= k: cur += 1 k -= steps else: cur *= 10 k -= 1 return curn, m = map(int, input().split())print(findKthNumber(n, m)) 复杂度分析 时间复杂度:$O(logM * log N)$ 空间复杂度:$O(1)$ 总结其中三道算法题从难度上来说,基本都是困难难度。从内容来看,基本都是力扣的换皮题,且都或多或少和树有关。如果大家一开始没有思路,建议大家先给出暴力的解法兜底,再画图或举简单例子打开思路。 我也刷了很多字节的题了,还有一些难度比较大的题。如果你第一次做,那么需要你思考比较久才能想出来。加上面试紧张,很可能做不出来。这个时候就更需要你冷静分析,先暴力打底,慢慢优化。有时候即使给不了最优解,让面试官看出你的思路也很重要。 比如小兔的棋盘 想出最优解难度就不低,不过你可以先暴力 DFS 解决,再 DP 优化会慢慢帮你打开思路。有时候面试官也会引导你,给你提示, 加上你刚才“发挥不错”,说不定一下子就做出最优解了,这个我深有体会。 另外要提醒大家的是, 刷题要适量,不要贪多。要完全理清一道题的来龙去脉。多问几个为什么。 这道题暴力法怎么做?暴力法哪有问题?怎么优化?为什么选了这个算法就可以优化?为什么这种算法要用这种数据结构来实现? 扩展最近的力扣的周赛 1803. 统计异或值在范围内的数对有多少竟然也和这篇文章中的第二道题撞了。lucifer 我直接将第二道题的代码稍微改下就 AC 了。 这道题涉及了二进制前缀和的考点,看来大家还是比较喜欢考察的。一般二进制前缀树的题目难度基本都是困难。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"滑动窗口","slug":"算法/滑动窗口","permalink":"https://lucifer.ren/blog/categories/算法/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"},{"name":"字节跳动","slug":"面试/字节跳动","permalink":"https://lucifer.ren/blog/categories/面试/字节跳动/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"},{"name":"字节跳动","slug":"字节跳动","permalink":"https://lucifer.ren/blog/tags/字节跳动/"}]},{"title":"字节跳动的算法面试题是什么难度?","slug":"byte-dance-algo-ex","date":"2020-09-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.741Z","comments":true,"path":"2020/09/06/byte-dance-algo-ex/","link":"","permalink":"https://lucifer.ren/blog/2020/09/06/byte-dance-algo-ex/","excerpt":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary 实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。 ​","text":"由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary 实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。 ​ 这套题一共四道题, 两道问答题, 两道编程题。 其中一道问答题是 LeetCode 426 的原题,只不过题型变成了找茬(改错)。可惜的是 LeetCode 的 426 题是一个会员题目,没有会员的就看不来了。不过,剑指 Offer 正好也有这个题,并且力扣将剑指 Offer 全部的题目都 OJ 化了。 这道题大家可以去 https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof 提交答案。简单说一下这个题目的思路,我们只需要中序遍历即可得到一个有序的数列,同时在中序遍历过程中将 pre 和 cur 节点通过指针串起来即可。 另一个问答是红包题目,这里不多说了。我们重点看一下剩下两个算法编程题。 两个问答题由于不能在线判题,我没有做,只做了剩下两个编程题。 球队比赛第一个编程题是一个球队比赛的题目。 题目描述有三只球队,每只球队编号分别为球队 1,球队 2,球队 3,这三只球队一共需要进行 n 场比赛。现在已经踢完了 k 场比赛,每场比赛不能打平,踢赢一场比赛得一分,输了不得分不减分。已知球队 1 和球队 2 的比分相差 d1 分,球队 2 和球队 3 的比分相差 d2 分,每场比赛可以任意选择两只队伍进行。求如果打完最后的 (n-k) 场比赛,有没有可能三只球队的分数打平。 思路假设球队 1,球队 2,球队 3 此时的胜利次数分别为 a,b,c,球队 1,球队 2,球队 3 总的胜利次数分别为 n1,n2,n3。 我一开始的想法是只要保证 n1,n2,n3 相等且都小于等于 n / 3 即可。如果题目给了 n1,n2,n3 的值就直接: 1print(n1 == n2 == n3 == n / 3) 可是不仅 n1,n2,n3 没给, a,b,c 也没有给。 实际上此时我们的信息仅仅是: 123① a + b + c = k② a - b = d1 or b - a = d1③ b - c = d2 or c - b = d2 其中 k 和 d1,d2 是已知的。a ,b,c 是未知的。 也就是说我们需要枚举所有的 a,b,c 可能性,解方程求出合法的 a,b,c,并且 合法的 a,b,c 都小于等于 n / 3 即可。 这个 a,b,c 的求解数学方程就是中学数学难度, 三个等式化简一下即可,具体见下方代码区域。 a 只需要再次赢得 n / 3 - a 次 b 只需要再次赢得 n / 3 - b 次 c 只需要再次赢得 n / 3 - c 次 123n1 = a + n / 3 - a = n / 3n2 = b + (n / 3 - b) = n / 3n3 = c + (n / 3 - c) = n / 3 代码(Python) 牛客有点让人不爽, 需要 print 而不是 return 123456789101112131415161718192021222324t = int(input())for i in range(t): n, k, d1, d2 = map(int, input().split(\" \")) if n % 3 != 0: print('no') continue abcs = [] for r1 in [-1, 1]: for r2 in [-1, 1]: a = (k + 2 * r1 * d1 + r2 * d2) / 3 b = (k + -1 * r1 * d1 + r2 * d2) / 3 c = (k + -1 * r1 * d1 + -2 * r2 * d2) / 3 a + r1 if 0 <= a <= k and 0 <= b <= k and 0 <= c <= k and a.is_integer() and b.is_integer() and c.is_integer(): abcs.append([a, b, c]) flag = False for abc in abcs: if len(abc) > 0 and max(abc) <= n / 3: flag = True break if flag: print('yes') else: print('no') 复杂度分析 时间复杂度:$O(t)$ 空间复杂度:$O(t)$ 小结感觉这个难度也就是力扣中等水平吧,力扣也有一些数学等式转换的题目, 比如 494.target-sum 转换字符串题目描述有一个仅包含’a’和’b’两种字符的字符串 s,长度为 n,每次操作可以把一个字符做一次转换(把一个’a’设置为’b’,或者把一个’b’置成’a’);但是操作的次数有上限 m,问在有限的操作数范围内,能够得到最大连续的相同字符的子串的长度是多少。 思路看完题我就有种似曾相识的感觉。 每次对妹子说出这句话的时候,她们都会觉得好假 ^_^ 不过这次是真的。 ”哦,不!每次都是真的“。 这道题其实就是我之前写的滑动窗口的一道题【1004. 最大连续 1 的个数 III】滑动窗口(Python3)的换皮题。 专题地址:https://github.com/azl397985856/leetcode/blob/master/thinkings/slide-window.md 所以说,如果这道题你完全没有思路的话。说明: 抽象能力不够。 滑动窗口问题理解不到位。 第二个问题可以看我上面贴的地址,仔细读读,并完成课后练习即可解决。 第一个问题就比较困难了, 不过多看我的题解也可以慢慢提升的。比如: 《割绳子》 实际上就是 343. 整数拆分 的换皮题。 力扣 230 和 力扣 645 就是换皮题,详情参考位运算专题 以及 你的衣服我扒了 - 《最长公共子序列》 以及 穿上衣服我就不认识你了?来聊聊最长上升子序列 以及 一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~ 等等 回归这道题。其实我们只需要稍微抽象一下, 就是一个纯算法题。 抽象的另外一个好处则是将很多不同的题目返璞归真,从而可以在茫茫题海中逃脱。这也是我开启《我是你的妈妈呀》 的原因之一。 如果我们把 a 看成是 0 , b 看成是 1。或者将 b 看成 1, a 看成 0。不就抽象成了: 1234给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 m 个值从 0 变成 1 。返回仅包含 1 的最长(连续)子数组的长度。 这就是 力扣 1004. 最大连续 1 的个数 III 原题。 因此实际上我们要求的是上面两种情况: a 表示 0, b 表示 1 a 表示 1, b 表示 0 的较大值。 lucifer 小提示: 其实我们也可以仅仅考虑一种情况,比如 a 看成是 0 , b 看成是 1。这个时候, 我们操作变成了两种情况,0 变成 1 或者 1 变成 0,同时求解的也变成了最长连续 0 或者 最长连续 1 。 由于这种抽象操作起来更麻烦, 我们不考虑。 问题得到了抽象就好解决了。我们只需要记录下加入窗口的是 0 还是 1: 如果是 1,我们什么都不用做 如果是 0,我们将 m 减 1 相应地,我们需要记录移除窗口的是 0 还是 1: 如果是 1,我们什么都不做 如果是 0,说明加进来的时候就是 1,加进来的时候我们 m 减去了 1,这个时候我们再加 1。 lucifer 小提示: 实际上题目中是求连续 a 或者 b 的长度。看到连续,大家也应该有滑动窗口的敏感度, 别管行不行, 想到总该有的。 我们拿 A = [1, 1, 0, 1, 0, 1], m = 1 来说。看下算法的具体过程: lucifer 小提示: 左侧的数字表示此时窗口大小,黄色格子表示修补的墙,黑色方框表示的是窗口。 这里我形象地将 0 看成是洞,1 看成是墙, 我们的目标就是补洞,使得连续的墙最长。 每次碰到一个洞,我们都去不加选择地修补。由于 m 等于 1, 也就是说我们最多补一个洞。因此需要在修补超过一个洞的时候,我们需要调整窗口范围,使得窗口内最多修补一个墙。由于窗口表示的就是连续的墙(已有的或者修补的),因此最终我们返回窗口的最大值即可。 由于下面的图窗口内有两个洞,这和”最多补一个洞“冲突, 我们需要收缩窗口使得满足“最多补一个洞”的先决条件。 因此最大的窗口就是 max(2, 3, 4, …) = 4。 lucifer 小提示: 可以看出我们不加选择地修补了所有的洞,并调整窗口,使得窗口内最多有 m 个修补的洞,因此窗口的最大值就是答案。然而实际上,我们并不需要真的”修补“(0 变成 1),而是仅仅修改 m 的值即可。 我们先来看下抽象之后的其中一种情况的代码: 123456789class Solution: def longestOnes(self, A: List[int], m: int) -> int: i = 0 for j in range(len(A)): m -= 1 - A[j] if m < 0: m += 1 - A[i] i += 1 return j - i + 1 因此完整代码就是: 1234567891011class Solution: def longestOnes(self, A: List[int], m: int) -> int: i = 0 for j in range(len(A)): m -= 1 - A[j] if m < 0: m += 1 - A[i] i += 1 return j - i + 1 def longestAorB(self, A:List[int], m: int) -> int: return max(self.longestOnes(map(lambda x: 0 if x == 'a' else 1, A) ,m), self.longestOnes(map(lambda x: 1 if x == 'a' else 0, A),m)) 这里的两个 map 会生成两个不同的数组。 我只是为了方便大家理解才新建的两个数组, 实际上根本不需要,具体见后面的代码. 代码(Python)1234567891011121314151617181920i = 0n, m = map(int, input().split(\" \"))s = input()ans = 0k = m # 存一下,后面也要用这个初始值# 修补 bfor j in range(n): m -= ord(s[j]) - ord('a') if m < 0: m += ord(s[i]) - ord('a') i += 1ans = j - i + 1i = 0# 修补 afor j in range(n): k += ord(s[j]) - ord('b') if k < 0: k -= ord(s[i]) - ord('b') i += 1print(max(ans, j - i + 1)) 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 小结这道题就是一道换了皮的力扣题,难度中等。如果你能将问题抽象,同时又懂得滑动窗口,那这道题就很容易。我看了题解区的参考答案, 内容比较混乱,不够清晰。这也是我写下这篇文章的原因之一。 总结这一套字节跳动的题目一共四道,一道设计题,三道算法题。 其中三道算法题从难度上来说,基本都是中等难度。从内容来看,基本都是力扣的换皮题。但是如果我不说他们是换皮题, 你们能发现么? 如果你可以的话,说明你的抽象能力已经略有小成了。如果看不出来也没有关系,关注我。 手把手扒皮给你们看,扒多了慢慢就会了。切记,不要盲目做题!如果你做了很多题, 这几道题还是看不出套路,说明你该缓缓,改变下刷题方式了。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"滑动窗口","slug":"算法/滑动窗口","permalink":"https://lucifer.ren/blog/categories/算法/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/categories/面试/"},{"name":"字节跳动","slug":"面试/字节跳动","permalink":"https://lucifer.ren/blog/categories/面试/字节跳动/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"},{"name":"面试","slug":"面试","permalink":"https://lucifer.ren/blog/tags/面试/"},{"name":"字节跳动","slug":"字节跳动","permalink":"https://lucifer.ren/blog/tags/字节跳动/"}]},{"title":"对《丢鸡蛋问题》的一点补充","slug":"887.super-egg-drop-extension","date":"2020-08-29T16:00:00.000Z","updated":"2023-01-05T12:24:49.765Z","comments":true,"path":"2020/08/30/887.super-egg-drop-extension/","link":"","permalink":"https://lucifer.ren/blog/2020/08/30/887.super-egg-drop-extension/","excerpt":"​ 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 于是【异议!】系列应运而生。它是个什么东西呢?我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,「力扣加加」 来回答你。我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列「第二弹」。 ​","text":"​ 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 于是【异议!】系列应运而生。它是个什么东西呢?我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,「力扣加加」 来回答你。我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列「第二弹」。 ​ 原题地址:https://leetcode-cn.com/problems/super-egg-drop/ 事情的起源昨天有人在我的力扣题解下留言,问我《丢鸡蛋问题》重制版来袭~》题解中为什么第二种算法是加法而不是 min 什么的。毕竟我的第一种算法可是 min(max(碎, 不碎)),为什么第二种就是加法了呢?这个细节我在写题解的时候漏掉了,我打算详细给大家说一下。 题目描述你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。 每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。 你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。 每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。 你的目标是确切地知道 F 的值是多少。 无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少? 示例 1: 输入:K = 1, N = 2 输出:2 解释:鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。如果它没碎,那么我们肯定知道 F = 2 。因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。示例 2: 输入:K = 2, N = 6 输出:3 示例 3: 输入:K = 3, N = 14 输出:4 提示: 1 <= K <= 100 1 <= N <= 10000 我当时的解法我们这样来思考这个问题。 既然题目要求最少的扔的次数,假设有一个函数 f(k, i),他的功能是求出 k 个鸡蛋,扔 i 次所能检测的最高楼层。 我们只需要不断进行发问: ”f 函数啊 f 函数,我扔一次可以么?“, 也就是判断 f(k, 1) >= N 的返回值 ”f 函数啊 f 函数,我扔两次呢?“, 也就是判断 f(k, 2) >= N 的返回值 … ”f 函数啊 f 函数,我扔 m 次呢?“, 也就是判断 f(k, m) >= N 的返回值 我们只需要返回第一个返回值为 true 的 m 即可。 想到这里,我条件发射地想到了二分法。 聪明的小朋友们,你们觉得二分可以么?为什么?欢迎评论区留言讨论。 那么这个神奇的 f 函数怎么实现呢?其实很简单。 摔碎的情况,可以检测的最高楼层是f(m - 1, k - 1) + 1。因为碎了嘛,我们多检测了摔碎的这一层。 没有摔碎的情况,可以检测的最高楼层是f(m - 1, k)。因为没有碎,也就是说我们啥都没检测出来(对能检测的最高楼层无贡献)。 我们来看下代码: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: def f(m, k): if k == 0 or m == 0: return 0 return f(m - 1, k - 1) + 1 + f(m - 1, k) m = 0 while f(m, K) < N: m += 1 return m 上面的代码可以 AC。我们来顺手优化成迭代式。 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m 代码代码支持:JavaSCript,Python Python: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m JavaSCript: 12345678910111213var superEggDrop = function (K, N) { // 不选择dp[K][M]的原因是dp[M][K]可以简化操作 const dp = Array(N + 1) .fill(0) .map((_) => Array(K + 1).fill(0)); let m = 0; while (dp[m][K] < N) { m++; for (let k = 1; k <= K; ++k) dp[m][k] = dp[m - 1][k - 1] + 1 + dp[m - 1][k]; } return m;}; 复杂度分析 时间复杂度:$O(m * K)$,其中 m 为答案。 空间复杂度:$O(K * N)$ 为什么是加法在解法一种我提到了:算法一的本质就是暴力地枚举所有的可能楼层,然后比较最坏情况下的最少扔鸡蛋次数。而实际上解法二也是基于这个大前提,假设我们选择一个楼层是 x。那么在 x 层扔鸡蛋会有两种可能: 鸡蛋碎了。 说明目标楼层 F 就是本层或者比本层低,楼上的 N - x 层不需要检测了,全部排除了。因此我们需要继续探测楼下 x - 1 层,而我们剩下可以探测的最高楼层为 dp[k - 1][m - 1]。由于我们需要探测到具体的 F,因此我们需要使得 dp[k - 1][m - 1] >= x - 1。 鸡蛋没碎。 说明目标楼层 F 比本层高,楼下的 x - 1 不需要检测了,全部排除了。因此我们需要继续探测楼上 N - x 层,而我们剩下可以探测的最高楼层为 dp[k][m - 1]。由于我们需要探测到具体的 F,因此我们需要使得 dp[k][m - 1] >= N - x。 无论鸡蛋碎还是不碎,我们都只需要检测上面或者检测下面,不需要同时检测。 也就是说我需要找到一个楼层 x, 使得 x 同时满足: dp[k - 1][m - 1] >= x - 1 dp[k][m - 1] >= N - x 这样能保证 100% 可以检测出来目标楼层 F。实际上不管鸡蛋碎不碎,可以检测的楼层都是 max(dp[k - 1][m - 1], x -1) + max(dp[k][m - 1], N -x) + 1,由于 dp[k - 1][m - 1] >= x - 1,dp[k][m - 1] >= N - x,因此可以检测的楼层就是 dp[k - 1][m - 1] + dp[k][m - 1] + 1。 这个我可能需要解释一下。 由于选择 x 开始扔之前已经确定了上面的两个不等式是成立的了,因此如果鸡蛋没碎,我们需要继续往上检测,下面是不需要检测的,虽然下面是 x - 1,但是为了保证万一碎的情况也有解,所以有 dp[k - 1][m - 1] >= x - 1,因此实际上可以确定的最大楼层是 dp[k][m - 1] + max(dp[k - 1][m - 1], x -1) + 1,也就是 dp[k][m - 1] + dp[k - 1][m - 1] + 1。鸡蛋如果碎的情况也是一样的分析逻辑。 大家对这道题还有任何问题,都可以留言告诉我! 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"动态规划问题为什么要画表格?","slug":"91algo-dp-lecture","date":"2020-08-26T16:00:00.000Z","updated":"2023-01-05T12:24:49.748Z","comments":true,"path":"2020/08/27/91algo-dp-lecture/","link":"","permalink":"https://lucifer.ren/blog/2020/08/27/91algo-dp-lecture/","excerpt":"本文是我的 91 算法第一期的部分讲义内容。 91 算法第一期已经接近尾声,二期的具体时间关注我的公众号即可,一旦开放,会第一时间在公众号《力扣加加》通知大家。 动态规划可以理解为是查表的递归(记忆化)。那么什么是递归?什么是查表(记忆化)? ​","text":"本文是我的 91 算法第一期的部分讲义内容。 91 算法第一期已经接近尾声,二期的具体时间关注我的公众号即可,一旦开放,会第一时间在公众号《力扣加加》通知大家。 动态规划可以理解为是查表的递归(记忆化)。那么什么是递归?什么是查表(记忆化)? ​ 递归定义: 递归是指在函数的定义中使用函数自身的方法。 算法中使用递归可以很简单地完成一些用循环实现的功能,比如二叉树的左中右序遍历。递归在算法中有非常广泛的使用,包括现在日趋流行的函数式编程。 纯粹的函数式编程中没有循环,只有递归。 有意义的递归算法会把问题分解成规模缩小的同类子问题,当子问题缩写到寻常的时候,我们可以知道它的解。然后我们建立递归函数之间的联系即可解决原问题,这也是我们使用递归的意义。准确来说, 递归并不是算法,它是和迭代对应的一种编程方法。只不过,我们通常借助递归去分解问题而已。 一个问题要使用递归来解决必须有递归终止条件(算法的有穷性),也就是顺递归会逐步缩小规模到寻常。 虽然以下代码也是递归,但由于其无法结束,因此不是一个有效的算法: 12def f(n): return n + f(n - 1) 更多的情况应该是: 123def f(n): if n == 1: return 1 return n + f(n - 1) 练习递归一个简单练习递归的方式是将你写的迭代全部改成递归形式。比如你写了一个程序,功能是“将一个字符串逆序输出”,那么使用迭代将其写出来会非常容易,那么你是否可以使用递归写出来呢?通过这样的练习,可以让你逐步适应使用递归来写程序。 如果你已经对递归比较熟悉了,那么我们继续往下看。 递归中的重复计算递归中可能存在这么多的重复计算,为了消除这种重复计算,一种简单的方式就是记忆化递归。即一边递归一边使用“记录表”(比如哈希表或者数组)记录我们已经计算过的情况,当下次再次碰到的时候,如果之前已经计算了,那么直接返回即可,这样就避免了重复计算。而动态规划中 DP 数组其实和这里“记录表”的作用是一样的。 递归的时间复杂度分析敬请期待我的新书。 小结使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。这里我列举了几道算法题目,这几道算法题目都可以用递归轻松写出来: 递归实现 sum 二叉树的遍历 走楼梯问题 汉诺塔问题 杨辉三角 当你已经适应了递归的时候,那就让我们继续学习动态规划吧! 动态规划如果你已经熟悉了递归的技巧,那么使用递归解决问题非常符合人的直觉,代码写起来也比较简单。这个时候我们来关注另一个问题 - 重复计算 。我们可以通过分析(可以尝试画一个递归树),可以看出递归在缩小问题规模的同时是否可能会重复计算。 279.perfect-squares 中 我通过递归的方式来解决这个问题,同时内部维护了一个缓存来存储计算过的运算,这么做可以减少很多运算。 这其实和动态规划有着异曲同工的地方。 小提示:如果你发现并没有重复计算,那么就没有必要用记忆化递归或者动态规划了。 因此动态规划就是枚举所以可能。不过相比暴力枚举,动态规划不会有重复计算。因此如何保证枚举时不重不漏是关键点之一。 递归由于使用了函数调用栈来存储数据,因此如果栈变得很大,那么会容易爆栈。 爆栈我们结合求和问题来讲解一下,题目是给定一个数组,求出数组中所有项的和,要求使用递归实现。 代码: 123456function sum(nums) { if (nums.length === 0) return 0; if (nums.length === 1) return nums[0]; return nums[0] + sum(nums.slice(1));} 我们用递归树来直观地看一下。 这种做法本身没有问题,但是每次执行一个函数都有一定的开销,拿 JS 引擎执行 JS 来说,每次函数执行都会进行入栈操作,并进行预处理和执行过程,所以内存会有额外的开销,数据量大的时候很容易造成爆栈。 浏览器中的 JS 引擎对于代码执行栈的长度是有限制的,超过会爆栈,抛出异常。 重复计算我们再举一个重复计算的例子,问题描述: 一个人爬楼梯,每次只能爬 1 个或 2 个台阶,假设有 n 个台阶,那么这个人有多少种不同的爬楼梯方法? 由于上第 n 级台阶一定是从 n - 1 或者 n - 2 来的,因此 上第 n 级台阶的数目就是 上 n - 1 级台阶的数目加上 n - 1 级台阶的数目。 递归代码: 12345function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; return climbStairs(n - 1) + climbStairs(n - 2);} 我们继续用一个递归树来直观感受以下: 红色表示重复的计算 可以看出这里面有很多重复计算,我们可以使用一个 hashtable 去缓存中间计算结果,从而省去不必要的计算。 那么动态规划是怎么解决这个问题呢? 答案也是“查表”,不过区别于递归使用函数调用栈,动态规划通常使用的是 dp 数组,数组的索引通常是问题规模,值通常是递归函数的返回值。递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。 如果上面的爬楼梯问题,使用动态规划,代码是这样的: 1234567891011function climbStairs(n) { if (n == 1) return 1; const dp = new Array(n); dp[0] = 1; dp[1] = 2; for (let i = 2; i < n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[dp.length - 1];} 不会也没关系,我们将递归的代码稍微改造一下。其实就是将函数的名字改一下: 12345function dp(n) { if (n === 1) return 1; if (n === 2) return 2; return dp(n - 1) + dp(n - 2);} dp[n] 和 dp(n) 对比看,这样是不是有点理解了呢? 只不过递归用调用栈枚举状态, 而动态规划使用迭代枚举状态。 动态规划的查表过程如果画成图,就是这样的: 虚线代表的是查表过程 这道题目是动态规划中最简单的问题了,因为设计到单个因素的变化,如果涉及到多个因素,就比较复杂了,比如著名的背包问题,挖金矿问题等。 对于单个因素的,我们最多只需要一个一维数组即可,对于如背包问题我们需要二维数组等更高纬度。 爬楼梯我们并没有必要使用一维数组,而是借助两个变量来实现的,空间复杂度是 O(1)。代码: 12345678910111213141516function climbStairs(n) { if (n === 1) return 1; if (n === 2) return 2; let a = 1; let b = 2; let temp; for (let i = 3; i <= n; i++) { temp = a + b; a = b; b = temp; } return temp;} 之所以能这么做,是因为爬楼梯问题的状态转移方程中当前状态只和前两个状态有关,因此只需要存储这两个即可。 动态规划问题有很多这种讨巧的方式,这个技巧叫做滚动数组。 再次强调一下: 如果说递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。 记忆化递归和动态规划没有本质不同。都是枚举状态,并根据状态直接的联系逐步推导求解。 动态规划性能通常更好。 一方面是递归的栈开销,一方面是滚动数组的技巧。 动态规划的三个要素 状态转移方程 临界条件 枚举状态 可以看出,用递归解决也是一样的思路 在上面讲解的爬楼梯问题中,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么: 12f(1) 与 f(2) 就是【边界】f(n) = f(n-1) + f(n-2) 就是【状态转移公式】 我用动态规划的形式表示一下: 12dp[0] 与 dp[1] 就是【边界】dp[n] = dp[n - 1] + dp[n - 2] 就是【状态转移方程】 可以看出两者是多么的相似。 实际上临界条件相对简单,大家只有多刷几道题,里面就有感觉。困难的是找到状态转移方程和枚举状态。这两个核心点的都建立在已经抽象好了状态的基础上。比如爬楼梯的问题,如果我们用 f(n) 表示爬 n 级台阶有多少种方法的话,那么 f(1), f(2), … 就是各个独立的状态。 不过状态的定义都有特点的套路。 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 当然状态转移方程可能不止一个, 不同的转移方程对应的效率也可能大相径庭,这个就是比较玄学的话题了,需要大家在做题的过程中领悟。 搞定了状态的定义,那么我们来看下状态转移方程。 状态转移方程爬楼梯问题由于上第 n 级台阶一定是从 n - 1 或者 n - 2 来的,因此 上第 n 级台阶的数目就是 上 n - 1 级台阶的数目加上 n - 1 级台阶的数目。 上面的这个理解是核心, 它就是我们的状态转移方程,用代码表示就是 f(n) = f(n - 1) + f(n - 2)。 实际操作的过程,有可能题目和爬楼梯一样直观,我们不难想到。也可能隐藏很深或者维度过高。 如果你实在想不到,可以尝试画图打开思路,这也是我刚学习动态规划时候的方法。当你做题量上去了,你的题感就会来,那个时候就可以不用画图了。 状态转移方程实在是没有什么灵丹妙药,不同的题目有不同的解法。状态转移方程同时也是解决动态规划问题中最最困难和关键的点,大家一定要多多练习,提高题感。接下来,我们来看下不那么困难,但是新手疑问比较多的问题 - 如何枚举状态。 如何枚举状态前面说了如何枚举状态,才能不重不漏是枚举状态的关键所在。 如果是一维状态,那么我们使用一层循环可以搞定。 如果是两维状态,那么我们使用两层循环可以搞定。 。。。 这样可以保证不重不漏。 但是实际操作的过程有很多细节比如: 一维状态我是先枚举左边的还是右边的?(从左到右遍历还是从右到左遍历) 二维状态我是先枚举左上边的还是右上的,还是左下的还是右下的? 里层循环和外层循环的位置关系(可以互换么) 。。。 其实这个东西和很多因素有关,很难总结出一个规律,而且我认为也完全没有必要去总结规律。不过这里我还是总结了一个关键点,那就是: 如果你没有使用滚动数组的技巧,那么遍历顺序取决于状态转移方程。比如: 12for i in range(1, n + 1): dp[i] = dp[i - 1] + 1; 那么我们就需要从左到右遍历,原因很简单,因为 dp[i] 依赖于 dp[i - 1],因此计算 dp[i] 的时候, dp[i - 1] 需要已经计算好了。 二维的也是一样的,大家可以试试。 如果你使用了滚动数组的技巧,则怎么遍历都可以,但是不同的遍历意义通常不不同的。比如我将二维的压缩到了一维: 123for i in range(1, n + 1): for j in range(1, n + 1): dp[j] = dp[j - 1] + 1; 这样是可以的。 dp[j - 1] 实际上指的是压缩前的 dp[i][j - 1] 而: 1234for i in range(1, n + 1): # 倒着遍历 for j in range(n, 0, -1): dp[j] = dp[j - 1] + 1; 这样也是可以的。 但是 dp[j - 1] 实际上指的是压缩前的 dp[i - 1][j - 1]。因此实际中采用怎么样的遍历手段取决于题目。我特意写了一个 【完全背包问题】套路题(1449. 数位成本和为目标值的最大数字 文章,通过一个具体的例子告诉大家不同的遍历有什么实际不同,强烈建议大家看看,并顺手给个三连。 关于里外循环的问题,其实和上面原理类似。 这个比较微妙,大家可以参考这篇文章理解一下 0518.coin-change-2。 小结关于如何确定临界条件通常是比较简单的,多做几个题就可以快速掌握。 关于如何确定状态转移方程,这个其实比较困难。 不过所幸的是,这些套路性比较强, 比如一个字符串的状态,通常是 dp[i] 表示字符串 s 以 i 结尾的 ….。 比如两个字符串的状态,通常是 dp[i][j] 表示字符串 s1 以 i 结尾,s2 以 j 结尾的 ….。 这样遇到新的题目可以往上套, 实在套不出那就先老实画图,不断观察,提高题感。 关于如何枚举状态,如果没有滚动数组, 那么根据转移方程决定如何枚举即可。 如果用了滚动数组,那么要注意压缩后和压缩前的 dp 对应关系即可。 动态规划为什么要画表格动态规划问题要画表格,但是有的人不知道为什么要画,就觉得这个是必然的,必要要画表格才是动态规划。 其实动态规划本质上是将大问题转化为小问题,然后大问题的解是和小问题有关联的,换句话说大问题可以由小问题进行计算得到。这一点是和用递归解决一样的, 但是动态规划是一种类似查表的方法来缩短时间复杂度和空间复杂度。 画表格的目的就是去不断推导,完成状态转移, 表格中的每一个 cell 都是一个小问题, 我们填表的过程其实就是在解决问题的过程, 我们先解决规模为寻常的情况,然后根据这个结果逐步推导,通常情况下,表格的右下角是问题的最大的规模,也就是我们想要求解的规模。 比如我们用动态规划解决背包问题, 其实就是在不断根据之前的小问题A[i - 1][j] A[i -1][w - wj]来询问: 应该选择它 还是不选择它 至于判断的标准很简单,就是价值最大,因此我们要做的就是对于选择和不选择两种情况分别求价值,然后取最大,最后更新 cell 即可。 其实大部分的动态规划问题套路都是“选择”或者“不选择”,也就是说是一种“选择题”。 并且大多数动态规划题目还伴随着空间的优化(滚动数组),这是动态规划相对于传统的记忆化递归优势的地方。除了这点优势,就是上文提到的使用动态规划可以减少递归产生的函数调用栈,因此性能上更好。 相关问题 0091.decode-ways 0139.word-break 0198.house-robber 0309.best-time-to-buy-and-sell-stock-with-cooldown 0322.coin-change 0416.partition-equal-subset-sum 0518.coin-change-2 总结本篇文章总结了算法中比较常用的两个方法 - 递归和动态规划。递归的话可以拿树的题目练手,动态规划的话则将我上面推荐的刷完,再考虑去刷力扣的动态规划标签即可。 大家前期学习动态规划的时候,可以先尝试使用记忆化递归解决。然后将其改造为动态规划,这样多练习几次就会有感觉。之后大家可以练习一下滚动数组,这个技巧很有用,并且相对来说比较简单。 比较动态规划的难点在于枚举所以状态(无重复) 和 寻找状态转移方程。 如果你只能记住一句话,那么请记住:递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。 另外,大家可以去 LeetCode 探索中的 递归 I 中进行互动式学习。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"TypeScript 配置文件该怎么写?","slug":"ts-config","date":"2020-08-23T16:00:00.000Z","updated":"2023-01-07T12:34:34.455Z","comments":true,"path":"2020/08/24/ts-config/","link":"","permalink":"https://lucifer.ren/blog/2020/08/24/ts-config/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(已发布) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写?(就是本文) TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(已发布) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写?(就是本文) TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言这篇文章是我的 TypeScript 系列的第 5 篇。今天我们就来看下, TypeScript 的配置文件 tsconfig.json 该如何写。 和 package.json 一样, 它也是一个 JSON 文件。package.json 是包描述文件,对应的 Commonjs 规范,而 tsconfig.json 是最终被 TypeScript Compiler 解析和使用的一个 JSON 文件。 TypeScript Compiler 用这个配置文件来决定如何对项目进行编译。 说到编译,不得不提一个知名选手 - babel。 和 TypeScript 类似, 他们都可以将一种语法静态编译成另外一种语法。如果说我想编译一个文件,我只需要告诉 babel 我的文件路径即可。 1npx babel script.js 有时候我想编译整个文件夹: 1npx babel src --out-dir lib babel 也可以指定输出目录,指定需要忽略的文件或目录等等, TypeScript 也是一样!你当然可以像 babel 一样在命令行中全部指定好,也可以将这些配置放到 tsconfig.json 中,以配置文件的形式传递给 TypeScript Compiler 。 这就是 tsconfig.json 文件的初衷,即接受用户输入作为配置项。 初探 tsconfig我们先来看一个简单的 tsconfig 文件。 12345678{ \"compilerOptions\": { \"outDir\": \"./built\", \"allowJs\": true, \"target\": \"es5\" }, \"include\": [\"./src/**/*\"]} 如上配置做了: 读取所有可识别的 src 目录下的文件(通过 include)。 接受 JavaScript 做为输入(通过 allowJs)。 生成的所有文件放在 built 目录下(通过 outDir)。 将 JavaScript 代码降级到低版本比如 ECMAScript 5(通过 target)。 实际项目有比这个更复杂。 接下来, 我们来进一步解读。 不过在讲配置项之前,我们先来看下 tsconfig.json 是如何被解析的。 tsconfig 是如何被解析的?如果一个目录下存在一个 tsconfig.json 文件,那么意味着这个目录是 TypeScript 项目的根目录。 如果你使用 tsc 编译你的项目,并且没有显式地指定配置文件的路径,那么 tsc 则会逐级向上搜索父目录寻找 tsconfig.json ,这个过程类似 node 的模块查找机制。 如图: 在 _uglify-js@3.7.2@uglify-js 下执行 tsc 则会找到 配置文件 1,在 _uglify-js@3.7.2@uglify-js/bin 下执行 tsc 也会找到 配置文件 1 同理在 lib,node_modules 也会找到 配置文件 1 在 _uglify-js@3.7.2@uglify-js/bin/lucifer 下执行 tsc 则会找到 配置文件 2 在 _uglify-js@3.7.2@uglify-js/lib/lucifer 下执行 tsc 则会找到 配置文件 3 我在 上帝视角看 TypeScript 一种讲述了 TypeScript 究竟做了什么,带你从宏观的角度看了一下 TypeScript。 其中提到了 TypeScript 编译器会接受文件或者文件集合作为输入,最终转换为 JavaScript(noEmit 为 false) 和 .d.ts(declarations 为 true)。 这里其实还少了一个点,那就是除了接受文件或者文件集合作为输入,还会接受 tsconfig.json。tsconfig.json 的内容决定了编译的范围和行为,不同的 配置可能会得到不同的输出,或者得到不同的检查结果。 当 tsc 找到了一个 tsconfig.json 文件,那么其规定的编译目录则全部会被 typescript 处理,当然也包括其依赖的文件。 如果 tsc 没有找到一个 tsconfig.json 或 tsconfig 没有有效信息,那么 tsc 会使用默认配置。 比如 tsconfig 是一个空的就没有有效信息: 1{} tsconfig 的全部属性,以及属性的默认值可以在这里找到: http://json.schemastore.org/tsconfig 总结一下 tsc 解析 tsconfig.json 的逻辑。 如果命令行指定了配置选项或者指定了配置文件的路径,那么直接会读取。 根据 tsconfig json schema 校验是否格式正确。 如果正确,则将其和默认配置合并(如果有 extends 字段,也会一起合并),将合并后的配置传递给 TypeScript 编译器并开始编译。 否则抛出错误 否则,会从当前目录查找 tsconfig.json 文件, 如果找不到则逐层向上搜索父目录。 如果找到了则会去根据 tsconfig json schema 校验是否格式正确。 如果正确,则将其和默认配置合并(如果有 extends 字段,也会一起合并),将合并后的配置传递给 TypeScript 编译器并开始编译。 否则抛出错误 否则,始终找不到则直接使用默认配置 tsconfig 的顶层属性tsconfig 的顶层属性(Top Level)不多,主要有:compilerOptions, files, include, exclude,extends,compileOnSave等。 compilerOptions 是重头戏,其属性也是最多的,我们的项目也是对这个定制比较多,这个我后面会重点讲。 files 则是你需要编译的文件 exclude 则是你不需要编译的文件目录(支持 glob) include 是你需要编译的文件目录(支持 glob) extends 就是继承另外一个配置文件,TypeScript 会对其进行合并,多项目公共配置有用。你也可以直接继承社区的“最佳实践”,比如: 12345678{ \"extends\": \"@tsconfig/node12/tsconfig.json\", \"compilerOptions\": {}, \"include\": [\"src/**/*\"], \"exclude\": [\"node_modules\"]} compileOnSave 则是和编辑器(确切地说是文件系统)联动的配置,即是否在文件保存后进行编译,实际项目不建议使用。 除了 compilerOptions,其他也相对比较好理解。 因此接下来我只针对 compilerOptions 详细讲解一番。 tsconfig 的编译项详细全面的内容,大家只需要参考官网的就好了。官网写的不仅全面,而且做了分类,非常清晰。 接下来,我会根据功能分开讲几个常用 的配置。 文件相关常用的是以下四个,由于前面已经做了介绍,因此就不赘述了。 exclude extends files include 严格检查 alwaysStrict 默认:false 首次发布版本:2.1 这个是和 ECMAScript 规范相关的,工作机制和 ES 5 的严格模式一样, 并且输出的 JS 顶部也会也会带上 ‘use strict’。 noImplicitAny(推荐打开) 默认:true 首次发布版本:- 我在 - TypeScript 类型系统 中提到了如果不对变量显式声明类型,那么 TypeScript 会对变量进行类型推导,这当然也有推导不出的情况,这个时候该变量的类型就是 any,这个叫做隐式 any。区别于显式 any: 1const a: any = {}; 隐式 any 是 TypeScript 编译器推断的。 noImplicitThis(推荐打开) 默认:true 首次发布版本:2.0 和隐式 any 类型, 只不过这次是针对的特殊的一个关键字 this,也就是你需要显式地指定 this 的类型。 strict(推荐打开) 默认:true 首次发布版本:2.3 实际上 strict 只是一个简写,是多个规则的合集。 类似于 babel 中插件(plugins)和 预设(presets)的差别。换句话说如果你指定了 strict 为 true ,那么所有严格相关的规则的都会开启,我所讲的严格检查都是,还有一部分我没有提到的。另外将来如果增加更多严格规则,你只要开启了 strict 则会自动加进来。 模块解析模块相关目的:allowSyntheticDefaultImports,allowUmdGlobalAccess,esModuleInterop,moduleResolution 都是为了和其他模块化规范兼容做的。 allowSyntheticDefaultImports allowUmdGlobalAccess esModuleInterop moduleResolution 还有一个配置 module,规定了项目的模块化方式,选项有 AMD,UMD,commonjs 等。 路径相关目的: baseUrl,paths,rootDirs, typeRoots,types 都是为了简化路径的拼写做的。 baseUrl 这个配置是告诉 TypeScript 如何解析模块路径的。比如: 123import { helloWorld } from \"hello/world\";console.log(helloWorld); 这个就会从 baseUrl 下找 hello 目录下的 world 文件。 paths 定义类似别名的存在,从而简化路径的书写。 rootDirs 注意是 rootDirs ,而不是 rootDir,也就是说根目录可以有多个。 当你指定了多个根目录的时候, 不同根目录的文件可以像在一个目录下一样互相访问。 实际上也有一个叫 rootDir 的, 和 rootDirs 的区别就是其只能指定一个。 typeRoots types types 和 typeRoots 我在 - types 和 @types 是什么? 已经讲得很清楚了,这里就不多说了。 项目配置JavaScript 相关 allowJs 默认:false 首次发布版本:1.8 顾名思义,允许在 TypeScript 项目中使用 JavaScript,这在从 JavaScript 迁移到 TypeScript 中是非常重要的。 checkJs 默认:false 首次发布版本:- 和 allowJs 类似, 只不过 checkJs 会额外对 JS 文件进行校验。 声明文件相关如果 TypeScript 是将 TS 文件编译为 JS,那么声明文件 + JS 文件就可以反推出 TS 文件。 这两个用来生成 .d.ts 和 .d.ts 的 sourcemap 文件。 declaration 默认:false 首次发布版本:1.0 declarationMap 默认:false 首次发布版本:2.9 外部库相关 jsx 默认:react 首次发布版本:2.2 这个是告诉 TypeScript 如何编译 jsx 语法的。 lib 默认:- 首次发布版本:2.0 lib 我在 TypeScript 类型系统 中讲过。 Typescript 提供了诸如 lib.d.ts 等类型库文件。随着 ES 的不断更新, JavaScript 类型和全局变量会逐渐变多。Typescript 也是采用这种 lib 的方式来解决的。 (TypeScript 提供的部分 lib) 输出相关outDir 和 outFile 这两个配置则是告诉 TypeScript 将文件生成到哪里。 outDir 默认:和 ts 文件同目录(且同名,只是后缀不同) 首次发布版本:- outFile 默认:- 首次发布版本:1.0 module 是 CommonJS 和 ES6 module 不能知道 outFile,只有是 None, System 或 AMD 才行,其会将这些模块的文件内容打包到全局文件内容之后。 而 noEmit 则是控制是否输出 JS 文件的。 noEmit 默认:false 首次发布版本:- 如果你只希望用 TypeScript 进行类型检查,不希望要它生成文件,则可以将 noEmit 设置成 true。 target 即输出的 JavaScript 对标的 ECMA 规范。 比如 “target”: “es6” 就是将 es6 + 的语法转换为 ES6 的 代码。其选项有 ES3,ES5,ES6 等。 为什么没有 ES4 ? ^_^ 总结 tsconfig 就是一个 JSON 文件,TypeScript 会使用该文件来决定如何编译和检查 TypeScript 项目。和 babel 类似,甚至很多配置项都是相通的。 如果一个目录下存在一个 tsconfig.json 文件,那么意味着这个目录是 TypeScript 项目的根目录。 如果你使用 tsc 编译你的项目,并且没有显式地指定配置文件的路径,那么 tsc 则会逐级向上搜索父目录寻找 tsconfig.json ,这个过程类似 node 的模块查找机制。 tsconfig 中最重要的恐怕就是编译器选项(compilerOptions)了。如果你按照功能去记忆则会比较简单, 比如文件相关的有哪些, 严格检查的有哪些,声明文件的有哪些等等。 参考 typescriptlang’s tsconfig 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"types 和 @types 是什么?","slug":"ts-type","date":"2020-08-20T16:00:00.000Z","updated":"2023-01-07T12:34:34.563Z","comments":true,"path":"2020/08/21/ts-type/","link":"","permalink":"https://lucifer.ren/blog/2020/08/21/ts-type/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在_逻辑上_比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(就是本文) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在_逻辑上_比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(已发布) types 和 @types 是什么?(就是本文) 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言 作者:feiker & Lucifer TypeScript 中有几个概念和名字很像,会让初学者傻傻分不清楚。比如配置文件中的 _types 和 typeRoots_,并且还有一个 @types。接触过 TypeScript 的人一定接触过它们, 这几个有什么区别和联系呢?今天就带你来重新认识下它们。 一个例子这里我通过一个例子来说明一下什么是 @types,这样大家理解起来更深刻一点。 当我们用 npm 等包管理工具安装第三方包的时候,有些包并不是 TypeScript 编写的,自然也不会导出 TypeScript 声明文件。这种情况下,如果我们在 TypeScript 项目中引入了这种包,则会编译报错(没有设置 allowJS)。举个例子,当我们通过npm install jquery --save 安装 jquery 包并引用的时候,TypeScript 会报错。 allowJS 是 TypeScript 1.8 引进的一个编译项。 报错内容如下: Could not find a declaration file for module ‘jquery’. Try npm install @types/jquery if it exists or add a new declaration (.d.ts) file containing declare module 'jquery'; 这里的意思是 TypeScript 没有找到 jquery 这个包的定义,你可以通过npm install @types/jquery安装相关声明,或者自己定义一份.d.ts 文件,并将 jquery 声明为 module。 全世界不是 TypeScript 编写的包多了去了。即使你的包是 TypeScript 编写的,如果你没有导出声明文件,也是没用的。(TypeScript 默认不会导出声明文件,只会编译输出 JavaScript 文件)。因此 TypeScript 必须对这种情况提供解决方案,而上面的两种方案(安装 @types 和 自己 declare module)就是 TypeScript 官方提出的, 你可以选择适合你的方案。我的推荐是尽量使用 @types 下的声明,实在没有,再使用第二种方法。 值得一提的是,并不是所有的包都可以通过这种方式解决的, 能解决的是 DefinitelyTyped 组织已经写好定义的包, 好消息是比较流行的包基本都有。 如果你想查一个包是否在 @type 下,可以访问 https://microsoft.github.io/TypeSearch/ 那么 TypeScript 是怎么找定义的,什么情况会找不到定义而报类似上面举的例子的错误,这里简单介绍下原理。 包类型定义的查找就好像 node 的包查找是先在当前文件夹找 node_modules,在它下找递归找,如果找不到则往上层目录继续找,直到顶部一样, TypeScript 类型查找也是类似的方式。 具体来说就是: TypeScript 编译器先在当前编译上下文找 jquery 的定义。 如果找不到,则会去 node_modules 中的@types (默认情况,目录可以修改,后面会提到)目录下去寻找对应包名的模块声明文件。 @types/*模块声明文件由社区维护,通过发布到@types 空间下。 GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions. 变量类型定义的查找和包查找类似,默认情况下变量类型定义的查找也会去 @types 下去寻找。只不过并不是直接去 @types 找,而是有一定的优先级, 这个过程类似原型链或者作用域链。 比如如下代码: 1const user: User = { name: \"lucifer\" }; Typescript 则会先在本模块查找 User 的定义。 如果找到,则直接返回。 如果找不到, 则会到全局作用域找,而这个全局默认就是指的就是 @types 下的所有类型定义。(注意目录页是可以配的) 也就是说 @types 下的定义都是全局的。当然你可以导入 @types 下导出的定义,使得它们的作用域变成你的模块内部。 typeRoots 与 types前面说了 TypeScript 会默认引入node_modules下的所有@types声明,但是开发者也可以通过修改tsconfig.json的配置来修改默认的行为. tsconfig.json 中有两个配置和类型引入有关。 typeRoots: 用来指定默认的类型声明文件查找路径,默认为node_modules/@types, 指定typeRoots后,TypeScript 编译器会从指定的路径去引入声明文件,而不是node_modules/@types, 比如以下配置会从typings路径下去搜索声明 12345{ \"compilerOptions\": { \"typeRoots\": [\"./typings\"] }} types: TypeScript 编译器会默认引入typeRoot下所有的声明文件,但是有时候我们并_不希望全局引入所有定义_,而是仅引入部分模块。这种情景下可以通过types指定模块名只引入我们想要的模块,比如以下只会引入 jquery 的声明文件 12345{ \"compilerOptions\": { \"types\": [\"jquery\"] }} 总结 typeRoots 是 tsconfig 中 compilerOptions 的一个配置项,typeRoots 下面的包会被 ts 编译器自动包含进来,typeRoots 默认指向 node_modules/@types。 @types 是 npm 的 scope 命名空间,和@babel 类似,@types 下的所有包会默认被引入,你可以通过修改 compilerOptions 来修改默认策略。 types 和 typeRoots 一样也是 compilerOptions 的配置,指定 types 后,typeRoots 下只有被指定的包才会被引入。 参考 GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions. @types | 深入理解 TypeScript tsconfig.json · TypeScript 中文网 · TypeScript——JavaScript 的超集 理解 Typescript 配置文件 - 个人文章 - SegmentFault 思否 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"值得关注的技术类大会","slug":"tech-conf","date":"2020-08-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.645Z","comments":true,"path":"2020/08/20/tech-conf/","link":"","permalink":"https://lucifer.ren/blog/2020/08/20/tech-conf/","excerpt":"作为一个技术人,怎么能不参加和关注几场技术大会呢?让我们来看下那些你不能错过的技术大会吧。","text":"作为一个技术人,怎么能不参加和关注几场技术大会呢?让我们来看下那些你不能错过的技术大会吧。 JSConf地址 React Conf地址 Google IO地址 D2地址 QCon地址 更多技术大会 https://juejin.im/events https://segmentfault.com/events https://www.huodongxing.com https://www.bagevent.com https://www.hdb.com https://www.meetup.com … 欢迎大家补充~","categories":[{"name":"技术大会","slug":"技术大会","permalink":"https://lucifer.ren/blog/categories/技术大会/"},{"name":"JSConf","slug":"技术大会/JSConf","permalink":"https://lucifer.ren/blog/categories/技术大会/JSConf/"},{"name":"React Conf","slug":"技术大会/React-Conf","permalink":"https://lucifer.ren/blog/categories/技术大会/React-Conf/"},{"name":"Google IO","slug":"技术大会/Google-IO","permalink":"https://lucifer.ren/blog/categories/技术大会/Google-IO/"},{"name":"D2","slug":"技术大会/D2","permalink":"https://lucifer.ren/blog/categories/技术大会/D2/"},{"name":"QCon","slug":"技术大会/QCon","permalink":"https://lucifer.ren/blog/categories/技术大会/QCon/"}],"tags":[{"name":"技术大会","slug":"技术大会","permalink":"https://lucifer.ren/blog/tags/技术大会/"},{"name":"JSConf","slug":"JSConf","permalink":"https://lucifer.ren/blog/tags/JSConf/"},{"name":"Google IO","slug":"Google-IO","permalink":"https://lucifer.ren/blog/tags/Google-IO/"},{"name":"D2","slug":"D2","permalink":"https://lucifer.ren/blog/tags/D2/"},{"name":"QCon","slug":"QCon","permalink":"https://lucifer.ren/blog/tags/QCon/"}]},{"title":"力扣刷题插件","slug":"leetcode-cheat","date":"2020-08-15T16:00:00.000Z","updated":"2023-01-07T12:34:34.356Z","comments":true,"path":"2020/08/16/leetcode-cheat/","link":"","permalink":"https://lucifer.ren/blog/2020/08/16/leetcode-cheat/","excerpt":"之前我做了一个视频, 介绍我的刷题浏览器扩展插件,视频地址:https://www.bilibili.com/video/BV1UK4y1x7zj/。 今天我在上次的基础上增加了部分公司的显示以及优化了若干体验功能。","text":"之前我做了一个视频, 介绍我的刷题浏览器扩展插件,视频地址:https://www.bilibili.com/video/BV1UK4y1x7zj/。 今天我在上次的基础上增加了部分公司的显示以及优化了若干体验功能。 这个刷题插件能做什么?题解模板(新功能)为了方便大家写出格式良好的题解,插件现在内置题解模板功能,目前模板只有一套,这套模板是我经常使用的题解模板。 安装好我们的插件(版本需要 v0.8.0 及以上)后,打开力扣中文版,会发现如下的按钮。 点击之后会自动引导你到一个新的页面, 该页面的题解语言,题目地址和题目名称信息会自动填充。 你可以快速完成时间复杂度,空间复杂度的插入,复杂度已经按照性能好坏的顺序给大家排好了,点击即可插入。 此外我们提供了若干常用的公式供你快速复制使用。除了公式,其他内容都可以在右侧的预览区域查看。 写完只会可以点击复制,将其复制到其他地方以便持久化存储。由于我们没有做持久化存储,因此页面刷新内容就会消失哦。 最后祝大家写出漂亮的题解! 数据结构可视化你可以使用 canvas 自由绘制各种数据结构,不管是写题解还是帮助理解题目都很有用。 我们提供了很多内置的模板供你快速上手。 如果你对内置的模板不满意,也可以将自己的模板保存以便下次使用。 学习路线算法怎么学?推荐按专题来。具体到某一个专题怎么学?这里提供了一个学习路线帮助你。本功能旨在将一个专题中的题目进行分类。专题本质就是对题目的一种划分,学习路线基于专题又进行了一次划分。 复杂度分析你的代码能会超时么?复杂度分析帮助你。 一键复制所有内置测试用例省去了一个个手动复制的过程,效率翻倍! 模板提供了大量的经过反复验证的模板。模板的作用是在你理解了问题的基础上,快速书写,并减少出错概率,即使出错,也容易 debug。 禅定模式 点击之后会变成这样: 底部控制台会消失,当你鼠标重新移过来或者退出禅定模式就出现了。 查看题解当你在任意非题目详情页或者我还没有收录的题目详情页的时候, 我都会列出当前我总结的所有题解。 其实我给比较经典的题目做了题解,因此这个题目数目不是很多,目前是 173 道题。另外有时候我直接写了专题,没有单独给每道题写题解,因此数量上要比 173 多很多。 当你进到一个我写了题解的题目详情页的时候, 你就可以正式使用我的插件了。 它可以: 给出这道题目的前置知识。换句话说就是我需要先掌握什么才能做出这道题。 这个题目的关键点。 哪些公司出了这道题。 我实在不会了,给我看看题解吧。好,满足你。 题解我就不看了,直接 show me code 吧。好,满足你。 根据公司,查找题目。面试突击必备 其他功能 刷题插件可以隐藏测试用例啦 有了这个可视化插件,刷题调试更轻松 力扣刷题插件近期更新盘点 我怎么才能获取呢?公众号《力扣加加》后台回复刷题插件即可。 如何离线安装 将下载的压缩包解压 在 Chrome 浏览器的地址栏输入 chrome://extensions/ 点击 load uppack 不知道中文是什么名字,反正就是上面三个按钮最左边的。 选择你解压之后的文件夹 出现下面这个就说明你安装成功了,点一下试试吧。 后期的规划是怎么样的? 后期的功能计划先对 91 活动的用户开发。关于 91 活动,大家可以关注我的公众号《力扣加加》了解详情。 更多公司信息。 持续完善题目的公司信息,这个过程需要大家的帮助,大家可以把自己面试遇到的问题发给我(附带公司和岗位信息),我可以免费提供咨询服务。 岗位信息。 这个过程同样需要大家的帮助,大家可以把自己面试遇到的问题发给我(附带公司和岗位信息),我可以免费提供咨询服务。 可视化调试。 可视化展示你的代码允许情况。 (一个双指针题目的可视化调试过程) 自动制定复习计划。 AI 智能提示。即新的提示也可以根据题目信息推测可能的解法。 等等 关注更新大家可以关注我的公众号, 如果插件有更新,会第一时间在公众号同步的哦~ 想看题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/categories/插件/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"插件","slug":"插件","permalink":"https://lucifer.ren/blog/tags/插件/"},{"name":"刷题","slug":"刷题","permalink":"https://lucifer.ren/blog/tags/刷题/"}]},{"title":"TypeScript 类型系统","slug":"ts-type-system","date":"2020-08-14T16:00:00.000Z","updated":"2023-01-07T12:34:34.562Z","comments":true,"path":"2020/08/15/ts-type-system/","link":"","permalink":"https://lucifer.ren/blog/2020/08/15/ts-type-system/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(就是本文) types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 系列安排: 上帝视角看 TypeScript(已发布) TypeScript 类型系统(就是本文) types 和 @types 是什么? 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? TypeScript 练习题 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 前言上一节的上帝视角看 TypeScript,我们从宏观的角度来对 Typescript 进行了一个展望。之所以把那个放到开头讲是让大家有一个大体的认识,不想让大家一叶障目。当你对整个宏观层面有了一定的了解,那么对 Typescript 的理解就不会错太多。相反,一开始就是具体的概念和 API,则很可能会让你丧失都整体的基本判断。 实际上, Typescript 一直在不断更新迭代。一方面是因为当初许下的诺言”Typescript 是 JavaScript 的超集“(JavaScript 的特性你要同步支持,同时也要处理各种新语法带来的不兼容情况)。不单是 ECMA,社区的其他发展可能也会让 Typescript 很难受。 比如 JSX 的广泛使用就给 Typescript 泛型的使用带来了影响。 TypeScript 一直处于高速的迭代。除了修复日常的 bug 之外,TypeScript 也在不断发布新的功能,比如最新 4.0.0 beta 版本的标签元祖 的功能就对智能提示这块很有用。Typescript 在社区发展方面也做的格外好,以至于它的竞争对手 Flow 被 Typescript 完美击败,这在很大程度上就是因为 Typescript 没有烂尾。如今微软在开源方向的发力是越来越显著了,我很期待微软接下来的表现,让我们拭目以待。 变量类型和值类型有的同学可能有疑问, JavaScript 不是也有类型么? 它和 Typescript 的类型是一回事么?JavaScript 不是动态语言么,那么经过 Typescript 的限定会不会丧失动态语言的动态性呢?我们继续往下看。 JavaScript 中的类型其实是值的类型。实际上不仅仅是 JavaScript,任何动态类型语言都是如此,这也是动态类型语言的本质。 Typescript 中的类型其实是变量的类型。实际上不仅仅是 Typescript,任何静态类型语言都是如此,这也是静态类型语言的本质。 记住这两句话,我们接下来解释一下这两句话。 对于 JavaScript 来说,一个变量可以是任意类型。 1234var a = 1;a = \"lucifer\";a = {};a = []; 上面的值是有类型的。比如 1 是 number 类型,”lucifer” 是字符串类型, {} 是对象类型, [] 是数组类型。而变量 a 是没有固定类型的。 对于 Typescript 来说, 一个变量只能接受和它类型兼容的类型的值。说起来比较拗口, 看个例子就明白了。 123456var a: number = 1;a = \"lucifer\"; // errorvar b: any = 1;b = \"lucifer\"; // okb = {}; // okb = []; // ok 我们不能将 string 类型的值赋值给变量 a, 因为 string 和 number 类型不兼容。而我们可以将 string,Object,Array 类型的值赋值给 b,因此 它们和 any 类型兼容。简单来说就是,一旦一个变量被标注了某种类型,那么其就只能接受这个类型以及它的子类型。 类型空间和值空间类型和值居住在不同的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。类型不能当做值来用,反之亦然。 类型空间如下代码会报类型找不到的错: 1const aa: User = { name: \"lucifer\", age: 17 }; 这个比较好理解,我们只需要使用 interface 声明一下 User 就行。 123456interface User { name: string; age: number;}const aa: User = { name: \"lucifer\", age: 17 }; 也就是说使用 interface 可以在类型空间声明一个类型,这个是 Typescript 的类型检查的基础之一。 实际上类型空间内部也会有子空间。我们可以用 namespace(老)和 module(新) 来创建新的子空间。子空间之间不能直接接触,需要依赖导入导出来交互。 值空间比如,我用 Typescript 写出如下的代码: 1const a = window.lucifer(); Typescript 会报告一个类似Property 'lucifer' does not exist on type 'Window & typeof globalThis'. 的错误。 实际上,这种错误并不是类型错误,而是找不到成员变量的错误。我们可以这样解决: 1declare var lucifer: () => any; 也就是说使用 declare 可以在值空间声明一个变量。这个是 Typescript 的变量检查的基础,不是本文要讲的主要内容,大家知道就行。 明白了 JavaScript 和 TypeScript 类型的区别和联系之后,我们就可以来进入我们本文的主题了:类型系统。 类型系统是 TypeScript 最主要的功能TypeScript 官方描述中有一句:TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications。实际上这也正是 Typescript 的主要功能,即给 JavaScript 添加静态类型检查。要想实现静态类型检查,首先就要有类型系统。总之,我们使用 Typescript 的主要目的仍然是要它的静态类型检查,帮助我们提供代码的扩展性和可维护性。因此 Typescript 需要维护一套完整的类型系统。 类型系统包括 1. 类型 和 2.对类型的使用和操作,我们先来看类型。 类型TypeScript 支持 JavaScript 中所有的类型,并且还支持一些 JavaScript 中没有的类型(毕竟是超集嘛)。没有的类型可以直接提供,也可以提供自定义能力让用户来自己创造。 那为什么要增加 JavaScript 中没有的类型呢?我举个例子,比如如下给一个变量声明类型为 Object,Array 的代码。 12const a: Object = {};const b: Array = []; 其中: 第一行代码 Typescript 允许,但是太宽泛了,我们很难得到有用的信息,推荐的做法是使用 interface 来描述,这个后面会讲到。 第二行 Typescript 则会直接报错,原因的本质也是太宽泛,我们需要使用泛型来进一步约束。 对类型的使用和操作上面说了类型和值居住在不同的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。 使用 declare 和 interface or type 就是分别在两个空间编程。比如 Typescript 的泛型就是在类型空间编程,叫做类型编程。除了泛型,还有集合运算,一些操作符比如 keyof 等。值的编程在 Typescript 中更多的体现是在类似 lib.d.ts 这样的库。当然 lib.d.ts 也会在类型空间定义各种内置类型。我们没有必要去死扣这个,只需要了解即可。 lib.d.ts 的内容主要是一些变量声明(如:window、document、math)和一些类似的接口声明(如:Window、Document、Math)。寻找代码类型(如:Math.floor)的最简单方式是使用 IDE 的 F12(跳转到定义)。 类型是如何做到静态类型检查的?TypeScript 要想解决 JavaScript 动态语言类型太宽松的问题,就需要: 提供给变量设定类型的能力 注意是变量,不是值。 提供常用类型(不必须,但是没有用户体验会极差)并可以扩展出自定义类型(必须)。 根据第一步给变量设定的类型进行类型检查,即不允许类型不兼容的赋值, 不允许使用值空间和类型空间不存在的变量和类型等。 第一个点是通过类型注解的语法来完成。即类似这样: 1const a: number = 1; Typescript 的类型注解是这样, Java 的类型注解是另一个样子,Java 类似 int a = 1。 这个只是语法差异而已,作用是一样的。 第二个问题, Typescript 提供了诸如 lib.d.ts 等类型库文件。随着 ES 的不断更新, JavaScript 类型和全局变量会逐渐变多。Typescript 也是采用这种 lib 的方式来解决的。 (TypeScript 提供的部分 lib) 第三个问题,Typescript 主要是通过 interface,type,函数类型等打通类型空间,通过 declare 等打通值空间,并结合 binder 来进行类型诊断。关于 checker ,binder 是如何运作的,可以参考我第一篇的介绍。 接下来,我们介绍类型系统的功能,即它能为我们带来什么。如果上面的内容你已经懂了,那么接下来的内容会让你感到”你也不过如此嘛“。 类型系统的主要功能 定义类型以及其上的属性和方法。 比如定义 String 类型, 以及其原型上的方法和属性。 length, includes 以及 toString 是 String 的成员变量, 生活在值空间, 值空间虽然不能直接和类型空间接触,但是类型空间可以作用在值空间,从而给其添加类型(如上图黄色部分)。 提供自定义类型的能力 12345interface User { name: string; age: number; say(name: string): string;} 这个是我自定义的类型 User,这是 Typescript 必须提供的能力。 类型兼容体系。 这个主要是用来判断类型是否正确的,上面我已经提过了,这里就不赘述了。 类型推导 有时候你不需要显式说明类型(类型注解),Typescript 也能知道他的类型,这就是类型推导结果。 1const a = 1; 如上代码,编译器会自动推导出 a 的类型 为 number。还可以有连锁推导,泛型的入参(泛型的入参是类型)推导等。类型推导还有一个特别有用的地方,就是用到类型收敛。 接下来我们详细了解下类型推导和类型收敛。 类型推导和类型收敛1let a = 1; 如上代码。 Typescript 会推导出 a 的类型为 number。 如果只会你这么写就会报错: 1a = \"1\"; 因此 string 类型的值不能赋值给 number 类型的变量。我们可以使用 Typescript 内置的 typeof 关键字来证明一下。 12let a = 1;type A = typeof a; 此时 A 的类型就是 number,证明了变量 a 的类型确实被隐式推导成了 number 类型。 有意思的是如果 a 使用 const 声明,那么 a 不会被推导为 number,而是推导为类型 1。即值只能为 1 的类型,这就是类型收敛。 12const a = 1;type A = typeof a; 通过 const ,我们将 number 类型收缩到了 值只能为 1 的类型。 实际情况的类型推导和类型收敛要远比这个复杂, 但是做的事情都是一致的。 比如这个: 1234function test(a: number, b: number) { return a + b;}type A = ReturnType<typeof test>; A 就是 number 类型。 也就是 Typescript 知道两个 number 相加结果也是一个 number。因此即使你不显示地注明返回值是 number, Typescript 也能猜到。这也是为什么 JavaScript 项目不接入 Typescript 也可以获得类型提示的原因之一。 除了 const 可以收缩类型, typeof, instanceof 都也可以。 原因很简单,就是Typescript 在这个时候可以 100% 确定你的类型了。 我来解释一下: 比如上面的 const ,由于你是用 const 声明的,因此 100% 不会变,一定永远是 1,因此类型可以收缩为 1。 再比如: 12345let a: number | string = 1;a = \"1\";if (typeof a === \"string\") { a.includes;} if 语句内 a 100% 是 string ,不能是 number。因此 if 语句内类型会被收缩为 string。instanceof 也是类似,原理一模一样。大家只要记住Typescript 如果可以 100% 确定你的类型,并且这个类型要比你定义的或者 Typescript 自动推导的范围更小,那么就会发生类型收缩就行了。 总结本文主要讲了 Typescript 的类型系统。 Typescript 和 JavaScript 的类型是很不一样的。从表面上来看, TypeScript 的类型是 JavaScript 类型的超集。但是从更深层次上来说,两者的本质是不一样的,一个是值的类型,一个是变量的类型。 Typescript 空间分为值空间和类型空间。两个空间不互通,因此值不能当成类型,类型不能当成值,并且值和类型不能做运算等。不过 TypeScript 可以将两者结合起来用,这个能力只有 TypeScript 有, 作为 TypeScript 的开发者的你没有这个能力,这个我在第一节也简单介绍了。 TypeScript 既会对变量存在与否进行检查,也会对变量类型进行兼容检查。因此 TypeScript 就需要定义一系列的类型,以及类型之间的兼容关系。默认情况,TypeScript 是没有任何类型和变量的,因此你使用 String 等都会报错。TypeScript 使用库文件来解决这个问题,最经典的就是 lib.d.ts。 TypeScript 已经做到了足够智能了,以至于你不需要写类型,它也能猜出来,这就是类型推导和类型收缩。当然 TypeScript 也有一些功能,我们觉得应该有,并且也是可以做到的功能空缺。但是我相信随着 TypeScript 的逐步迭代(截止本文发布,TypeScript 刚刚发布了 4.0.0 的 beta 版本),一定会越来越完善,用着越来越舒服的。 我们每个项目的需要是不一样的, 简单的基本类型肯定无法满足多样的项目需求,因此我们必须支持自定义类型,比如 interface, type 以及复杂一点的泛型。当然泛型很大程度上是为了减少样板代码而生的,和 interface , type 这种刚需不太一样。 有了各种各样的类型以及类型上的成员变量,以及成员变量的类型,再就加上类型的兼容关系,我们就可以做类型检查了,这就是 TypeScript 类型检查的基础。TypeScript 内部需要维护这样的一个关系,并对变量进行类型绑定,从而给开发者提供类型分析服务。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"想去力扣当前端,TypeScript 需要掌握到什么程度?","slug":"leetcode-interview-ts","date":"2020-08-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.910Z","comments":true,"path":"2020/08/13/leetcode-interview-ts/","link":"","permalink":"https://lucifer.ren/blog/2020/08/13/leetcode-interview-ts/","excerpt":"2018 年底的时候,力扣发布了岗位招聘,其中就有前端,仓库地址:https://github.com/LeetCode-OpenSource/hire 。与大多数 JD 不同, 其提供了 5 道题, 并注明了完成一个或多个面试题,获取免第一轮面试的面试机会。完成的题目越多,质量越高,在面试中的加分更多。完成后的代码可以任意形式发送给 jobs@lingkou.com。以上几个问题完成一个或多个都有可能获得面试机会,具体情况取决于提交给我们的代码。 (力扣中国前端工程师 JD) 今天我们就来看下第二题:编写复杂的 TypeScript 类型。通过这道题来看下, TypeScript 究竟要到什么水平才能进力扣当前端? 其它四道题也蛮有意思的,值得一看。","text":"2018 年底的时候,力扣发布了岗位招聘,其中就有前端,仓库地址:https://github.com/LeetCode-OpenSource/hire 。与大多数 JD 不同, 其提供了 5 道题, 并注明了完成一个或多个面试题,获取免第一轮面试的面试机会。完成的题目越多,质量越高,在面试中的加分更多。完成后的代码可以任意形式发送给 jobs@lingkou.com。以上几个问题完成一个或多个都有可能获得面试机会,具体情况取决于提交给我们的代码。 (力扣中国前端工程师 JD) 今天我们就来看下第二题:编写复杂的 TypeScript 类型。通过这道题来看下, TypeScript 究竟要到什么水平才能进力扣当前端? 其它四道题也蛮有意思的,值得一看。 问题描述假设有一个叫 EffectModule 的类 1class EffectModule {} 这个对象上的方法只可能有两种类型签名: 12345678interface Action<T> { payload?: T type: string}asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>syncMethod<T, U>(action: Action<T>): Action<U> 这个对象上还可能有一些任意的非函数属性: 1234567891011121314151617181920212223interface Action<T> { payload?: T; type: string;}class EffectModule { count = 1; message = \"hello!\"; delay(input: Promise<number>) { return input.then((i) => ({ payload: `hello ${i}!`, type: \"delay\", })); } setMessage(action: Action<Date>) { return { payload: action.payload!.getMilliseconds(), type: \"set-message\", }; }} 现在有一个叫 connect 的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有EffectModule 的同名方法,但是方法的类型签名被改变了: 12asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> 变成了asyncMethod<T, U>(input: T): Action<U> 12syncMethod<T, U>(action: Action<T>): Action<U> 变成了syncMethod<T, U>(action: T): Action<U> 例子: EffectModule 定义如下: 1234567891011121314151617181920212223interface Action<T> { payload?: T; type: string;}class EffectModule { count = 1; message = \"hello!\"; delay(input: Promise<number>) { return input.then((i) => ({ payload: `hello ${i}!`, type: \"delay\", })); } setMessage(action: Action<Date>) { return { payload: action.payload!.getMilliseconds(), type: \"set-message\", }; }} connect 之后: 123456type Connected = { delay(input: number): Action<string>; setMessage(action: Date): Action<number>;};const effectModule = new EffectModule();const connected: Connected = connect(effectModule); 要求: 在 题目链接 里面的 index.ts 文件中,有一个 type Connect = (module: EffectModule) => any,将 any 替换成题目的解答,让编译能够顺利通过,并且 index.ts 中 connected 的类型与: 1234type Connected = { delay(input: number): Action<string>; setMessage(action: Date): Action<number>;}; 完全匹配。 以上是官方题目描述,下面我的补充 上文提到的index.ts 比 题目描述多了两个语句,它们分别是: (题目额外信息) 思路首先来解读下题目。 题目要求我们补充类型 Connect 的定义, 也就是将 any 替换为不报错的其他代码。 回顾一下题目信息: 有一个叫 connect 的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有EffectModule 的同名方法,但是方法的类型签名被改变了 这个对象上还可能有一些任意的非函数属性 这个对象(EffectModule 实例)上的方法只可能有两种类型签名 根据以上信息,我们能够得到:我们只需要将作为参数传递进来的 EffectModule 实例上的函数类型签名修改一下,非函数属性去掉即可。所以,我们有两件问题要解决: 如何将非函数属性去掉 如何转换函数类型签名 如何将非函数属性去掉我们需要定义一个泛型,功能是接受一个对象,如果对象的 value 是 函数,则保留,否则去掉即可。不懂泛型的朋友可以先看下我之前写的文章: 你不知道的 TypeScript 泛型(万字长文,建议收藏) 这让我想起了官方提供的 Omit 泛型 Omit<T,K>。举个例子: 12345678910111213interface Todo { title: string; description: string; completed: boolean;}type TodoPreview = Omit<Todo, \"description\">;// description 属性没了const todo: TodoPreview = { title: \"Clean room\", completed: false,}; 官方的 Omit 实现: 12345type Pick<T, K extends keyof T> = { [P in K]: T[P];};type Exclude<T, U> = T extends U ? never : T;type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; 实际上我们要做的就是 Omit 的变种,不是 Omit 某些 key,而是 Omit 值为非函数的 key。 由于 Omit 非函数实际就就是 Pick 函数,并且无需显式指定 key,因此我们的泛型只接受一个参数即可。 于是模仿官方的 Pick 写出了如下代码: 1234567// 获取值为函数的 key,形如: 'funcKeyA' | 'funcKeyB'type PickFuncKeys<T> = { [K in keyof T]: T[K] extends Function ? K : never;}[keyof T];// 获取值为函数的 key value 对,形如: { 'funcKeyA': ..., 'funKeyB': ...}type PickFunc<T> = Pick<T, PickFuncKeys<T>>; 使用效果: 123456789101112131415interface Todo { title: string; description: string; addTodo(): string;}type AddTodo = PickFunc<Todo>;const todo: AddTodo = { addTodo() { return \"关注脑洞前端~\"; },};type ADDTodoKey = PickFuncKeys<Todo>; // 'addTodo' 可以看出,PickFunc 只提取了函数属性,忽略了非函数属性。 如何转换函数类型签名我们再来回顾一下题目要求: 也就是我们需要知道怎么才能提取 Promise 和 Action 泛型中的值。 实际上这两个几乎一样,会了一个,另外一个也就会了。我们先来看下 Promise。 从: 1(arg: Promise<T>) => Promise<U> 变为: 1(arg: T) => U; 如果想要完成这个需求,需要借助infer。只需要在类型前加一个关键字前缀 infer,TS 会将推导出的类型自动填充进去。 infer 最早出现在此 官方 PR 中,表示在 extends 条件语句中待推断的类型变量。 简单示例如下: 1type ParamType<T> = T extends (param: infer P) => any ? P : T; 在这个条件语句 T extends (param: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。 整句表示为:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T。 一个更具体的例子: 123456789interface User { name: string; age: number;}type Func = (user: User) => void;type Param = ParamType<Func>; // Param = Usertype AA = ParamType<string>; // string 这些知识已经够我们用了。 更多用法可以参考 深入理解 TypeScript - infer 。 根据上面的知识,不难写出如下代码: 1234567type ExtractPromise<P> = { [K in PickFuncKeys<P>]: P[K] extends ( arg: Promise<infer T> ) => Promise<infer U> ? (arg: T) => U : never;}; 提取 Action 的 代码也是类似: 1234567type ExtractAction<P> = { [K in keyof PickFunc<P>]: P[K] extends ( arg: Action<infer T> ) => Action<infer U> ? (arg: T) => Action<U> : never;}; 至此我们已经解决了全部两个问题,完整代码见下方代码区。 关键点 泛型 extends 做类型约束 infer 做类型提取 内置基本范型的使用和实现 代码我们将这几个点串起来,不难写出如下最终代码: 123456type ExtractContainer<P> = { [K in PickFuncKeys<P>]: P[K] extends (arg: Promise<infer T>) => Promise<infer U> ? (arg: T) => U : P[K] extends (arg: Action<infer T>) => Action<infer U> ? (arg: T) => Action<U> : nevertype Connect = (module: EffectModule) => ExtractContainer<EffectModule> 完整代码在我的 Gist 上。 总结我们先对问题进行定义,然后分解问题为:1. 如何将非函数属性去掉, 2. 如何转换函数类型签名。最后从分解的问题,以及基础泛型工具入手,联系到可能用到的语法。 这个题目不算难,最多只是中等。但是你可能也看出来了,其不仅仅是考一个语法和 API 而已,而是考综合实力。这点在其他四道题体现地尤为明显。这种考察方式能真正考察一个人的综合实力,背题是背不来的。我个人在面试别人的时候也非常喜欢问这种问题。 只有掌握基础 + 解决问题的思维方法,面对复杂问题才能从容不迫,手到擒来。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/categories/TypeScript/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"}]},{"title":"Webkit 内核初探","slug":"webkit-intro","date":"2020-08-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.712Z","comments":true,"path":"2020/08/11/webkit-intro/","link":"","permalink":"https://lucifer.ren/blog/2020/08/11/webkit-intro/","excerpt":"作者: 阿吉校对&整理: lucifer 当下浏览器内核主要有 Webkit、Blink 等。本文分析注意是自 2001 年 Webkit 从 KHTML 分离出去并开源后,各大浏览器厂商魔改 Webkit 的时期,这些魔改的内核最终以 Chromium 受众最多而脱颖而出。本文就以 Chromium 浏览器架构为基础,逐层探入进行剖析。","text":"作者: 阿吉校对&整理: lucifer 当下浏览器内核主要有 Webkit、Blink 等。本文分析注意是自 2001 年 Webkit 从 KHTML 分离出去并开源后,各大浏览器厂商魔改 Webkit 的时期,这些魔改的内核最终以 Chromium 受众最多而脱颖而出。本文就以 Chromium 浏览器架构为基础,逐层探入进行剖析。 引子这里以一个面试中最常见的题目从 URL 输入到浏览器渲染页面发生了什么?开始。 这个很常见的题目,涉及的知识非常广泛。大家可先从浏览器监听用户输入开始,浏览器解析 url 的部分,分析出应用层协议 是 HTTPS 还是 HTTP 来决定是否经过会话层 TLS 套接字,然后到 DNS 解析获取 IP,建立 TCP 套接字池 以及 TCP 三次握手,数据封装切片的过程,浏览器发送请求获取对应数据,如何解析 HTML,四次挥手等等等等。 这个回答理论上可以非常详细,远比我提到的多得多。 本文试图从浏览器获取资源开始探究 Webkit。如浏览器如何获取资源,获取资源时 Webkit 调用了哪些资源加载器(不同的资源使用不同的加载器),Webkit 如何解析 HTML 等入手。想要从前端工程师的角度弄明白这些问题,可以先暂时抛开 C++源码,从浏览器架构出发,做到大致了解。之后学有余力的同学再去深入研究各个底层细节。 本文的路线循序渐进,从 Chromium 浏览器架构出发,到 Webkit 资源下载时对应的浏览器获取对应资源如 HTML、CSS 等,再到 HTML 的解析,再到 JS 阻塞 DOM 解析而产生的 Webkit 优化 引出浏览器多线程架构,继而出于安全性和稳定性的考虑引出浏览器多进程架构。 一. Chromium 浏览器架构 (Chromium 浏览器架构) 我们通常说的浏览器内核,指的是渲染引擎。 WebCore 基本是共享的,只是在不同浏览器中使用 Webkit 的实现方式不同。它包含解析 HTML 生成 DOM、解析 CSS、渲染布局、资源加载器等等,用于加载和渲染网页。 JS 解析可以使用 JSCore 或 V8 等 JS 引擎。我们熟悉的谷歌浏览器就是使用 V8。比如比较常见的有内置属性 [[scope]] 就仅在 V8 内部使用,用于对象根据其向上索引自身不存在的属性。而对外暴露的 API,如 __proto__ 也可用于更改原型链。实际上 __proto__ 并不是 ES 标准提供的,它是浏览器提供的(浏览器可以不提供,因此如果有浏览器不提供的话这也并不是 b ug)。 Webkit Ports 是不共享的部分。它包含视频、音频、图片解码、硬件加速、网络栈等等,常用于移植。 同时,浏览器是多进程多线程架构,稍后也会细入。 在解析 HTML 文档之前,需要先获取资源,那么资源的获取在 Webkit 中应该如何进行呢? 二.Webkit 资源加载HTTP 是超文本传输协议,超文本的含义即包含了文本、图片、视频、音频等等。其对应的不同文件格式,在 Webkit 中 需要调用不同的资源加载器,即 特定资源加载器。 而浏览器有四级缓存,Disk Cache 是我们最常说的通过 HTTP Header 去控制的,比如强缓存、协商缓存。同时也有浏览器自带的启发式缓存。而 Webkit 对应使用的加载器是资源缓存机制的资源加载器 CachedResoureLoader 类。 如果每个资源加载器都实现自己的加载方法,则浪费内存空间,同时违背了单一职责的原则,因此可以抽象出一个共享类,即通用资源加载器 ResoureLoader 类。 Webkit 资源加载是使用了三类加载器:特定资源加载器,资源缓存机制的资源加载器 CachedResoureLoader 和 通用资源加载器 ResoureLoader。 既然说到了缓存,那不妨多谈一点。 资源既然缓存了,那是如何命中的呢?答案是根据资源唯一性的特征 URL。资源存储是有一定有效期的,而这个有效期在 Webkit 中采用的就是 LRU 算法。那什么时候更新缓存呢?答案是不同的缓存类型对应不同的缓存策略。我们知道缓存多数是利用 HTTP 协议减少网络负载的,即强缓存、协商缓存。但是如果关闭缓存了呢? 比如 HTTP/1.0 Pragma:no-cache 和 HTTP/1.1 Cache-Control: no-cache。此时,对于 Webkit 来说,它会清空全局唯一的对象 MemoryCache 中的所有资源。 资源加载器内容先到这里。浏览器架构是多进程多线程的,其实多线程可以直接体现在资源加载的过程中,在 JS 阻塞 DOM 解析中发挥作用,下面我们详细讲解一下。 三.浏览器架构浏览器是多进程多线程架构。 对于浏览器来讲,从网络获取资源是非常耗时的。从资源是否阻塞渲染的角度,对浏览器而言资源仅分为两类:阻塞渲染如 JS 和 不阻塞渲染如图片。 我们都知道 JS 阻塞 DOM 解析,反之亦然。然而对于阻塞,Webkit 不会傻傻等着浪费时间,它在内部做了优化:启动另一个线程,去遍历后续的 HTML 文档,收集需要的资源 URL,并发下载资源。最常见的比如<script async>和<script defer>,其 JS 资源下载和 DOM 解析是并行的,JS 下载并不会阻塞 DOM 解析。这就是浏览器的多线程架构。 总结一下,多线程的好处就是,高响应度,UI 线程不会被耗时操作阻塞而完全阻塞浏览器进程。 关于多线程,有 GUI 渲染线程,负责解析 HTML、CSS、渲染和布局等等,调用 WebCore 的功能。JS 引擎线程,负责解析 JS 脚本,调用 JSCore 或 V8。我们都知道 JS 阻塞 DOM 解析,这是因为 Webkit 设计上 GUI 渲染线程和 JS 引擎线程的执行是互斥的。如果二者不互斥,假设 JS 引擎线程清空了 DOM 树,在 JS 引擎线程清空的过程中 GUI 渲染线程仍继续渲染页面,这就造成了资源的浪费。更严重的,还可能发生各种多线程问题,比如脏数据等。 另外我们常说的 JS 操作 DOM 消耗性能,其实有一部分指的就是 JS 引擎线程和 GUI 渲染线程之间的通信,线程之间比较消耗性能。 除此之外还有别的线程,比如事件触发线程,负责当一个事件被触发时将其添加到待处理队列的队尾。 值得注意的是,多启动的线程,仅仅是收集后续资源的 URL,线程并不会去下载资源。该线程会把下载的资源 URL 送给 Browser 进程,Browser 进程调用网络栈去下载对应的资源,返回资源交由 Renderer 进程进行渲染,Renderer 进程将最终的渲染结果返回 Browser 进程,由 Browser 进程进行最终呈现。这就是浏览器的多进程架构。 多进程加载资源的过程是如何的呢?我们上面说到的 HTML 文档在浏览器的渲染,是交由 Renderer 进程的。Renderer 进程在解析 HTML 的过程中,已搜集到所有的资源 URL,如 link CSS、Img src 等等。但出于安全性和效率的角度考虑,Renderer 进程并不能直接下载资源,它需要通过进程间通信将 URL 交由 Browser 进程,Browser 进程有权限调用 URLRequest 类从网络或本地获取资源。 近年来,对于有的浏览器,网络栈由 Browser 进程中的一个模块,变成一个单独的进程。 同时,多进程的好处远远不止安全这一项,即沙箱模型。还有单个网页或者第三方插件的崩溃,并不会影响到浏览器的稳定性。资源加载完成,对于 Webkit 而言,它需要调用 WebCore 对资源进行解析。那么我们先看下 HTML 的解析。之后我们再谈一下,对于浏览器来说,它拥有哪些进程呢? 四.HTML 解析对于 Webkit 而言,将解析半结构化的 HTML 生成 DOM,但是对于 CSS 样式表的解析,严格意义 CSSOM 并不是树,而是一个映射表集合。我们可以通过 document.styleSheets 来获取样式表的有序集合来操作 CSSOM。对于 CSS,Webkit 也有对应的优化策略—-ComputedStyle。ComputedStyle 就是如果多个元素的样式可以不经过计算就确认相等,那么就仅会进行一次样式计算,其余元素仅共享该 ComputedStyle。 共享 ComputedStyle 原则: (1) TagName 和 Class 属性必须一样。 (2)不能有 Style。 (3)不能有 sibling selector。 (4)mappedAttribute 必须相等。 对于 DOM 和 CSSOM,大家说的合成的 render 树在 Webkit 而言是不存在的,在 Webkit 内部生成的是 RenderObject,在它的节点在创建的同时,会根据层次结构创建 RenderLayer 树,同时构建一个虚拟的绘图上下文,生成可视化图像。这四个内部表示结构会一直存在,直到网页被销毁。 RenderLayer 在浏览器控制台中 Layers 功能卡中可以看到当前网页的图层分层。图层涉及到显式和隐式,如 scale()、z-index 等。层的优点之一是只重绘当前层而不影响其他层,这也是 Webkit 做的优化之一。同时 V8 引擎也做了一些优化,比如说隐藏类、优化回退、内联缓存等等。 五.浏览器进程浏览器进程包括 Browser 进程、Renderer 进程、GPU 进程、NPAPI 插件进程、Pepper 进程等等。下面让我们详细看看各大进程。 Browser 进程:浏览器的主进程,有且仅有一个,它是进程祖先。负责页面的显示和管理、其他进程的管理。 Renderer 进程:网页的渲染进程,可有多个,和网页数量不一定是一一对应关系。它负责网页的渲染,Webkit 的渲染工作就是在这里完成的。 GPU 进程:最多一个。仅当 GPU 硬件加速被打开时创建。它负责 3D 绘制。 NPAPI 进程:为 NPAPI 类型的插件而创建。其创建的基本原则是每种类型的插件都只会被创建一次,仅当使用时被创建,可被共享。 Pepper 进程:同 NPAPI 进程,不同的是 它为 Pepper 插件而创建的进程。 注意:如果页面有 iframe,它会形成影子节点,会运行在单独的进程中。 我们仅仅在围绕 Chromium 浏览器来说上述进程,因为在移动端,毕竟手机厂商很多,各大厂商对浏览器进程的支持也不一样。这其实也是我们最常见的 H5 兼容性问题,比如 IOS margin-bottom 失效等等。再比如 H5 使用 video 标签做直播,也在不同手机之间会存在问题。有的手机直播页面跳出主进程再回来,就会黑屏。 以 Chromium 的 Android 版为例子,不存在 GPU 进程,GPU 进程变成了 Browser 进程的线程。同时,Renderer 进程演变为服务进程,同时被限制了最大数量。 为了方便起见,我们以 PC 端谷歌浏览器为例子,打开任务管理器,查看当前浏览器中打开的网页及其进程。 当前我打开了 14 个网页,不太好容易观察,但可以从下图中看到,只有一个 Browser 进程,即第 1 行。但是打开的网页对应的 Renderer 进程,并不一定是一个网页对应一个 Renderer 进程,这跟 Renderer 进程配置有关系。比如你看第 6、7 行是每个标签页创建独立 Renderer 进程,但是蓝色光标所在的第 8、9、10 行是共用一个 Renderer 进程,这属于为每个页面创建一个 Renderer 进程。因为第 9、10 行打开的页面是从第 8 行点击链接打开的。第 2 行的 GPU 进程也清晰可见,以及第 3、4、5 行的插件进程。 关于,Renderer 进程和打开的网页并不一定是一一对应的关系,下面我们详细说一下 Renderer 进程。当前只有四种多进程策略: Process-per-site-instance: 为每个页面单独创建一个进程,从某个网站打开的一系列网站都属于同一个进程。这是浏览器的默认项。上图中的蓝色光标就是这种情况。 Process-per-site:同一个域的页面共享一个进程。 Process-per-tab:为每个标签页创建一个独立的进程。比如上图第 6、7 行。 Single process:所有的渲染工作作为多个线程都在 Browser 进程中进行。这个基本不会用到的。 Single process 突然让我联想到零几年的时候,那会 IE 应该还是单进程浏览器。单进程就是指所有的功能模块全部运行在一个进程,就类似于 Single process。那会玩 4399 如果一个网页卡死了,没响应,点关闭等一会,整个浏览器就崩溃了,得重新打开。所以多进程架构是有利于浏览器的稳定性的。虽然当下浏览器架构为多进程架构,但如果 Renderer 进程配置为 Process-per-site-instance,也可能会出现由于单个页面卡死而导致所有页面崩溃的情况。 故浏览器多进程架构综上所述,好处有三: (1)单个网页的崩溃不会影响这个浏览器的稳定性。 (2)第三方插件的崩溃不会影响浏览器的稳定性。 (3)沙箱模型提供了安全保障。 总结Webkit 使用三类资源加载器去下载对应的资源,并存入缓存池中,对于 HTML 文档的解析,在阻塞时调用另一个线程去收集后续资源的 URL,将其发送给 Browser 进程,Browser 进程调用网络栈去下载对应的本地或网络资源,返回给 Renderer 进程进行渲染,Renderer 进程将最终渲染结果(一系列的合成帧)发送给 Browser 进程,Browser 进程将这些合成帧发送给 GPU 从而显示在屏幕上。(文中有部分不严谨的地方,已由 lucifer 指出修改)","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/categories/浏览器/"},{"name":"浏览器","slug":"前端/浏览器","permalink":"https://lucifer.ren/blog/categories/前端/浏览器/"},{"name":"webkit","slug":"前端/webkit","permalink":"https://lucifer.ren/blog/categories/前端/webkit/"},{"name":"webkit","slug":"浏览器/webkit","permalink":"https://lucifer.ren/blog/categories/浏览器/webkit/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/tags/浏览器/"},{"name":"webkit","slug":"webkit","permalink":"https://lucifer.ren/blog/tags/webkit/"}]},{"title":"上帝视角看 TypeScript","slug":"ts-internal","date":"2020-08-03T16:00:00.000Z","updated":"2023-01-07T12:34:34.557Z","comments":true,"path":"2020/08/04/ts-internal/","link":"","permalink":"https://lucifer.ren/blog/2020/08/04/ts-internal/","excerpt":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 而本篇文章则是这个系列的开篇。 系列安排: 上帝视角看 TypeScript(就是本文) TypeScript 类型系统 什么是 types?什么是 @types? 类型推导, 类型断言与类型保护 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 练习题 TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。","text":"TypeScript 的学习资料非常多,其中也不乏很多优秀的文章和教程。但是目前为止没有一个我特别满意的。原因有: 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺乏能够引起强烈共鸣的例子。 因此我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮助大家建立 TypeScript 世界观。 而本篇文章则是这个系列的开篇。 系列安排: 上帝视角看 TypeScript(就是本文) TypeScript 类型系统 什么是 types?什么是 @types? 类型推导, 类型断言与类型保护 你不知道的 TypeScript 泛型(万字长文,建议收藏)(已发布) TypeScript 练习题 TypeScript 配置文件该怎么写? TypeScript 是如何与 React,Vue,Webpack 集成的? 目录将来可能会有所调整。 注意,我的系列文章基本不会讲 API,因此需要你有一定的 TypeScript 使用基础,推荐两个学习资料。 深入理解 TypeScript 官方文档 结合这两个资料和我的系列教程,掌握 TypeScript 指日可待。 接下来,我们通过几个方面来从宏观的角度来看一下 TypeScript。 从输入输出上来看如果我们把 Typescript 编译器看成一个黑盒的话。其输入则是使用 TypeScript 语法书写的文本或者文本集合。 (文本) 如果几个文本有引用关系,比如 a.ts 依赖 foo.ts 和 bar.ts,其就是一个文本集合。 (文本集合) 输出是编译之后的 JS 文件 和 .d.ts 的声明文件。 其中 JS 是将来需要运行的文件,而 .d.ts 声明文件则是 ts 文件中的类型声明,这个类型声明就是你在 ts 文件中声明的类型和 TypeScript 类型推导系统推导的类型。当然你也可以自己写 .d.ts 声明文件。 从功能上来看从宏观的视角来看,TypeScript 的功能就是: 提供了丰富的类型系统。 最简单的就是 变量名:类型 = 值 1const a: Number = 1; 除了这些基本类型,还提供了函数类型,复合类型等。 提供了类型操作 API。TypeScript 不但提供内置类型,用户也可以利用集合操作和泛型对类型操作从而生成新的类型。 对每一种类型的属性和方法都进行了定义。 比如 String 类型有 toString 方法,但是没有 toFixed 方法,这就是 lib.d.ts 定义的。这样我在 String 类型的变量上使用 toFixed 方法就会报错,达到了“类型检查”的作用。 小提示:lib.d.ts 的内容主要是一些变量声明(如:window、document、math)和一些类似的接口声明(如:Window、Document、Math)。 你可以通过 —noLib 来关闭这一功能 提供了模块系统(module,namespace)。 提供了更加方面的 API,比如 class(这在 ES6 class 出来之前尤其好用),装饰器等。 。。。 TypeScript 编译器是如何工作的?上面已经讨论了 TypeScript 编译器的输入和输出。那黑盒内部是怎么工作呢?这里我简单介绍一下: TypeScript 文本首先会被解析为 token 流。这个过程比较简单,就是单纯地按照分隔符去分割文本即可。 接着 token 流会被转换为 AST,也就是抽象语法树。 binder 则根据 AST 信息生成 Symbol(TypeScript 中的一个数据结构)。拿上面的图来说,就是 number 节点。 当我们需要类型检查的时候, checker 会根据前面生成的 AST 和 symbols 生成类型检查结果。 当我们需要生成 JS 文件的时候,emitter 同样会根据前面生成的 AST 和 symbols 生成 JS 文件。 完整图: 总结总的来说,TypeScript 就是一门语言,和 Java,Python,C++ 等类似。只不过这门语言主要目标就是为了弥补 JavaScript 弱类型带来的问题的。因此设计语言的出发点就是: 静态类型系统 可以编译成 JavaScript 因此 TypeScript 是一门最终编译为 JavaScript 的语言(当然还有类型文件)。既然是一门语言,就涉及词法分析,语法分析等流程。由于相对 JavaScript 增加了很多功能, 其中最主要的就是类型系统。因此 TypeScript 的分析工作要比 JavaScript 更加复杂, 集中体现在 binder 和 checker 部分。 由于提供了静态类型, 因此就需要提供一些内置类型给我们用,比如 number,string,Array 等。但是这并不能满足我们的所有需求,我们需要自定义类型,因此有了 type,有了 interface 等。后来我们又发现自定义的类型重复代码太多, 要是类型也可以通过编程生成新的类型就好了,于是有了集合运算和泛型。 代码都放到一起不方便维护,要是可以放到不同文件,需要用的时候组装起来就好了,于是有了模块化。我用了别人的用 TypeScript 开发的库,如果也能有类型校验就好了,于是有了 types。 。。。 其实这些都是有因果关系的,如果你可以牢牢地掌握这些因果关系,那么学起来还不是易如反掌? 相关阅读 TypeScript 编译原理 Bring your own TypeScript with more internal definitions Compiler Internals TypeScript 编译器是用 TypeScript 写的,那是先有编译器还是 TS? 点关注,不迷路大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"},{"name":"泛型","slug":"前端/TypeScript/泛型","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/泛型/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"},{"name":"泛型","slug":"泛型","permalink":"https://lucifer.ren/blog/tags/泛型/"}]},{"title":"《我是你的妈妈呀》 - 第一期","slug":"mother-01","date":"2020-08-02T16:00:00.000Z","updated":"2023-01-07T12:34:34.364Z","comments":true,"path":"2020/08/03/mother-01/","link":"","permalink":"https://lucifer.ren/blog/2020/08/03/mother-01/","excerpt":"记得我初中的时候,学校发的一个小册子的名字就是母题啥的。 大概意思是市面上的题(尤其是中考题)都是这些母题生的,都是它们的儿子。 熟悉我的朋友应该知道,我有一个风格:”喜欢用通俗易懂的语言以及图片,还原解题过程“。包括我是如何抽象的,如何与其他题目建立联系的等。比如: 一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~ 超级详细记忆化递归,图解,带你一次攻克三道 Hard 套路题(44. 通配符匹配) 穿上衣服我就不认识你了?来聊聊最长上升子序列 扒一扒这种题的外套(343. 整数拆分) 如果把这个思考过程称之为自顶向下的话,那么实际上能写出来取决于你: 是否有良好的抽象能力 是否有足够的基础知识 是否能与学过的基础知识建立联系 如果反着呢? 我先把所有抽象之后的纯粹的东西掌握,也就是母题。那么遇到新的题,我就往上套呗?这就是我在《LeetCode 题解仓库》中所说的只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 这种思路就是自底向上。(有点像动态规划?) 市面上的题那么多,但是题目类型就是那几种。甚至出题人出题的时候都是根据以前的题目变个条件,变个说法从而搞出一个“新”的题。 这个专题的目标就是从反的方向来,我们先学习和记忆底层的被抽象过的经典的题目。遇到新的题目,就往这些母题上套即可。 那让我们来自底向上看下第一期的这八道母题吧~","text":"记得我初中的时候,学校发的一个小册子的名字就是母题啥的。 大概意思是市面上的题(尤其是中考题)都是这些母题生的,都是它们的儿子。 熟悉我的朋友应该知道,我有一个风格:”喜欢用通俗易懂的语言以及图片,还原解题过程“。包括我是如何抽象的,如何与其他题目建立联系的等。比如: 一招吃遍力扣四道题,妈妈再也不用担心我被套路啦~ 超级详细记忆化递归,图解,带你一次攻克三道 Hard 套路题(44. 通配符匹配) 穿上衣服我就不认识你了?来聊聊最长上升子序列 扒一扒这种题的外套(343. 整数拆分) 如果把这个思考过程称之为自顶向下的话,那么实际上能写出来取决于你: 是否有良好的抽象能力 是否有足够的基础知识 是否能与学过的基础知识建立联系 如果反着呢? 我先把所有抽象之后的纯粹的东西掌握,也就是母题。那么遇到新的题,我就往上套呗?这就是我在《LeetCode 题解仓库》中所说的只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 这种思路就是自底向上。(有点像动态规划?) 市面上的题那么多,但是题目类型就是那几种。甚至出题人出题的时候都是根据以前的题目变个条件,变个说法从而搞出一个“新”的题。 这个专题的目标就是从反的方向来,我们先学习和记忆底层的被抽象过的经典的题目。遇到新的题目,就往这些母题上套即可。 那让我们来自底向上看下第一期的这八道母题吧~ 母题 1题目描述给你两个有序的非空数组 nums1 和 nums2,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路 初始化 ans 为无限大 使用两个指针,一个指针指向数组 1,一个指针指向数组 2 比较两个指针指向的数字的大小,并更新较小的那个的指针,使其向后移动一位。更新的过程顺便计算 ans 最后返回 ans 代码12345678910def f(nums1, nums2): i = j = 0 ans = float('inf') while i < len(nums1) and j < len(nums2): ans = min(ans, abs(nums1[i] - nums2[j])) if nums1[i] < nums2[j]: i += 1 else: j += 1 return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 母题 2题目描述给你两个非空数组 nums1 和 nums2,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路数组没有说明是有序的,可以选择暴力。两两计算绝对值,返回最小的即可。 代码: 123456def f(nums1, nums2): ans = float('inf') for num1 in nums1: for num2 in nums2: ans = min(ans, abs(num1 - num2)) return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(1)$ 由于暴力的时间复杂度是 $O(N^2)$,因此其实也可以先排序将问题转换为母题 1,然后用母题 1 的解法求解。 复杂度分析 时间复杂度:$O(NlogN)$ 空间复杂度:$O(1)$ 母题 3题目描述给你 k 个有序的非空数组,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路继续使用母题 1 的思路,使用 k 个 指针即可。 复杂度分析 时间复杂度:$O(klogM)$,其中 M 为 k 个非空数组的长度的最小值。 空间复杂度:$O(1)$ 我们也可以使用堆来处理,代码更简单,逻辑更清晰。这里我们使用小顶堆,作用就是选出最小值。 代码12345678910111213141516def f(matrix): ans = float('inf') max_value = max(nums[0] for nums in matrix) heap = [(nums[0], i, 0) for i, nums in enumerate(nums)] heapq.heapify(heap) while True: min_value, row, idx = heapq.heappop(heap) if max_value - min_value < ans: ans = max_value - min_value if idx == len(matrix[row]) - 1: break max_value = max(max_value, matrix[row][idx + 1]) heapq.heappush(heap, (matrix[row][idx + 1], row, idx + 1)) return ans 复杂度分析 建堆的时间和空间复杂度为 $O(k)$。 while 循环会执行 M 次 ,其中 M 为 k 个非空数组的长度的最小值。heappop 和 heappush 的时间复杂度都是 logk。因此 while 循环总的时间复杂度为 $O(Mlogk)$。 时间复杂度:$O(max(Mlogk, k))$,其中 M 为 k 个非空数组的长度的最小值。 空间复杂度:$O(k)$ 母题 4题目描述给你 k 个非空数组,让你从每个数组中分别挑一个,使得二者差的绝对值最小。 思路先排序,然后转换为母题 3 母题 5题目描述给你两个有序的非空数组 nums1 和 nums2,让你将两个数组合并,使得新的数组有序。 LeetCode 地址: https://leetcode-cn.com/problems/merge-sorted-array/ 思路和母题 1 类似。 代码123456789101112131415def f(nums1, nums2): i = j = 0 ans = [] while i < len(nums1) and j < len(nums2): if nums1[i] < nums2[j]: ans.append(nums1[i]) i += 1 else: ans.append(nums2[j]) j += 1 if nums1: ans += nums2[j:] else: ans += nums1[i:] return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 母题 6题目描述给你 k 个有序的非空数组 nums1 和 nums2,让你将 k 个数组合并,使得新的数组有序。 思路和母题 5 类似。 只不过不是两个,而是多个。我们继续套用堆的思路。 代码123456789101112131415import heapqdef f(matrix): ans = [] heap = [] for row in matrix: heap += row heapq.heapify(heap) while heap: cur = heapq.heappop(heap) ans.append(cur) return ans 复杂度分析 建堆的时间和空间复杂度为 $O(N)$。 heappop 的时间复杂度为 $O(logN)$。 时间复杂度:$O(NlogN)$,其中 N 是矩阵中的数字总数。 空间复杂度:$O(N)$,其中 N 是矩阵中的数字总数。 母题 7题目描述给你两个有序的链表 root1 和 root2,让你将两个链表合并,使得新的链表有序。 LeetCode 地址:https://leetcode-cn.com/problems/merge-two-sorted-lists/ 思路和母题 5 类似。 不同的地方在于数据结构从数组变成了链表,我们只需要注意链表的操作即可。 这里我使用了迭代和递归两种方式。 大家可以把母题 5 使用递归写一下。 代码12345678910111213141516# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 if l1.val < l2.val: l1.next = self.mergeTwoLists(l1.next, l2) return l1 else: l2.next = self.mergeTwoLists(l1, l2.next) return l2 复杂度分析 时间复杂度:$O(N)$,其中 N 为两个链表中较短的那个的长度。 空间复杂度:$O(N)$,其中 N 为两个链表中较短的那个的长度。 123456789101112131415161718192021222324252627# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 ans = cur = ListNode(0) while l1 and l2: if l1.val < l2.val: cur.next = l1 cur = cur.next l1 = l1.next else: cur.next = l2 cur = cur.next l2 = l2.next if l1: cur.next = l1 else: cur.next = l2 return ans.next 复杂度分析 时间复杂度:$O(N)$,其中 N 为两个链表中较短的那个的长度。 空间复杂度:$O(1)$ 母题 8题目描述给你 k 个有序的链表,让你将 k 个链表合并,使得新的链表有序。 LeetCode 地址:https://leetcode-cn.com/problems/merge-k-sorted-lists/ 思路和母题 7 类似,我们使用递归可以轻松解决。其实本质上就是 代码1234567891011121314151617181920# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 if l1.val < l2.val: l1.next = self.mergeTwoLists(l1.next, l2) return l1 else: l2.next = self.mergeTwoLists(l1, l2.next) return l2 def mergeKLists(self, lists: List[ListNode]) -> ListNode: if not lists: return None if len(lists) == 1: return lists[0] return self.mergeTwoLists(lists[0], self.mergeKLists(lists[1:])) 复杂度分析 mergeKLists 执行了 k 次,每次都执行一次 mergeTwoLists,mergeTwoLists 的时间复杂度前面已经分析过了,为 $O(N)$,其中 N 为两个链表中较短的那个的长度。 时间复杂度:$O(k * N)$,其中 N 为两个链表中较短的那个的长度 空间复杂度:$O(max(k, N))$ 1234567891011121314151617181920# Definition for singly-linked list.class ListNode: def __init__(self, x): self.val = x self.next = Noneclass Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: if not l1: return l2 if not l2: return l1 if l1.val < l2.val: l1.next = self.mergeTwoLists(l1.next, l2) return l1 else: l2.next = self.mergeTwoLists(l1, l2.next) return l2 def mergeKLists(self, lists: List[ListNode]) -> ListNode: if not lists: return None if len(lists) == 1: return lists[0] return self.mergeTwoLists(self.mergeKLists(lists[:len(lists) // 2]), self.mergeKLists(lists[len(lists) // 2:])) 复杂度分析 mergeKLists 执行了 logk 次,每次都执行一次 mergeTwoLists,mergeTwoLists 的时间复杂度前面已经分析过了,为 $O(N)$,其中 N 为两个链表中较短的那个的长度。 时间复杂度:$O(Nlogk)$,其中 N 为两个链表中较短的那个的长度 空间复杂度:$O(max(logk, N))$,其中 N 为两个链表中较短的那个的长度 全家福最后送大家一张全家福: 子题实际子题数量有很多,这里提供几个供大家练习。一定要练习,不能眼高手低。多看我的题解,多练习,多总结,你也可以的。 面试题 17.14. 最小 K 个数 1200. 最小绝对差 632. 最小区间 两数和,三数和,四数和。。。 k 数和 总结母题就是抽象之后的纯粹的东西。如果你掌握了母题,即使没有掌握抽象的能力,依然有可能套出来。但是随着题目做的变多,“抽象能力”也会越来越强。因为你知道这些题背后是怎么产生的。 本期给大家介绍了八道母题, 大家可以在之后的刷题过程中尝试使用母题来套模板。之后会给大家带来更多的母题。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"母题","slug":"算法/母题","permalink":"https://lucifer.ren/blog/categories/算法/母题/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"母题","slug":"母题","permalink":"https://lucifer.ren/blog/tags/母题/"}]},{"title":"平衡二叉树专题","slug":"balanced-tree","date":"2020-07-20T16:00:00.000Z","updated":"2023-01-05T12:24:49.992Z","comments":true,"path":"2020/07/21/balanced-tree/","link":"","permalink":"https://lucifer.ren/blog/2020/07/21/balanced-tree/","excerpt":"力扣关于平衡二叉树的题目还是有一些的,并且都非常经典,推荐大家练习。今天给大家精选了 4 道题,如果你彻底搞明白了这几道题,碰到其他的平衡二叉树的题目应该不至于没有思路。当你领会了我的思路之后, 建议再找几个题目练手,巩固一下学习成果。","text":"力扣关于平衡二叉树的题目还是有一些的,并且都非常经典,推荐大家练习。今天给大家精选了 4 道题,如果你彻底搞明白了这几道题,碰到其他的平衡二叉树的题目应该不至于没有思路。当你领会了我的思路之后, 建议再找几个题目练手,巩固一下学习成果。 110. 平衡二叉树(简单)最简单的莫过于判断一个树是否为平衡二叉树了,我们来看下。 题目描述1234567891011121314151617181920212223242526272829给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。示例 1:给定二叉树 [3,9,20,null,null,15,7] 3 / \\ 9 20 / \\ 15 7返回 true 。示例 2:给定二叉树 [1,2,2,3,3,null,null,4,4] 1 / \\ 2 2 / \\ 3 3 / \\ 4 4返回 false 思路由于平衡二叉树定义为就是一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1。用伪代码描述就是: 1234if abs(高度(root.left) - 高度(root.right)) <= 1 and root.left 也是平衡二叉树 and root.right 也是平衡二叉树: print('是平衡二叉树')else: print('不是平衡二叉树') 而 root.left 和 root.right 如何判断是否是二叉平衡树就和 root 是一样的了,可以看出这个问题有明显的递归性。 因此我们首先需要知道如何计算一个子树的高度。这个可以通过递归的方式轻松地计算出来。计算子树高度的 Python 代码如下: 12345def dfs(node): if not node: return 0 l = dfs(node.left) r = dfs(node.right) return max(l, r) + 1 代码代码支持: Python3 Python3 Code: 12345678910class Solution: def isBalanced(self, root: TreeNode) -> bool: def dfs(node): if not node: return 0 l = dfs(node.left) r = dfs(node.right) return max(l, r) + 1 if not root: return True if abs(dfs(root.left) - dfs(root.right)) > 1: return False return self.isBalanced(root.left) and self.isBalanced(root.right) 复杂度分析 时间复杂度:对于 isBalanced 来说,由于每个节点最多被访问一次,这部分的时间复杂度为 $O(N)$,而 dfs 函数 每次被调用的次数不超过 $log N$,因此总的时间复杂度为 $O(NlogN)$,其中 $N$ 为 树的节点总数。 空间复杂度:由于使用了递归,这里的空间复杂度的瓶颈在栈空间,因此空间复杂度为 $O(h)$,其中 $h$ 为树的高度。 108. 将有序数组转换为二叉搜索树(简单)108 和 109 基本是一样的,只不过数据结构不一样,109 变成了链表了而已。由于链表操作比数组需要考虑更多的因素,因此 109 是 中等难度。 题目描述123456789101112131415将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。示例:给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 0 / \\ -3 9 / / -10 5 思路对于这个问题或者 给定一个二叉搜索树,将其改为平衡(后面会讲) 基本思路都是一样的。 题目的要求是将有序数组转化为: 高度平衡的二叉树 二叉搜索树 由于平衡二叉树是左右两个子树的高度差的绝对值不超过 1。因此一种简单的方法是选择中点作为根节点,根节点左侧的作为左子树,右侧的作为右子树即可。原因很简单,这样分配可以保证左右子树的节点数目差不超过 1。因此高度差自然也不会超过 1 了。 上面的操作同时也满足了二叉搜索树,原因就是题目给的数组是有序的。 你也可以选择别的数作为根节点,而不是中点,这也可以看出答案是不唯一的。 代码代码支持: Python3 Python3 Code: 12345678class Solution: def sortedArrayToBST(self, nums: List[int]) -> TreeNode: if not nums: return None mid = (len(nums) - 1) // 2 root = TreeNode(nums[mid]) root.left = self.sortedArrayToBST(nums[:mid]) root.right = self.sortedArrayToBST(nums[mid + 1:]) return root 复杂度分析 时间复杂度:由于每个节点最多被访问一次,因此总的时间复杂度为 $O(N)$,其中 $N$ 为数组长度。 空间复杂度:由于使用了递归,这里的空间复杂度的瓶颈在栈空间,因此空间复杂度为 $O(h)$,其中 $h$ 为树的高度。同时由于是平衡二叉树,因此 $h$ 就是 $log N$。 109. 有序链表转换二叉搜索树(中等)题目描述123456789101112131415`给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。示例:给定的有序链表: [-10, -3, 0, 5, 9],一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树: 0 / \\ -3 9 / / -10 5 思路和 108 思路一样。 不同的是数据结构的不同,因此我们需要关注的是链表和数组的操作差异。 (数组的情况) 我们再来看下链表: (链表的情况) 找到中点,只需要使用经典的快慢指针即可。同时为了防止环的出现, 我们需要斩断指向 mid 的 next 指针,因此需要记录一下中点前的一个节点,这只需要用一个变量 pre 记录即可。 代码代码代码支持:JS,Java,Python,C++ JS Code 123456789101112131415161718var sortedListToBST = function (head) { if (!head) return null; return dfs(head, null);};function dfs(head, tail) { if (head == tail) return null; let fast = head; let slow = head; while (fast != tail && fast.next != tail) { fast = fast.next.next; slow = slow.next; } let root = new TreeNode(slow.val); root.left = dfs(head, slow); root.right = dfs(slow.next, tail); return root;} Java Code: 123456789101112131415161718class Solution { public TreeNode sortedListToBST(ListNode head) { if(head == null) return null; return dfs(head,null); } private TreeNode dfs(ListNode head, ListNode tail){ if(head == tail) return null; ListNode fast = head, slow = head; while(fast != tail && fast.next != tail){ fast = fast.next.next; slow = slow.next; } TreeNode root = new TreeNode(slow.val); root.left = dfs(head, slow); root.right = dfs(slow.next, tail); return root; }} Python Code: 123456789101112131415161718class Solution: def sortedListToBST(self, head: ListNode) -> TreeNode: if not head: return head pre, slow, fast = None, head, head while fast and fast.next: fast = fast.next.next pre = slow slow = slow.next if pre: pre.next = None node = TreeNode(slow.val) if slow == fast: return node node.left = self.sortedListToBST(head) node.right = self.sortedListToBST(slow.next) return node C++ Code: 1234567891011121314151617181920212223class Solution {public: TreeNode* sortedListToBST(ListNode* head) { if (head == nullptr) return nullptr; return sortedListToBST(head, nullptr); } TreeNode* sortedListToBST(ListNode* head, ListNode* tail) { if (head == tail) return nullptr; ListNode* slow = head; ListNode* fast = head; while (fast != tail && fast->next != tail) { slow = slow->next; fast = fast->next->next; } TreeNode* root = new TreeNode(slow->val); root->left = sortedListToBST(head, slow); root->right = sortedListToBST(slow->next, tail); return root; }}; 复杂度分析 令 n 为链表长度。 时间复杂度:递归树的深度为 $logn$,每一层的基本操作数为 $n$,因此总的时间复杂度为$O(nlogn)$ 空间复杂度:空间复杂度为$O(logn)$ 有的同学不太会分析递归的时间复杂度和空间复杂度,我们在这里给大家再次介绍一下。 首先我们尝试画出如下的递归树。由于递归树的深度为 $logn$ 因此空间复杂度就是 $logn$ * 递归函数内部的空间复杂度,由于递归函数内空间复杂度为 $O(1)$,因此总的空间复杂度为 $O(logn)$。 时间复杂度稍微困难一点点。之前西法在先导篇给大家说过:如果有递归那就是:递归树的节点数 * 递归函数内部的基础操作数。而这句话的前提是所有递归函数内部的基本操作数是一样的,这样才能直接乘。而这里递归函数的基本操作数不一样。 不过我们发现递归树内部每一层的基本操作数都是固定的, 为啥固定已经在图上给大家算出来了。因此总的空间复杂度其实可以通过递归深度 * 每一层基础操作数计算得出,也就是 $nlogn$。 类似的技巧可以用于归并排序的复杂度分析中。 另外大家也直接可以通过公式推导得出。对于这道题来说,设基本操作数 T(n),那么就有 T(n) = T(n/2) * 2 + n/2,推导出来 T(n) 大概是 nlogn。这应该高中的知识。具体推导过程如下: T(n) = T(n/2) _ 2 + n/2 = \\frac{n}{2} + 2 _ (\\frac{n}{2}) ^ 2 + 2 ^ 2 _ (\\frac{n}{2}) ^ 3 + ... = logn _ \\frac{n}{2}类似地,如果递推公式为 T(n) = T(n/2) * 2 + 1 ,那么 T(n) 大概就是 logn。 1382. 将二叉搜索树变平衡(中等)题目描述123456789给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的 。如果有多种构造方法,请你返回任意一种。 示例: 12345678910输入:root = [1,null,2,null,3,null,4,null,null]输出:[2,1,3,null,null,null,4]解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。 提示:树节点的数目在 1 到 10^4 之间。树节点的值互不相同,且在 1 到 10^5 之间。 思路由于二叉搜索树的中序遍历是一个有序数组,因此问题很容易就转化为 108. 将有序数组转换为二叉搜索树(简单)。 代码代码支持: Python3 Python3 Code: 123456789101112131415class Solution: def inorder(self, node): if not node: return [] return self.inorder(node.left) + [node.val] + self.inorder(node.right) def balanceBST(self, root: TreeNode) -> TreeNode: nums = self.inorder(root) def dfs(start, end): if start == end: return TreeNode(nums[start]) if start > end: return None mid = (start + end) // 2 root = TreeNode(nums[mid]) root.left = dfs(start, mid - 1) root.right = dfs(mid + 1, end) return root return dfs(0, len(nums) - 1) 复杂度分析 时间复杂度:由于每个节点最多被访问一次,因此总的时间复杂度为 $O(N)$,其中 $N$ 为链表长度。 空间复杂度:虽然使用了递归,但是瓶颈不在栈空间,而是开辟的长度为 $N$ 的 nums 数组,因此空间复杂度为 $O(N)$,其中 $N$ 为树的节点总数。 总结本文通过四道关于二叉平衡树的题帮助大家识别此类型题目背后的思维逻辑,我们来总结一下学到的知识。 平衡二叉树指的是:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。 如果需要让你判断一个树是否是平衡二叉树,只需要死扣定义,然后用递归即可轻松解决。 如果需要你将一个数组或者链表(逻辑上都是线性的数据结构)转化为平衡二叉树,只需要随便选一个节点,并分配一半到左子树,另一半到右子树即可。 同时,如果要求你转化为平衡二叉搜索树,则可以选择排序数组(或链表)的中点,左边的元素为左子树, 右边的元素为右子树即可。 小提示 1: 如果不需要是二叉搜索树则不需要排序,否则需要排序。 小提示 2: 你也可以不选择中点, 算法需要相应调整,感兴趣的同学可以试试。 小提示 3: 链表的操作需要特别注意环的存在。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"平衡二叉树","slug":"数据结构/平衡二叉树","permalink":"https://lucifer.ren/blog/categories/数据结构/平衡二叉树/"},{"name":"二叉搜索树","slug":"数据结构/二叉搜索树","permalink":"https://lucifer.ren/blog/categories/数据结构/二叉搜索树/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/tags/二叉树/"}]},{"title":"听说这题套个BFS模板就可以 AC?","slug":"1091.shortest-path-in-binary-matrix","date":"2020-07-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.779Z","comments":true,"path":"2020/07/04/1091.shortest-path-in-binary-matrix/","link":"","permalink":"https://lucifer.ren/blog/2020/07/04/1091.shortest-path-in-binary-matrix/","excerpt":"​","text":"​ 题目描述123456789在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1)。一条从左上角到右下角、长度为 k 的畅通路径,由满足下述条件的单元格 C_1, C_2, ..., C_k 组成:相邻单元格 C*i 和 C*{i+1} 在八个方向之一上连通(此时,C*i 和 C*{i+1} 不同且共享边或角)C_1 位于 (0, 0)(即,值为 grid[0][0])C_k 位于 (N-1, N-1)(即,值为 grid[N-1][n-1])如果 C_i 位于 (r, c),则 grid[r][c] 为空(即,grid[r][c] == 0)返回这条从左上角到右下角的最短畅通路径的长度。如果不存在这样的路径,返回 -1 。 示例 1: 输入:[[0,1],[1,0]] 输出:2 示例 2: 输入:[[0,0,0],[1,1,0],[1,1,0]] 输出:4 提示: 1 <= grid.length == grid[0].length <= 100grid[i][j] 为 0 或 1 思路这道题乍一看很像之前写过的一些“机器人”。但是不同的地方在于机器人只能“向下移动和向右移动”,因此机器人那个题目就很适合用动态规划来做。为什么呢? 因为这道题可以移动的范围是八个方向,题目给的示例不是很好,我这里给大家画了一个示例。我相信你一看就明白了。 (图 1) 如图,我们发现每一个点的状态其实依赖了周围八个方向。如果我们使用动态规划来求解的时候,我们如何遍历(枚举所有子问题)呢? 由于每一个 cell 依赖了周围八个 cell,那么我应该先更新谁呢?这个问题就会比较复杂。 具体来说, 当我需要计算 dp[1][2]的值的时候,实际上我需要先计算dp[0][2],dp[1][1],dp[2][2] … 等八个值,这样才能确定 dp[1][2]的值。而计算 dp[0][2] 又是八个值,dp[1][1]等也是同理。 这样就会很复杂。 而如果你做题比较多的话,分析到这里会发现,应该会想到 BFS。 即使你做题不多,那么根据题目给出的关键字最短畅通路径,也应该想到 BFS 才对。 这道题我直接复制了一个我直接总结的模板,稍微改了一下就 OK 了。大家也可以在平时刷题过程总结自己的解题模板,这在争分夺秒的打比赛环节是很重要的。 我复制的模板是下面这个,大家可以对比下我提交的代码看看相似度有多少。 12345678910111213141516171819202122232425class Solution: def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: m = len(matrix) if m == 0: return [] n = len(matrix[0]) ans = [[0] * n for _ in range(m)] seen = set() queue = collections.deque() steps = 0 for i in range(m): for j in range(n): if matrix[i][j] == 0: queue.append((i, j)) seen.add((i, j)) while queue: for _ in range(len(queue)): i, j = queue.popleft() if matrix[i][j] == 1: ans[i][j] = steps for x, y in [(i + 1, j), (i - 1, j),(i, j + 1),(i, j - 1)]: if x >= 0 and x < m and y >=0 and y < n and (x, y) not in seen: queue.append((x, y)) seen.add((x, y)) steps += 1 return ans (Python BFS 模板代码) 我来用伪代码解释下这段代码的意思: 1234567891011121314151617template BFS(board) { 边界处理 seen = set() # 存储已经遍历过的节点,防止环的出现。 初始化队列 steps = 0 while 队列不为空 { 逐个取出队列中的元素(不包括在 while 循环内新添加的) if 满足条件 return steps for dir in dirs { 将周围的都加到队列,注意边界处理 } steps += 1 } return 不存在(一般是 -1)} (BFS 模板伪代码) 大家可以根据我的伪代码,自己定制属于自己的模板。 值得注意的是,本题我并没有使用 seen 来记录访问过的节点,而是直接原地修改,这是一个很常见的技巧,对这个技巧不熟悉的可以看下我的小岛专题 关键点 BFS BFS 模板 代码代码支持:Python3 123456789101112131415161718192021class Solution: def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: n = len(grid) if not grid or grid[0][0] == 1 or grid[n-1][n-1] == 1: return -1 steps = 1 queue = collections.deque() queue.append((0, 0)) grid[0][0] = 1 while queue: for _ in range(len(queue)): i, j = queue.popleft() if i == n - 1 and j == n - 1: return steps for dx, dy in [(-1,-1), (1,0), (0,1), (-1,0), (0,-1), (1,1), (1,-1), (-1,1)]: # 注意越界处理 if 0 <= i + dx < n and 0 <= j + dy < n and grid[i+dx][j+dy] == 0: queue.append((i + dx, j + dy)) grid[i + dx][j + dy] = 1 steps += 1 return -1 复杂度分析 时间复杂度:最坏的情况,我们需要遍历整个 board,因此时间复杂度取决于 cell 数,故时间复杂度为 $O(N ^ 2)$,其中 N 为边长。 空间复杂度:我们没有使用 seen,仅仅是借助了队列, 故空间复杂度为 $O(N)$,如果使用 seen 的话复杂度会上升到$O(N ^ 2)$,其中 N 为边长。 补充: 空间复杂度的$O(N)$ 是怎么来的? 我这里给大家画了一个图, 相信大家一下子就懂来。其中不同的颜色表示不同的层次,从红色开始表示第一层,然后往外扩张。可以看出队列最长的情况下和$N$同阶,因此空间复杂度为$O(N)$。 相关题目 200. 岛屿数量 695. 岛屿的最大面积 1162. 地图分析 62. 不同路径 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加 sa》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"BFS","slug":"算法/BFS","permalink":"https://lucifer.ren/blog/categories/算法/BFS/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"BFS","slug":"BFS","permalink":"https://lucifer.ren/blog/tags/BFS/"}]},{"title":"你的衣服我扒了 - 《最长公共子序列》","slug":"LCS","date":"2020-06-30T16:00:00.000Z","updated":"2023-01-07T12:34:34.312Z","comments":true,"path":"2020/07/01/LCS/","link":"","permalink":"https://lucifer.ren/blog/2020/07/01/LCS/","excerpt":"之前出了一篇穿上衣服我就不认识你了?来聊聊最长上升子序列,收到了大家的一致好评。今天给大家带来的依然是换皮题 - 最长公共子序列系列。 最长公共子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长公共子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长公共子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法可能不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。","text":"之前出了一篇穿上衣服我就不认识你了?来聊聊最长上升子序列,收到了大家的一致好评。今天给大家带来的依然是换皮题 - 最长公共子序列系列。 最长公共子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长公共子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长公共子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法可能不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。 718. 最长重复子数组题目地址https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/ 题目描述1234567891011121314给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。示例 1:输入:A: [1,2,3,2,1]B: [3,2,1,4,7]输出: 3解释:长度最长的公共子数组是 [3, 2, 1]。说明:1 <= len(A), len(B) <= 10000 <= A[i], B[i] < 100 前置知识 哈希表 数组 二分查找 动态规划 思路这就是最经典的最长公共子序列问题。一般这种求解两个数组或者字符串求最大或者最小的题目都可以考虑动态规划,并且通常都定义 dp[i][j] 为 以 A[i], B[j] 结尾的 xxx。这道题就是:以 A[i], B[j] 结尾的两个数组中公共的、长度最长的子数组的长度。 关于状态转移方程的选择可以参考: 穿上衣服我就不认识你了?来聊聊最长上升子序列 算法很简单: 双层循环找出所有的 i, j 组合,时间复杂度 $O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 如果 A[i] == B[j],dp[i][j] = dp[i - 1][j - 1] + 1 否则,dp[i][j] = 0 循环过程记录最大值即可。 记住这个状态转移方程,后面我们还会频繁用到。 关键点解析 dp 建模套路 代码代码支持:Python Python Code: 1234567891011class Solution: def findLength(self, A, B): m, n = len(A), len(B) ans = 0 dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if A[i - 1] == B[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 ans = max(ans, dp[i][j]) return ans 复杂度分析 时间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 空间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 二分查找也是可以的,不过并不容易想到,大家可以试试。 1143.最长公共子序列题目地址https://leetcode-cn.com/problems/longest-common-subsequence 题目描述给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。例如,”ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 若这两个字符串没有公共子序列,则返回 0。 示例 1: 输入:text1 = “abcde”, text2 = “ace” 输出:3解释:最长公共子序列是 “ace”,它的长度为 3。示例 2: 输入:text1 = “abc”, text2 = “abc” 输出:3 解释:最长公共子序列是 “abc”,它的长度为 3。示例 3: 输入:text1 = “abc”, text2 = “def” 输出:0 解释:两个字符串没有公共子序列,返回 0。 提示: 1 <= text1.length <= 1000 1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。 前置知识 数组 动态规划 思路和上面的题目类似,只不过数组变成了字符串(这个无所谓),子数组(连续)变成了子序列 (非连续)。 算法只需要一点小的微调: 如果 A[i] != B[j],那么 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 关键点解析 dp 建模套路 代码 你看代码多像 代码支持:Python Python Code: 12345678910111213class Solution: def longestCommonSubsequence(self, A: str, B: str) -> int: m, n = len(A), len(B) ans = 0 dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if A[i - 1] == B[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 ans = max(ans, dp[i][j]) else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) return ans 复杂度分析 时间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 空间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 1035. 不相交的线题目地址https://leetcode-cn.com/problems/uncrossed-lines/description/ 题目描述我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。 现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。 以这种方法绘制线条,并返回我们可以绘制的最大连线数。 示例 1: 输入:A = [1,4,2], B = [1,2,4] 输出:2 解释:我们可以画出两条不交叉的线,如上图所示。我们无法画出第三条不相交的直线,因为从 A[1]=4 到 B[2]=4 的直线将与从 A[2]=2 到 B[1]=2 的直线相交。示例 2: 输入:A = [2,5,1,2,5], B = [10,5,2,1,5,2] 输出:3 示例 3: 输入:A = [1,3,7,1,7,5], B = [1,9,2,5,1] 输出:2 提示: 1 <= A.length <= 500 1 <= B.length <= 500 1 <= A[i], B[i] <= 2000 前置知识 数组 动态规划 思路从图中可以看出,如果想要不相交,则必然相对位置要一致,换句话说就是:公共子序列。因此和上面的 1143.最长公共子序列 一样,属于换皮题,代码也是一模一样。 关键点解析 dp 建模套路 代码 你看代码多像 代码支持:Python Python Code: 12345678910111213class Solution: def longestCommonSubsequence(self, A: str, B: str) -> int: m, n = len(A), len(B) ans = 0 dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if A[i - 1] == B[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 ans = max(ans, dp[i][j]) else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) return ans 复杂度分析 时间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 空间复杂度:$O(m * n)$,其中 m 和 n 分别为 A 和 B 的 长度。 总结第一道是“子串”题型,第二和第三则是“子序列”。不管是“子串”还是“子序列”,状态定义都是一样的,不同的只是一点小细节。 只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 基础算法,把它彻底搞懂,再去面对出题人的各种换皮就不怕了。相反,如果你不去思考题目背后的逻辑,就会刷地很痛苦。题目稍微一变化你就不会了,这也是为什么很多人说刷了很多题,但是碰到新的题目还是不会做的原因之一。关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"动态规划","slug":"LeetCode/动态规划","permalink":"https://lucifer.ren/blog/categories/LeetCode/动态规划/"}],"tags":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"最长公共子序列","slug":"最长公共子序列","permalink":"https://lucifer.ren/blog/tags/最长公共子序列/"}]},{"title":"【LeetCode日记】 611. 有效三角形的个数","slug":"611.triangle","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.943Z","comments":true,"path":"2020/06/20/611.triangle/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/611.triangle/","excerpt":"题目地址: https://leetcode-cn.com/problems/valid-triangle-number/ ​","text":"题目地址: https://leetcode-cn.com/problems/valid-triangle-number/ ​ 题目描述123456789101112131415给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。示例 1:输入: [2,2,3,4]输出: 3解释:有效的组合是:2,3,4 (使用第一个 2)2,3,4 (使用第二个 2)2,2,3注意:数组长度不超过1000。数组里整数的范围为 [0, 1000]。 前置知识 排序 双指针 二分法 三角形边的关系 暴力法(超时)思路首先要有一个数学前提: 如果三条线段中任意两条的和都大于第三边,那么这三条线段可以组成一个三角形。即给定三个线段 a,b,c,如果满足 a + b > c and a + c > b and b + c > a,则线段 a,b,c 可以构成三角形,否则不可以。 力扣中有一些题目是需要一些数学前提的,不过这些数学前提都比较简单,一般不会超过高中数学知识,并且也不会特别复杂。一般都是小学初中知识即可。 如果你在面试中碰到不知道的数学前提,可以寻求面试官提示试试。 关键点解析 三角形边的关系 三层循环确定三个线段 代码代码支持: Python 1234567891011121314class Solution: def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False if a + b > c and a + c > b and b + c > a: return True return False def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 for i in range(n - 2): for j in range(i + 1, n - 1): for k in range(j + 1, n): if self.is_triangle(nums[i], nums[j], nums[k]): ans += 1 return ans 复杂度分析 时间复杂度:$O(N ^ 3)$,其中 N 为 数组长度。 空间复杂度:$O(1)$ 优化的暴力法思路暴力法的时间复杂度为 $O(N ^ 3)$, 其中 $N$ 最大为 1000。一般来说, $O(N ^ 3)$ 的算法在数据量 <= 500 是可以 AC 的。1000 的数量级则需要考虑 $O(N ^ 2)$ 或者更好的解法。 OK,到这里了。我给大家一个干货。 应该是其他博主不太会提的。原因可能是他们不知道, 也可能是他们觉得太小儿科不需要说。 由于前面我根据数据规模推测到到了解法的复杂度区间是 $N ^ 2$, $N ^ 2 * logN$,不可能是 $N$ (WHY?)。 降低时间复杂度的方法主要有: 空间换时间 和 排序换时间(我们一般都是使用基于比较的排序方法)。而排序换时间仅仅在总体复杂度大于 $O(NlogN)$ 才适用(原因不用多说了吧?)。 这里由于总体的时间复杂度是 $O(N ^ 3)$,因此我自然想到了排序换时间。当我们对 nums 进行一次排序之后,我发现: is_triangle 函数有一些判断是无效的 12345def is_triangle(self, a, b, c): if a == 0 or b == 0 or c == 0: return False # a + c > b 和 b + c > a 是无效的判断,因为恒成立 if a + b > c and a + c > b and b + c > a: return True return False 因此我们的目标变为找到a + b > c即可,因此第三层循环是可以提前退出的。 123456for i in range(n - 2): for j in range(i + 1, n - 1): k = j + 1 while k < n and num[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 这也仅仅是减枝而已,复杂度没有变化。通过进一步观察,发现 k 没有必要每次都从 j + 1 开始。而是从上次找到的 k 值开始就行。原因很简单, 当 nums[i] + nums[j] > nums[k] 时,我们想要找到下一个满足 nums[i] + nums[j] > nums[k] 的 新的 k 值,由于进行了排序,因此这个 k 肯定比之前的大(单调递增性),因此上一个 k 值之前的数都是无效的,可以跳过。 123456for i in range(n - 2): k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 由于 K 不会后退,因此最内层循环总共最多执行 N 次,因此总的时间复杂度为 $O(N ^ 2)$。 这个复杂度分析有点像单调栈,大家可以结合起来理解。 关键点分析 排序 代码12345678910111213class Solution: def triangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 nums.sort() for i in range(n - 2): if nums[i] == 0: continue k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:取决于排序算法 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/categories/Medium/"},{"name":"双指针","slug":"算法/双指针","permalink":"https://lucifer.ren/blog/categories/算法/双指针/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/tags/Medium/"}]},{"title":"穿上衣服我就不认识你了?来聊聊最长上升子序列","slug":"LIS","date":"2020-06-19T16:00:00.000Z","updated":"2023-03-31T10:20:34.521Z","comments":true,"path":"2020/06/20/LIS/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/LIS/","excerpt":"最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。","text":"最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么? 如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在? 虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维。 注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列。 300. 最长上升子序列题目地址https://leetcode-cn.com/problems/longest-increasing-subsequence 题目描述123456789101112给定一个无序的整数数组,找到其中最长上升子序列的长度。示例:输入: [10,9,2,5,3,7,101,18]输出: 4解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。说明:可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。你算法的时间复杂度应该为 O(n2) 。进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? 思路 美团和华为都考了这个题。 题目的意思是让我们从给定数组中挑选若干数字,这些数字满足: 如果 i < j 则 nums[i] < nums[j]。问:一次可以挑选最多满足条件的数字是多少个。 这种子序列求极值的题目,应该要考虑到贪心或者动态规划。这道题贪心是不可以的,我们考虑动态规划。 按照动态规划定义状态的套路,我们有两种常见的定义状态的方式: dp[i] : 以 i 结尾(一定包括 i)所能形成的最长上升子序列长度, 答案是 max(dp[i]),其中 i = 0,1,2, …, n - 1 dp[i] : 以 i 结尾(可能包括 i)所能形成的最长上升子序列长度,答案是 dp[-1] (-1 表示最后一个元素) 容易看出第二种定义方式由于无需比较不同的 dp[i] 就可以获得答案,因此更加方便。但是想了下,状态转移方程会很不好写,因为 dp[i] 的末尾数字(最大的)可能是 任意 j < i 的位置。 第一种定义方式虽然需要比较不同的 dp[i] 从而获得结果,但是我们可以在循环的时候顺便得出,对复杂度不会有影响,只是代码多了一点而已。因此我们选择第一种建模方式。 由于 dp[j] 中一定会包括 j,且以 j 结尾, 那么 nums[j] 一定是其所形成的序列中最大的元素,那么如果位于其后(意味着 i > j)的 nums[i] > nums[j],那么 nums[i] 一定能够融入 dp[j] 从而形成更大的序列,这个序列的长度是 dp[j] + 1。因此状态转移方程就有了:dp[i] = dp[j] + 1 (其中 i > j, nums[i] > nums[j]) 以 [10, 9, 2, 5, 3, 7, 101, 18] 为例,当我们计算到 dp[5]的时候,我们需要往回和 0,1,2,3,4 进行比较。 具体的比较内容是: 最后从三个中选一个最大的 + 1 赋给 dp[5]即可。 记住这个状态转移方程,后面我们还会频繁用到。 代码123456789101112class Solution: def lengthOfLIS(self, nums: List[int]) -> int: n = len(nums) if n == 0: return 0 dp = [1] * n ans = 1 for i in range(n): for j in range(i): if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) ans = max(ans, dp[i]) return ans 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 435. 无重叠区间题目地址https://leetcode-cn.com/problems/non-overlapping-intervals/ 题目描述123456789101112131415161718192021222324252627给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。注意:可以认为区间的终点总是大于它的起点。区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。示例 1:输入: [ [1,2], [2,3], [3,4], [1,3] ]输出: 1解释: 移除 [1,3] 后,剩下的区间没有重叠。示例 2:输入: [ [1,2], [1,2], [1,2] ]输出: 2解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。示例 3:输入: [ [1,2], [2,3] ]输出: 0解释: 你不需要移除任何区间,因为它们已经是无重叠的了。 思路我们先来看下最终剩下的区间。由于剩下的区间都是不重叠的,因此剩下的相邻区间的后一个区间的开始时间一定是不小于前一个区间的结束时间的。 比如我们剩下的区间是[ [1,2], [2,3], [3,4] ]。就是第一个区间的 2 小于等于 第二个区间的 2,第二个区间的 3 小于等于第三个区间的 3。 不难发现如果我们将前面区间的结束和后面区间的开始结合起来看,其就是一个非严格递增序列。而我们的目标就是删除若干区间,从而剩下最长的非严格递增子序列。这不就是上面的题么?只不过上面是严格递增,这不重要,就是改个符号的事情。 上面的题你可以看成是删除了若干数字,然后剩下剩下最长的严格递增子序列。 这就是抽象的力量,这就是套路。 如果对区间按照起点或者终点进行排序,那么就转化为上面的最长递增子序列问题了。和上面问题不同的是,由于是一个区间。因此实际上,我们是需要拿后面的开始时间和前面的结束时间进行比较。 而由于: 题目求的是需要移除的区间,因此最后 return 的时候需要做一个转化。 题目不是要求严格递增,而是可以相等,因此我们的判断条件要加上等号。 这道题还有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。 代码你看代码多像 123456789101112131415class Solution: def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: n = len(intervals) if n == 0: return 0 dp = [1] * n ans = 1 intervals.sort(key=lambda a: a[0]) for i in range(len(intervals)): for j in range(i - 1, -1, -1): if intervals[i][0] >= intervals[j][1]: dp[i] = max(dp[i], dp[j] + 1) break # 由于是按照开始时间排序的, 因此可以剪枝 return n - max(dp) 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 646. 最长数对链题目地址https://leetcode-cn.com/problems/maximum-length-of-pair-chain/ 题目描述1234567891011121314给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。示例 :输入: [[1,2], [2,3], [3,4]]输出: 2解释: 最长的数对链是 [1,2] -> [3,4]注意:给出数对的个数在 [1, 1000] 范围内。 思路和上面的435. 无重叠区间是换皮题,唯一的区别这里又变成了严格增加。没关系,我们把等号去掉就行了。并且由于这道题求解的是最长的长度,因此转化也不需要了。 当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。 代码这代码更像了! 123456789101112131415class Solution: def findLongestChain(self, intervals: List[List[int]]) -> int: n = len(intervals) if n == 0: return 0 dp = [1] * n ans = 1 intervals.sort(key=lambda a: a[0]) for i in range(len(intervals)): for j in range(i - 1, -1, -1): if intervals[i][0] > intervals[j][1]: dp[i] = max(dp[i], dp[j] + 1) break # 由于是按照开始时间排序的, 因此可以剪枝 return max(dp) 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 452. 用最少数量的箭引爆气球题目地址https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/ 题目描述1234567891011121314在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。Example:输入:[[10,16], [2,8], [1,6], [7,12]]输出:2解释:对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。 思路把气球看成区间,几个箭可以全部射爆,意思就是有多少不重叠的区间。注意这里重叠的情况也可以射爆。这么一抽象,就和上面的646. 最长数对链一模一样了,不用我多说了吧? 当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。 代码代码像不像? 123456789101112131415class Solution: def findMinArrowShots(self, intervals: List[List[int]]) -> int: n = len(intervals) if n == 0: return 0 dp = [1] * n ans = 1 intervals.sort(key=lambda a: a[0]) for i in range(len(intervals)): for j in range(i - 1, -1, -1): if intervals[i][0] > intervals[j][1]: dp[i] = max(dp[i], dp[j] + 1) break # 由于是按照开始时间排序的, 因此可以剪枝 return max(dp) 复杂度分析 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N)$ 优化大家想看效率高的,其实也不难。 LIS 也可以用 贪心 + 二分 达到不错的效率。代码如下: 代码文字版如下: 12345678910class Solution: def lengthOfLIS(self, A: List[int]) -> int: d = [] for a in A: i = bisect.bisect_left(d, a) if i < len(d): d[i] = a elif not d or d[-1] < a: d.append(a) return len(d) 如果求最长不递减子序列呢? 我们只需要将最左插入改为最右插入即可。代码: 123456789101112class Solution: def lengthOfLIS(self, A: List[int]) -> int: d = [] for a in A: # 这里改为最右 i = bisect.bisect(d, a) if i < len(d): d[i] = a # 这里改为小于等号 elif not d or d[-1] <= a: d.append(a) return len(d) 最左插入和最右插入分不清的可以看看我的二分专题。 也可以这么写,更简单一点: 12345678910def LIS(A): d = [] for a in A: # 如果求要严格递增就改为最左插入 bisect_left 即可 i = bisect.bisect(d, a) if i == len(d): d.append(a) elif d[i] != a: d[i] = a return len(d) More其他的我就不一一说了。 比如 673. 最长递增子序列的个数 (滴滴面试题)。 不就是求出最长序列,之后再循环比对一次就可以得出答案了么? 491. 递增子序列 由于需要找到所有的递增子序列,因此动态规划就不行了,妥妥回溯就行了,套一个模板就出来了。回溯的模板可以看我之前写的回溯专题。 最后推荐两道题大家练习一下,别看它们是 hard, 其实掌握了我这篇文章的内容一点都不难。 面试题 08.13. 堆箱子 参考代码: 12345678910111213class Solution: def pileBox(self, box: List[List[int]]) -> int: box = sorted(box, key=sorted) n = len(box) dp = [0 if i == 0 else box[i - 1][2] for i in range(n + 1)] ans = max(dp) for i in range(1, n + 1): for j in range(i + 1, n + 1): if box[j - 1][0] > box[i - 1][0] and box[j - 1][1] > box[i - 1][1] and box[j - 1][2] > box[i - 1][2]: dp[j] = max(dp[j], dp[i] + box[j - 1][2]) ans = max(ans , dp[j]) return ans 354. 俄罗斯套娃信封问题 参考代码: 1234567891011class Solution: def maxEnvelopes(self, envelopes: List[List[int]]) -> int: if not envelopes: return 0 n = len(envelopes) dp = [1] * n envelopes.sort() for i in range(n): for j in range(i + 1, n): if envelopes[i][0] < envelopes[j][0] and envelopes[i][1] < envelopes[j][1]: dp[j] = max(dp[j], dp[i] + 1) return max(dp) 960. 删列造序 III 参考代码: 1234567891011class Solution: def minDeletionSize(self, A): keep = 1 m, n = len(A), len(A[0]) dp = [1] * n for j in range(n): for k in range(j + 1, n): if all([A[i][k] >= A[i][j] for i in range(m)]): dp[k] = max(dp[k], dp[j] + 1) keep = max(keep, dp[k]) return n - keep 小任务:请尝试使用贪心在 NlogN 的时间内完成算法。(参考我上面的代码就行) 5644. 得到子序列的最少操作次数 由于这道题数据范围是 $10^5$,因此只能使用 $NlogN$ 的贪心才行。 关于为什么 10 ^ 5 就必须使用 $NlogN$ 甚至更优的算法我在刷题技巧提过。更多复杂度速查可参考我的刷题插件,公众号《力扣加加》回复插件获取即可。 参考代码: 12345678910111213141516class Solution: def minOperations(self, target: List[int], A: List[int]) -> int: def LIS(A): d = [] for a in A: i = bisect.bisect_left(d, a) if d and i < len(d): d[i] = a else: d.append(a) return len(d) B = [] target = { t:i for i, t in enumerate(target)} for a in A: if a in target: B.append(target[a]) return len(target) - LIS(B) 1626. 无矛盾的最佳球队 不就是先排下序,然后求 scores 的最长上升子序列么? 参考代码: 1234567891011class Solution: def bestTeamScore(self, scores: List[int], ages: List[int]) -> int: n = len(scores) persons = list(zip(ages, scores)) persons.sort(key=lambda x : (x[0], x[1])) dp = [persons[i][1] for i in range(n)] for i in range(n): for j in range(i): if persons[i][1] >= persons[j][1]: dp[i] = max(dp[i], dp[j]+persons[i][1]) return max(dp) 再比如 这道题 无非就是加了一个条件,我们可以结合循环移位的技巧来做。 关于循环移位算法西法在之前的文章 文科生都能看懂的循环移位算法 也做了详细讲解,不再赘述。 参考代码: 123456789101112131415class Solution: def solve(self, nums): n = len(nums) ans = 1 def LIS(A): d = [] for a in A: i = bisect.bisect_left(d,a) if i == len(d): d.append(a) else: d[i] = a return len(d) nums += nums for i in range(n): ans = max(ans , LIS(nums[i:i+n])) return ans 再再再比如 253 场周赛 Q4 压轴题 1964. 找出到每个位置为止最长的有效障碍赛跑路线 不就是求以每一个元素结尾的 LIS 么? 1234567891011121314class Solution: def longestObstacleCourseAtEachPosition(self, obstacles: List[int]) -> List[int]: def LIS(A): d = [] ans = [] for a in A: i = bisect.bisect_right(d, a) if d and i < len(d): d[i] = a else: d.append(a) ans.append(i+1) return ans return LIS(obstacles) 大家把我讲的思路搞懂,这几个题一写,还怕碰到类似的题不会么?只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 最长上升子序列就是一个非常经典的基础算法,把它彻底搞懂,再去面对出题人的各种换皮就不怕了。相反,如果你不去思考题目背后的逻辑,就会刷地很痛苦。题目稍微一变化你就不会了,这也是为什么很多人说刷了很多题,但是碰到新的题目还是不会做的原因之一。关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"动态规划","slug":"LeetCode/动态规划","permalink":"https://lucifer.ren/blog/categories/LeetCode/动态规划/"}],"tags":[{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"最长上升子序列","slug":"最长上升子序列","permalink":"https://lucifer.ren/blog/tags/最长上升子序列/"}]},{"title":"一文看懂《最大子序列和问题》","slug":"LSS","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:50.087Z","comments":true,"path":"2020/06/20/LSS/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/LSS/","excerpt":"最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。","text":"最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。 题目描述求取数组中最大连续子序列和,例如给定数组为 A = [1, 3, -2, 4, -5], 则最大连续子序列和为 6,即 1 + 3 +(-2)+ 4 = 6。 首先我们来明确一下题意。 题目说的子数组是连续的 题目只需要求和,不需要返回子数组的具体位置。 数组中的元素是整数,可能是正数,负数和 0。 子序列的最小长度为 1,不能为空。(这点很讨厌,不过题目就是这样~) 比如: 对于数组 [1, -2, 3, 5, -3, 2], 应该返回 3 + 5 = 8 对于数组 [0, -2, 3, 5, -1, 2], 应该返回 3 + 5 + -1 + 2 = 9 对于数组 [-9, -2, -3, -5, -3], 应该返回 -2(不能返回 0,即什么都不选) 解法一 - 暴力法(超时法)一般情况下,先从暴力解分析,然后再进行一步步的优化。 思路我们来试下最直接的方法,就是计算所有的子序列的和,然后取出最大值。记 Sum[i,….,j]为数组 A 中第 i 个元素到第 j 个元素的和,其中 0 <= i <= j < n,遍历所有可能的 Sum[i,….,j] 即可。 枚举以 0,1,2…n-1 开头的所有子序列,对于每一个以其开头的子序列,都去枚举从当前开始到索引 n-1 的情况。 这种做法的时间复杂度为 O(N^2), 空间复杂度为 O(1)。 代码JavaScript: 12345678910111213141516function LSS(list) { const len = list.length; let max = -Number.MAX_VALUE; let sum = 0; for (let i = 0; i < len; i++) { sum = 0; for (let j = i; j < len; j++) { sum += list[j]; if (sum > max) { max = sum; } } } return max;} Java: 123456789101112131415class MaximumSubarrayPrefixSum { public int maxSubArray(int[] nums) { int len = nums.length; int maxSum = Integer.MIN_VALUE; int sum = 0; for (int i = 0; i < len; i++) { sum = 0; for (int j = i; j < len; j++) { sum += nums[j]; maxSum = Math.max(maxSum, sum); } } return maxSum; }} Python 3: 12345678910111213import sysclass Solution: def maxSubArray(self, nums: List[int]) -> int: n = len(nums) maxSum = -sys.maxsize sum = 0 for i in range(n): sum = 0 for j in range(i, n): sum += nums[j] maxSum = max(maxSum, sum) return maxSum 空间复杂度非常理想,但是时间复杂度有点高。怎么优化呢?我们来看下下一个解法。 解法二 - 分治法思路先把数组平均分成左右两部分。 此时有三种情况: 最大子序列全部在数组左部分 最大子序列全部在数组右部分 最大子序列横跨左右数组 对于前两种情况,我们相当于将原问题转化为了规模更小的同样问题。 对于第三种情况,由于已知循环的一个端点(即中点),我们只需要进行一次循环,分别找出左边和右边的端点即可。 因此我们可以每次都将数组分成左右两部分,然后分别计算上面三种情况的最大子序列和,取出最大的即可。 举例说明,如下图: (by snowan) 这种做法的时间复杂度为 O(N*logN), 空间复杂度为 O(1)。 代码JavaScript: 1234567891011121314151617181920212223242526function helper(list, m, n) { if (m === n) return list[m]; let sum = 0; let lmax = -Number.MAX_VALUE; let rmax = -Number.MAX_VALUE; const mid = ((n - m) >> 1) + m; const l = helper(list, m, mid); const r = helper(list, mid + 1, n); for (let i = mid; i >= m; i--) { sum += list[i]; if (sum > lmax) lmax = sum; } sum = 0; for (let i = mid + 1; i <= n; i++) { sum += list[i]; if (sum > rmax) rmax = sum; } return Math.max(l, r, lmax + rmax);}function LSS(list) { return helper(list, 0, list.length - 1);} Java: 12345678910111213141516171819202122232425262728class MaximumSubarrayDivideConquer { public int maxSubArrayDividConquer(int[] nums) { if (nums == null || nums.length == 0) return 0; return helper(nums, 0, nums.length - 1); } private int helper(int[] nums, int l, int r) { if (l > r) return Integer.MIN_VALUE; int mid = (l + r) >>> 1; int left = helper(nums, l, mid - 1); int right = helper(nums, mid + 1, r); int leftMaxSum = 0; int sum = 0; // left surfix maxSum start from index mid - 1 to l for (int i = mid - 1; i >= l; i--) { sum += nums[i]; leftMaxSum = Math.max(leftMaxSum, sum); } int rightMaxSum = 0; sum = 0; // right prefix maxSum start from index mid + 1 to r for (int i = mid + 1; i <= r; i++) { sum += nums[i]; rightMaxSum = Math.max(sum, rightMaxSum); } // max(left, right, crossSum) return Math.max(leftMaxSum + rightMaxSum + nums[mid], Math.max(left, right)); }} Python 3 : 123456789101112131415161718192021import sysclass Solution: def maxSubArray(self, nums: List[int]) -> int: return self.helper(nums, 0, len(nums) - 1) def helper(self, nums, l, r): if l > r: return -sys.maxsize mid = (l + r) // 2 left = self.helper(nums, l, mid - 1) right = self.helper(nums, mid + 1, r) left_suffix_max_sum = right_prefix_max_sum = 0 sum = 0 for i in reversed(range(l, mid)): sum += nums[i] left_suffix_max_sum = max(left_suffix_max_sum, sum) sum = 0 for i in range(mid + 1, r + 1): sum += nums[i] right_prefix_max_sum = max(right_prefix_max_sum, sum) cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid] return max(cross_max_sum, left, right) 解法三 - 动态规划思路上面的分治虽然将问题规模缩小了,但是分解的三个子问题有两个是规模变小的同样问题,而有一个不是。 那能不能将其全部拆解为规模更小的同样问题,并且能找出递推关系呢? 如果可以,那么我们就可以使用记忆化递归或者动态规划来解决了。 不妨假设问题 Q(list, i) 表示 list 中以索引 i 结尾的情况下最大子序列和,那么原问题就转化为 max(Q(list, i)), 其中 i = 0,1,2…n-1 。 明确了状态, 继续来看下递归关系,这里是 Q(list, i)和 Q(list, i - 1)的关系,即如何根据 Q(list, i - 1) 推导出 Q(list, i)。 如果已知 Q(list, i - 1), 我们可以将问题分为两种情况,即以索引为 i 的元素终止,或者只有一个索引为 i 的元素。 如果以索引为 i 的元素终止, 那么就是 Q(list, i - 1) + list[i] 如果只有一个索引为 i 的元素,那么就是 list[i] 分析到这里,递推关系就很明朗了,即Q(list, i) = Math.max(0, Q(list, i - 1)) + list[i] 举例说明,如下图: (by snowan) 这种算法的时间复杂度 O(N), 空间复杂度为 O(1) 代码JavaScript: 12345678910function LSS(list) { const len = list.length; let max = list[0]; for (let i = 1; i < len; i++) { list[i] = Math.max(0, list[i - 1]) + list[i]; if (list[i] > max) max = list[i]; } return max;} Java: 1234567891011class MaximumSubarrayDP { public int maxSubArray(int[] nums) { int currMaxSum = nums[0]; int maxSum = nums[0]; for (int i = 1; i < nums.length; i++) { currMaxSum = Math.max(currMaxSum + nums[i], nums[i]); maxSum = Math.max(maxSum, currMaxSum); } return maxSum; }} Python 3: 12345678class Solution: def maxSubArray(self, nums: List[int]) -> int: dp = [0] * len(nums) ans = dp[0] = nums[0] for i in range(1, len(nums)): dp[i] = max(nums[i], dp[i - 1] + nums[i]) ans = max(ans, dp[i]) return ans 解法四 - 数学分析(前缀和)思路最后通过数学分析来看一下这个题目。 定义函数 S(i) ,它的功能是计算以 0(包括 0)开始加到 i(包括 i)的值。那么 S(j) - S(i - 1) 就等于 从 i 开始(包括 i)加到 j(包括 j)的值。 我们进一步分析,实际上我们只需要遍历一次计算出所有的 S(i), 其中 i 等于 0,1,2….,n-1。然后我们再减去之前的 S(k),其中 k 等于 0,1,i - 1,中的最小值即可。 因此我们需要用一个变量来维护这个最小值,还需要一个变量维护最大值。 这种算法的时间复杂度 O(N), 空间复杂度为 O(1)。 其实很多题目,都有这样的思想, 比如之前的《每日一题 - 电梯问题》。 代码JavaScript: 123456789101112131415function LSS(list) { const len = list.length; let max = list[0]; let min = 0; let sum = 0; for (let i = 0; i < len; i++) { sum += list[i]; if (sum - min > max) max = sum - min; if (sum < min) { min = sum; } } return max;} Java: 12345678910111213141516class MaxSumSubarray { public int maxSubArray3(int[] nums) { int maxSum = nums[0]; int sum = 0; int minSum = 0; for (int num : nums) { // prefix Sum sum += num; // update maxSum maxSum = Math.max(maxSum, sum - minSum); // update minSum minSum = Math.min(minSum, sum); } return maxSum; }} Python 3: 1234567891011class Solution: def maxSubArray(self, nums: List[int]) -> int: n = len(nums) maxSum = nums[0] minSum = sum = 0 for i in range(n): sum += nums[i] maxSum = max(maxSum, sum - minSum) minSum = min(minSum, sum) return maxSum 扩展如果题目变化为如下,问题该怎么解决呢? 1求取数组拼接 k 次的最大连续子序列和,例如给定数组为 A = [1, 3, 4, -5] ,k = 2, 拼接会的数组为 [1, 3, 4, -5, 1, 3, 4, -5],那么最大连续子序列和为 11,即子数组 [1, 3, 4, -5, 1, 3, 4] 的和。 直接将 A 拼接 k 次后转化为上面的问题的话空间会超出限制,空间复杂度为 $O(n * k)$。代码: 1234567class Solution: def solve(self, A, k): A = A * k dp = [0] * len(A) for i in range(len(A)): dp[i] = max(A[i], dp[i - 1] + A[i]) return max(dp) 然而实际上,我们可以仅拼接两次,因为当拼接次数大于 2,那么之后只是无限循环罢了。这种算法空间复杂度可以降低到 $O(n)$。 经过这样的处理,问题转化为求: A 拼接 min(2, k) 次后的最大子序和 + max(0, k-2) 次 A 的和(如果 A 的和小于 0,则不必加)。 代码: 123456789class Solution: def solve(self, nums, k): if not nums or not k: return 0 A = nums * min(2, k) dp = [0] * (len(A) + 1) for i in range(len(A)): dp[i] = max(A[i], dp[i - 1] + A[i]) return max(dp) + max(0, sum(nums)) * max(0, (k - 2)) 总结我们使用四种方法解决了《最大子序列和问题》,并详细分析了各个解法的思路以及复杂度,相信下次你碰到相同或者类似的问题的时候也能够发散思维,做到一题多解,多题一解。 实际上,我们只是求出了最大的和,如果题目进一步要求出最大子序列和的子序列呢?如果要题目允许不连续呢? 我们又该如何思考和变通?如何将数组改成二维,求解最大矩阵和怎么计算?这些问题留给读者自己来思考。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"子序列","slug":"算法/子序列","permalink":"https://lucifer.ren/blog/categories/算法/子序列/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"数组","slug":"数组","permalink":"https://lucifer.ren/blog/tags/数组/"}]},{"title":"好未来-北京-视频面试","slug":"interview-log-haoweilai","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.304Z","comments":true,"path":"2020/06/20/interview-log-haoweilai/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/interview-log-haoweilai/","excerpt":"作者:阿吉 整理&点评:lucifer","text":"作者:阿吉 整理&点评:lucifer 为什么要写这个面经?lucifer 说让我写一下秋招面经,但我很菜,一开始不想写的。最主要的是不想暴露自己的菜,虽然群佬都知道我比较菜。 lucifer 的群大概是我唯一一个每时每刻都能得到响应的群吧。很开心当时只是随便加了一下,但认识了好多大佬,经常在群里问一些很没头脑的问题,但总有人会艾特我回答,尽可能去触摸自己的知识边界帮我解答,大家都在交流(技术+扯皮)。 比较遗憾没偷学到 lucifer 的算法能力,也没能白嫖到小漾的美图。在 lucifer 群里开群语音,还有幸白嫖过川川(若川大佬)的声音 hhhhhh。大概我挺希望小漾看到这个文章能主动点晒照一下 hhhh(感觉第二天我就没了)。 面试体验好未来的四位面试官给予的面试体验很舒服。四面面试官说话一直用“您”。四面面试快结束的时候: 我:“我们的时间价值并不对等,所以很感谢您愿意抽时间面试我” 面试官:“不,我们的时间是对等的,我们今天只是在平等的交流”。 好未来的面试体验可能是目前秋招面试最舒服的,因为平时喜欢看很多闲书(面试很少会问的那种),把那些知识都有机会和面试官进行了分享。 面试官的问题用 Q 表示, 我的回答用 A 表示,序号表示第几个。 比如 Q5 表示面试官的第五个问题, A8 表示我对第八个问题的回答。 一面(男)面试内容 Q1: 聊项目,聊实习公司,问什么时候开始学前端 A1: 大二 Q2: 说一下排序算法。手撕冒泡,快排及其优化 A2: blabla lucifer 小提示: 要可以手撕才行哦。 Q3: TCP 和 UDP 区别。TCP 为什么是可靠的? A3: 《计网 自顶向下》,同时说了应用场景。 Q4: 三次握手,四次挥手 A4: 过程,字段,为何两次不行,各自作用。SYN 半连接攻击。 Q5: 谈一下 HTTP A5: 详细说 HTTP 1.0 1.1 2 3 细说文本格式和二进制格式(这个很感谢 feiker 大表哥在群里说了下),以及 HPACK,应用层和传输层的队头阻塞,多路复用和多路分解瞎扯了一下 302 303 307 在 RFC 规范中的发展历史,其实是跟浏览器大战年代相关的。 Q6: 浏览器缓存 A6: 四级:Service Work, Memory Cache, Disk Cache, Push Cache HTTP 控制的缓存位于 Disk Cache,即强缓存和协商缓存。二者中间的启发式缓存。 Q7: CDN A7: content dispatch newwork。 《计网 自顶向下》 Q8: 从 URL 到浏览器渲染,仅围绕 HTTP 相关展开 A8: 因为仅涉及 HTTP,除了常规回答,谈了下 webkit 里的三类资源加载器,以及网络栈。 Q9: 编译性语言 和 解释性语言的区别 A9: java,js。引申 JVM 和 V8。以及 V8 在早些年间拒绝采用中间码(《了不起的 nodejs》那本书比较老,里面就是无中间码),后来又采用了。其实最开始的原因就是考虑到移动端存储量。 Q10: 单线程的原因,好处 A10: JS 多线程对 DOM 的坏处。引申 webkit 多线程,看面试官比较感兴趣,又分析了浏览器的多进程架构,以及 Renderer 进程的四个配置项。 二面-男面试内容 Q1: 聊项目,前端倒计时,IOS 兼容等 A1: … Q2: 各种排序的时间复杂度 A2: … lucifer 小提示: 不要死记硬背。 Q3: 手撕代码 12345678function tpl(template, data){}// 输入tpl('<div class={%className%}>{%name%}</div>', {className:'hd', name:123})// 输出<div class=\"hd\">123</div>// 面试时编码思路:根据浏览器的词法分析去做,用stack。但存在问题。 A3:… Q4: 看代码说输出,作用域 1234567891011var a = 2;function fn1() { var a = 1; console.log(this.a + a);}function fn2() { var a = 10; fn1();}fn2();// 扯了一下this指向,以及C++中作用域的_variable表默认添加。 A4: … Q5: 你对闭包的理解 A5: 函数执行的保护机制。围绕函数执行机制(后来 lucifer 男神讲可以从词法作用域说,但毕竟是面试,感觉从函数执行来讲比较 ok),结合 V8 生成 AST 角度去谈何为闭包。具像为作用域链,及其 2 个表象。优缺点。理论应用:在 Vue 中的应用 Dep(),React 中的应用 Redux dispatch,设计模式中单例模式。项目应用:H5 前端自拟倒计时 destroyed 销毁引用。 三面-女面试内容 Q1: 自我介绍 A1: 叫 AJ,来自 X,能干活。 Q2: 聊点你的学校经历吧 A2: 在校职务… Q3: 为什么选择前端 A3: 经过大一尝试过 java,py,cnn 后决定,前端作为当下的生存技能。兴趣不局限于此。 lucifer 小提示: 不要把自己局限到前端。 Q4: 为什么不现在就去学后端 A4: 我明白自己每个阶段想要的是什么,当下秋招的我应该找一份匹配自己的工作。 Q5: 未来三年的职业规划 A5: 业务崽。 Q6: 实习公司,对比百度、腾讯、小米,最不喜欢哪一个? A6: 从不同的层面去说最喜欢哪家。 Q7: 你觉得你的缺点有什么 A7: 不喜欢跟人争执,浪费时间且无趣。 Q8: 为什么会投我们公司,了解我们吗 A8: 很好的朋友在开课吧(然哥),给我说好未来还挺不错的。秒投了。 Q9: 我们的业务有…你喜欢哪个业务啊 A10: 直播吧 四面-男面试内容 Q1: Vue 那种左右界面,中间的竖线可以滑动,左右布局跟着变化,怎么去做优化,可以从哪些角度触发 A1: 不会。尝试从 Vue Object.freeze() 和 提升图层角度去说。 Q2: GPU 硬件加速渲染说下原理 A2: 不会。从 CSS3 触发的角度说了下。 Q3: HTTPS 性能损耗在哪里? A3: TLS 握手。从《计算机网络 自顶向下》那本书里提的角度简单说了下。同时认为非对称加密算法对服务端资源消耗比较大。 Q4: 你如何去解决前端人员被需求压满,然后做业务觉得没有技术成长 A4: 不局限在功能点的开发,真正理解业务,理解业务流程中的数据流向以及坑点。当在当前环境遇到技术瓶颈要跳槽时,带着已有经验去下一个环境。 Q5. 谈谈 WebSocket,然后怎么去改造。 A5: 简单说了一点理论,直言没实践过。 lucifer 小提示: 可以自己实现一个 WebSocket 玩玩就啥都知道了。 Q6: 直播业务中,常用的协议是什么 A6: 仅知道 webRTC。 Q7: 海量数据找出最大的 K 个,怎么找?时间复杂度是多少? A7: lucifer 之前的文章应该有过,没记牢固。简单说了下。 lucifer 小提示:我们只需要建立一个大小为 K 的小顶堆,N 个数分别入堆,最后堆顶的元素就是第 K 大的。 时间复杂度 $O(NlogK)$ Q8: 了解好未来吗?为什么要来? A8: 做教育的。我哥推荐的。 PS: 面试白菜起步。SP 面是四面。 lucifer 点评由于是校招的原因,整个面试过程比较注重的是基础知识以及思考和学习方式。并且可以看出侧重点依然是: 网络(TCP,DNS,HTTP,HTTPS,浏览器缓存等) 浏览器渲染(GPU 硬件加速, webkit 原理等) 数据结构与算法(排序算法,复杂度分析,堆的应用等) 对于每一个部分,我们首先要做的是建立大局观,这样即使错,也不会错到哪去。大局观建立好了,相当于基本的知识框架有了,接下来就是填充知识框架了。这个阶段最主要的就是巩固复习和查缺补漏。经过这样的一个学习,相信你也能够在面试中崭露头角,获得心仪的 offer。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 公众号【 力扣加加】知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"},{"name":"好未来","slug":"好未来","permalink":"https://lucifer.ren/blog/categories/好未来/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"},{"name":"好未来","slug":"好未来","permalink":"https://lucifer.ren/blog/tags/好未来/"}]},{"title":"从零打造一个舒服的Mac开发环境 - 装机篇","slug":"mac-setup-for-fe","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.493Z","comments":true,"path":"2020/06/20/mac-setup-for-fe/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/mac-setup-for-fe/","excerpt":"前两天新买的 16 寸 mac pro 到手了。 本来想直接将旧电脑的数据做一个迁移,但是有同学反应想看“如何从零打造一个舒适的开发环境”,于是就有了这篇文章。","text":"前两天新买的 16 寸 mac pro 到手了。 本来想直接将旧电脑的数据做一个迁移,但是有同学反应想看“如何从零打造一个舒适的开发环境”,于是就有了这篇文章。 开箱 配置和价格在正式开始之前,我们先来介绍下主机的配置。 2.3GHz 8-core 9th-generation Intel Core i9 processor Turbo Boost up to 4.8GHz AMD Radeon Pro 5500M with 4GB of GDDR6 memory 32GB 2666MHz DDR4 memory 1TB SSD storage¹ 16-inch Retina display with True Tone Touch Bar and Touch ID Four Thunderbolt 3 ports 这个电脑要比 15 寸的 pro 重 100 多克,扬声器,显卡要比 15 寸的更加好一点,touch bar 重也将 ESC 和 TouchID 做成了实体键,最关键的是和 15 寸价格一样,我这个配置下来价格是RMB 25,135 。 如何从零打造一个舒适的开发环境 文字版 视频版 P1&P2 视频录制的声音比较小 Next本期视频只是一个简单的装机,以及系统配置。并不涉及到软件的深度使用,如果感兴趣可以给我留言,我会在之后给大家带来相关的攻略。","categories":[],"tags":[{"name":"Mac","slug":"Mac","permalink":"https://lucifer.ren/blog/tags/Mac/"},{"name":"装机","slug":"装机","permalink":"https://lucifer.ren/blog/tags/装机/"},{"name":"必备软件","slug":"必备软件","permalink":"https://lucifer.ren/blog/tags/必备软件/"}]},{"title":"【RFC】XXX 公司监控体系需求与技术调研","slug":"rfc-monitor","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.984Z","comments":true,"path":"2020/06/20/rfc-monitor/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/rfc-monitor/","excerpt":"线上问题回溯困难,无法快速准确重现问题,导致客户满意度下降,影响团队交付效率和质量,建立完善的监控体系可以很好的解决这个问题。","text":"线上问题回溯困难,无法快速准确重现问题,导致客户满意度下降,影响团队交付效率和质量,建立完善的监控体系可以很好的解决这个问题。 背景线上问题回溯困难,无法快速准确重现问题,导致客户满意度下降,影响团队交付效率和质量。 需求期望有一套工具,系统或者平台,可以满足: 在收到用户反馈的时候能够快速重现问题并解决。 测试同学发现问题,不需要花费大量事件和开发人员重现,沟通,以及记录问题重现路径等 线上发现问题可以进行告警,防止大规模用户有问题,并且不能及时感知和解决。 缩短团队内部 BUG 修复的闭环流程,减少非本质复杂度问题的干扰,快速将问题聚焦到具体的代码。 带着上面的需求,我们来看下市面上已有的经典方案, 在这里挑选几个具有代表性的。 市面上已有的方案对比LogRocket一句话概括: 用看录像的方式重现问题。 官网地址: https://logrocket.com/ 特点 更多功能: https://docs.logrocket.com/docs 接入方式 价格 Sentry一句话概括: 开源,强大的监控平台。 官网地址: https://sentry.io/ 特点功能较多,提供了较多的概念和功能,比如 Context,ENvironments,Breadcrumbs 等。另外其和 CI,CD 集成地也非常好。 详细内容: https://docs.sentry.io/workflow/releases/?platform=node 另外其支持的平台和扩展功能非常多,如果对这部分有特殊要求,Sentry 无疑是优先考虑的选择。 接入方式 Sign up for an account Install your SDK 12# Using yarn$ yarn add @sentry/node@5.8.0 Configure it 12const Sentry = require(\"@sentry/node\");Sentry.init({ dsn: \"https://<key>@sentry.io/<project>\" }); 价格 FunDebug一句话概括:国内知名度较高的监控工具,国内业务这块很有竞争力。 https://www.fundebug.com/ 特点支持小程序,小游戏。多种现成的报警方式,支持 WebHook,智能报警(同样的代码产生的同一个错误,在不同浏览器上的报错信息是各不相同的),内置团队协作工具。 接入方式这里以 Vue 项目为例。 免费注册 创建项目 配置 1npm install fundebug-javascript fundebug-vue --save 123456import * as fundebug from \"fundebug-javascript\";import fundebugVue from \"fundebug-vue\";fundebug.init({ apikey: \"API-KEY\",});fundebugVue(fundebug, Vue); 价格 其他后期可能功能点 性能监控 用户行为监控(已经有埋点,不不确定是否可以 Cover 这个需求) 自研假设我们已经做好了我们自己的监控平台,我们需要对公司内部甚至外部宣传我们的监控平台,我们会怎么进行宣传。 然后带着这些东西,我们进行规划,技术选型,排期,写代码,测试,上线。 宣传语 接入方便,侵入性小 支持多端,扩展性强(支持多种框架定制接入),完美契合业务发展 打通客服系统,开发直接对接到客户,免去了中间对接的信息缺失和时间损耗。 重现率高,能够准确重现用户的现场情况 打通报警系统 打通调试平台… 优劣分析优势完美契合我们自身的业务,后期好维护和增添功能 劣势如果功能需要做的超出市面,需要耗费巨大的人力和财力。 如果市面上不断发展,功能不能断完善,内部如果想要这样的功能要么继续追赶,要不买一套商用的,但是之前的努力岂不是白费了。除非内部两套系统,但是这种模式未免太反直觉。 架构与选型对外都宣传完了,我们需要具体开始进行架构与选型了。 定义对外接口我们对外宣传的目标是接入方便,侵入性小。因此一定要简洁,这里参考了以上几个平台的写法,其实这几个平台的都是大同小异。 注册应用获取 AppId 安装 1npm i --save @lucifer/monitor 引用 12345678910import monitor from \"@lucifer/monitor\";monitor.init({ user: { name: \"\", email: \"\", mobile: \"\", isVIP: true, }, appId: \"lucifer520\",}); 多端和多框架支持 Vue: 123456789101112import Vue form 'vue';import monitor from '@lucifer/connectors/vue';monitor.init({ user: { name: '', email: '', mobile: '', isVIP: true }, appId: 'lucifer520'})monitor.use(Vue) Wechat: 12345678910import monitor from \"@lucifer/connectors/wechat\";monitor.init({ user: { name: \"\", email: \"\", mobile: \"\", isVIP: true, }, appId: \"lucifer520\",}); 定义内部接口架构图: 接口系统交互图会在详细设计中给出,这里只给出大致范围: logs 服务器和告警平台的交互接口 rules 的规则解析 logs 的解析 构建系统对接 调试系统对接 … 业务形态特点 数据量会随着采集规模增大而增加,因此预估用户数量以及增长速度对系统架构设计有很大影响 终端的上报策略对影响很大,断网,弱网等情况如何上报也对结果有影响 框架选型 & 规范 & 约定暂无 其他解决方案 Badjs FrontJS","categories":[],"tags":[{"name":"RFC","slug":"RFC","permalink":"https://lucifer.ren/blog/tags/RFC/"},{"name":"技术调研","slug":"技术调研","permalink":"https://lucifer.ren/blog/tags/技术调研/"},{"name":"监控","slug":"监控","permalink":"https://lucifer.ren/blog/tags/监控/"}]},{"title":"算法小白如何高效、快速刷 leetcode?","slug":"刷题新手","date":"2020-06-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.694Z","comments":true,"path":"2020/06/20/刷题新手/","link":"","permalink":"https://lucifer.ren/blog/2020/06/20/刷题新手/","excerpt":"我本身刷了大概 600 道左右的题目,总结 200 多篇的题解,另外总结了十多个常见的算法专题,基本已经覆盖了大多数的常见考点和题型,全部放在我的 Github https://github.com/azl397985856/leetcode 。 然而作为一个新手,看着茫茫多的题解和资料难免会陷入一种“不知从何开始”的境地。不必担心,你不是一个人。 实际上,我最近一直在思考“初学者如何快速提高自己的算法能力,高效刷题”。因此我也一直在不断定位自己,最终我对自己作出了定位“用清晰直白的语言还原解题全过程,做西湖区最好的算法题解”。 然而我意识到,我进去了一个很大的误区。我的想法一直是“努力帮助算法小白提高算法能力,高效刷题”。然而算法小白除了清晰直白的算法题解外,还需要系统的前置知识。因此我的假设“大家都会基础的数据结构和算法”很可能就是不成立的。","text":"我本身刷了大概 600 道左右的题目,总结 200 多篇的题解,另外总结了十多个常见的算法专题,基本已经覆盖了大多数的常见考点和题型,全部放在我的 Github https://github.com/azl397985856/leetcode 。 然而作为一个新手,看着茫茫多的题解和资料难免会陷入一种“不知从何开始”的境地。不必担心,你不是一个人。 实际上,我最近一直在思考“初学者如何快速提高自己的算法能力,高效刷题”。因此我也一直在不断定位自己,最终我对自己作出了定位“用清晰直白的语言还原解题全过程,做西湖区最好的算法题解”。 然而我意识到,我进去了一个很大的误区。我的想法一直是“努力帮助算法小白提高算法能力,高效刷题”。然而算法小白除了清晰直白的算法题解外,还需要系统的前置知识。因此我的假设“大家都会基础的数据结构和算法”很可能就是不成立的。 小白阶段划分如果让我对算法小白进行一个阶段划分的话,我会将其分为: 阶段一 系统学习数据结构和算法知识。第一,你不能根本不懂得基础,比如根本不知道什么哈希表,或者只知道其简单的 API。 第二,你不能从网上不断搜索知识,因为这些知识是零散的,不利于新手形成自己的算法观。 当你成功跨越了上面两个坎,那么恭喜你,你可以进入下一个阶段啦。 对于这个阶段,想要跨过。需要系统性学习一些基础知识,推荐啃《算法 4》或者直接啃各个大学里面的教材。实在有困难的,可以先啃《算法图解》,《我的第一本算法书》这种入个门,然后再去啃。 阶段二 针对性刷题。比如按照力扣的标签去刷。因此上面的学习阶段并不见得你要学习完所有的基础再去刷,而是学习一个专题就可以针对性地刷。比如我学了二分法,就可以找一个二分法的题目刷一下。 想要跨越这一个坎,除了多做题之外,还有一个就是多看题解,多写题解。当然要看优秀的题解,这个我会在后面提到。 如果你跨越完上面两个坎,那么恭喜你, 你已经不是算法小白了(至少对于非算法岗来说)。 我的算法观继续回到刚才的问题“我的定位误区”。正因为很多小白没有跨越阶段一,因此我的所谓的“用清晰直白的语言还原解题全过程,做西湖区最好的算法题解”对他们没有实质帮助。他们迫切需要的是一个系统地整理算法思想,套路 的东西。因此我准备搞 91,这个就是后话,不再这里赘述。 注意,上面我提到了一个名次算法观。我并不知道这个词是否真的存在,不过这并不重要。如果不存在我就赋予其含义,如果存在我就来重新定义它。 算法观指的是你对于算法全面的认识。比如我拿到一个题目,如何审题,如何抽象成算法模型,如何根据模型选取合适的数据结构和算法。这就需要你对各种数据结构与算法的特性,使用场景有着身后的理解。 我举一个例子,这个例子就是今天(2020-06-12)我的 91 群的每日一题。 API 示例: 12345678910111213141516class LRUCache: def __init__(self, capacity: int): def get(self, key: int) -> int: def put(self, key: int, value: int) -> None:# Your LRUCache object will be instantiated and called as such:# obj = LRUCache(capacity)# param_1 = obj.get(key)# obj.put(key,value) 按照上面的过程,我们来套一个。 如何审题 看完题的话,只要抓住一个核心点即可。对于本题,核心点在于 删除最久未使用,O(1)时间。 抽象算法模型 这个题目是一个设计题。API 帮我们设计好了,只需要填充功能即可,也就是说算法模型不需要我们抽象了。 根据模型选取合适的数据结构和算法 我们的算法有两个操作:get 和 put。既然要支持这两个操作,肯定要有一个地方存数据。那么我们存到哪里呢?数组?链表?哈希表?其中链表又有很多,单向双向,循环不循环。 由于第一步审题过程中,我们获取到 O(1)时间 这个关键信息。那么: 数组无法做到更新,删除 $O(1)$ 链表无法做到查找,更新,删除 $O(1)$。 有的人说链表更新,删除是 $O(1)$,那么我要问你如何找到需要删除的节点呢?遍历找到的话最坏情况下就是 $O(N)$ 哈希表是无序的,因此不能实现 删除最久未使用。 似乎单独使用三种的任何一种都是不可以的。那么我们考虑组合多种数据结构。 我刚才说了链表只所以删除和更新都是 $O(N)$,是因为查找的时间损耗。 具体来说,我要删除图中值为 3 的节点,需要移动一次。因为我只能从头开始遍历去找。 (图 1) 又或者我要更新图中值为 7 的节点,则需要移动两次。 (图 2) 有没有什么办法可以省去这种遍历的时间损耗呢?其实我们的根本目的是找到目标节点, 而找到目标节点最暴力的方式是遍历。有没有巧妙一点的方法呢?毫无疑问,如果不借助额外的空间,这是不可能的。我们的想法只有空间换时间。 假设有这么一种数据结构,你告诉它你想要查的对象,它能帮你在 $O(1)$ 的时间内找到并返回给你结果。结合这个神秘数据结构和链表是不是我们就完成这道题了?这个神秘的数据结构就是哈希表。如果你对哈希表熟悉的话,想到几乎应该是瞬间的事情。如果不熟悉,那么经过排除,也应该可以得出这个结论。相信你随着做题数的增加,这种算法直觉会更加敏锐。 然而上面的空间复杂度是 $O(N)$。如果我的内存有限,不能承受 $O(N)$ 的空间,怎么办呢?相应地,我们可能就需要牺牲时间。那么问题是我们必须要退化到 $O(N)$ 么?显然不是,我们可以搞一些存档点。比如: 这样,我们需要操作 1 前面的,我们就从头开始遍历,如果需要操作 1 后面的,就从 1 开始遍历。时间复杂度最坏的情况可以降低到 $O(N / 2)$。通过进一步增加存档点,可以进一步减少时间,但是会增加空间。这是一种取舍。类似的取舍在实际工程中很多,这里不展开。 如果你了解过跳表, 实际上,上面的算法就是跳表的基本思想。 如果对每一道题你都能按照上面的流程走一遍,并且基于增加适当扩展,我相信你的刷题效率会高得可怕。 每道题都想这么多么?强烈建议新手都按照上面的逻辑进行思考,做题,并写题解总结。这样随着做题数的增加,量变引起质变,你会发现上面的几个步骤做下来很可能就是几秒钟的事情。如果你擅长图解,或者你经常看别人的图解(比如我的),那么这种图解能够帮你更快地检索大脑中的信息,这个时间会更短。 图解就是大脑检索信息的哈希表?哈哈,Maybe。 题解的水很深我看了很多人的题解直接就是两句话,然后跟上代码: 1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] 这种题解说实话,只针对那些”自己会, 然后去题解区看看有没有新的更好的解法的人“。但是大多数看题解的人是那种自己没思路,不会做的人。那么这种题解就没什么用了。 我认为好的题解应该是新手友好的,并且能够将解题人思路完整展现的题解。比如看到这个题目,我首先想到了什么(对错没有关系),然后头脑中经过怎么样的筛选将算法筛选到具体某一个或某几个。我的最终算法是如何想到的,有没有一些先行知识。 当然我也承认自己有很多题解也是直接给的答案,这对很多人来说用处不大,甚至有可能有反作用,给他们一种”我已经会了“的假象。实际上他们根本不懂解题人本身原本的想法, 也许是写题解的人觉得”这很自然“,也可能”只是为了秀技“。 刷题顺序最后给小白一个刷题顺序,帮助大家最大化利用自己的时间。 基础篇(30 天)基础永远是最重要的,先把最最基础的这些搞熟,磨刀不误砍柴工。 数组,队列,栈 链表 树与递归 哈希表 双指针 思想篇(30 天)这些思想是投资回报率极高的,强烈推荐每一个小的专题花一定的时间掌握。 二分 滑动窗口 搜索(BFS,DFS,回溯) 动态规划 提高篇(31 天)这部分收益没那么明显,并且往往需要一定的技术积累。出现的频率相对而言比较低。但是有的题目需要你使用这些技巧。又或者可以使用这些技巧可以实现降维打击。 贪心 分治 位运算 KMP & RK 并查集 前缀树 线段树 堆 最后目前,我本人也在写一本题解方面的书包括近期组织的 91 算法 ,其目标受众正是“阶段一到阶段二”。为了真正帮助刷题小白成长,我打算画三个月的时间对数据结构和算法进行系统总结,帮助大家跨过阶段一。当然我还会不断更新题解,通过清晰直白的方式来让大家跨越阶段二。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/categories/经验分享/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"经验分享","slug":"经验分享","permalink":"https://lucifer.ren/blog/tags/经验分享/"}]},{"title":"你不知道的 TypeScript 泛型(万字长文,建议收藏)","slug":"ts-generics","date":"2020-06-15T16:00:00.000Z","updated":"2023-01-07T12:34:34.555Z","comments":true,"path":"2020/06/16/ts-generics/","link":"","permalink":"https://lucifer.ren/blog/2020/06/16/ts-generics/","excerpt":"泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。 相信大家都经历过,看到过,或者正在写一些应用,这些应用充斥着各种重复类型定义, any 类型层出不穷,鼠标移到变量上面的提示只有 any,不要说类型操作了,类型能写对都是个问题。我也经历过这样的阶段,那个时候我对 TS 还比较陌生。 随着在 TS 方面学习的深入,越来越认识到 真正的 TS 高手都是在玩类型,对类型进行各种运算生成新的类型。这也好理解,毕竟 TS 提供的其实就是类型系统。你去看那些 TS 高手的代码,会各种花式使用泛型。 可以说泛型是一道坎,只有真正掌握它,你才知道原来 TS 还可以这么玩。怪不得面试的时候大家都愿意问泛型,尽管面试官很可能也不怎么懂。 只有理解事物的内在逻辑,才算真正掌握了,不然永远只是皮毛,不得其法。 本文就带你走进泛型,带你从另一个角度看看究竟什么是泛型,为什么要有它,它给 TS 带来了什么样的不同。 注意:不同语言泛型略有不同,知识迁移虽然可以,但是不能生搬硬套,本文所讲的泛型都指的是 TS 下的泛型。","text":"泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。 相信大家都经历过,看到过,或者正在写一些应用,这些应用充斥着各种重复类型定义, any 类型层出不穷,鼠标移到变量上面的提示只有 any,不要说类型操作了,类型能写对都是个问题。我也经历过这样的阶段,那个时候我对 TS 还比较陌生。 随着在 TS 方面学习的深入,越来越认识到 真正的 TS 高手都是在玩类型,对类型进行各种运算生成新的类型。这也好理解,毕竟 TS 提供的其实就是类型系统。你去看那些 TS 高手的代码,会各种花式使用泛型。 可以说泛型是一道坎,只有真正掌握它,你才知道原来 TS 还可以这么玩。怪不得面试的时候大家都愿意问泛型,尽管面试官很可能也不怎么懂。 只有理解事物的内在逻辑,才算真正掌握了,不然永远只是皮毛,不得其法。 本文就带你走进泛型,带你从另一个角度看看究竟什么是泛型,为什么要有它,它给 TS 带来了什么样的不同。 注意:不同语言泛型略有不同,知识迁移虽然可以,但是不能生搬硬套,本文所讲的泛型都指的是 TS 下的泛型。 引言我总结了一下,学习 TS 有两个难点。第一个是TS 和 JS 中容易混淆的写法,第二个是TS中特有的一些东西。 TS 中容易引起大家的混淆的写法 比如: (容易混淆的箭头函数) 再比如: (容易混淆的 interface 内的小括号) TS 中特有的一些东西 比如 typeof,keyof, infer 以及本文要讲的泛型。 把这些和 JS 中容易混淆的东西分清楚,然后搞懂 TS 特有的东西,尤其是泛型(其他基本上相对简单),TS 就入门了。 泛型初体验在强类型语言中,一般而言需要给变量指定类型才能使用该变量。如下代码: 12const name: string = \"lucifer\";console.log(name); 我们需要给 name 声明 string 类型,然后才能在后面使用 name 变量,当我们执行以下操作的时候会报错。 给 name 赋其他类型的值 使用其他类型值特有的方法(比如 Number 类型特有的 toFixed) 将 name 以参数传给不支持 string 的函数。 比如 divide(1, name),其中 divide 就是功能就是将第一个数(number 类型)除以第二个数(number 类型),并将结果返回。 TS 除了提供一些基本类型(比如上面的 string)供我们直接使用。还: 提供了 inteface 和 type 关键字供我们定义自己的类型,之后就能像使用基本类型一样使用自己定义的类型了。 提供了各种逻辑运算符,比如 &, | 等 ,供我们对类型进行操作,从而生成新的类型。 提供泛型,允许我们在定义的时候不具体指定类型,而是泛泛地说一种类型,并在函数调用的时候再指定具体的参数类型。 。。。 也就是说泛型也是一种类型,只不过不同于 string, number 等具体的类型,它是一种抽象的类型,我们不能直接定义一个变量类型为泛型。 简单来说,区别于平时我们对值进行编程,泛型是对类型进行编程。这个听起来比较抽象。之后我们会通过若干实例带你理解这句话,你先留一个印象就好。 为了明白上面这句话,·首先要区分“值”和“类型”。 值和类型我们平时写代码基本都是对值编程。比如: 123456789if (person.isVIP) { console.log('VIP')}if (cnt > 5) { // do something}const personNames = persons.map(p => p.name)... 可以看出这都是对具体的值进行编程,这符合我们对现实世界的抽象。从集合论的角度上来说, 值的集合就是类型,在 TS 中最简单的用法是对值限定类型,从根本上来说是限定值的集合。这个集合可以是一个具体的集合,也可以是多个集合通过集合运算(交叉并)生成的新集合。 (值和类型) 再来看一个更具体的例子: 1234function t(name: string) { return `hello, ${name}`;}t(\"lucifer\"); 字符串 “lucifer” 是 string 类型的一个具体值。 在这里 “lucifer” 就是值,而 string 就是类型。 TS 明白 “lucifer” 是 string 集合中的一个元素,因此上面代码不会有问题,但是如果是这样就会报错: 1t(123); 因为 123 并不是 string 集合中的一个元素。 对于 t(“lucifer”)而言,TS 判断逻辑的伪代码: 123456v = getValue(); // will return 'lucifer' by astif (typeof v === \"string\") { // ok} else { throw \"type error\";} 由于是静态类型分析工具,因此 TS 并不会执行 JS 代码,但并不是说 TS 内部没有执行逻辑。 简单来总结一下就是: 值的集合就是类型,平时写代码基本都是对值编程,TS 提供了很多类型(也可以自定义)以及很多类型操作帮助我们限定值以及对值的操作。 什么是泛型上面已经铺垫了一番,大家已经知道了值和类型的区别,以及 TS 究竟帮我们做了什么事情。但是直接理解泛型仍然会比较吃力,接下来我会通过若干实例,慢慢带大家走进泛型。 首先来思考一个问题:为什么要有泛型呢?这个原因实际上有很多,在这里我选择大家普遍认同的一个切入点来解释。如果你明白了这个点,其他点相对而言理解起来会比较轻松。还是通过一个例子来进行说明。 不容小觑的 id 函数假如让你实现一个函数 id,函数的参数可以是任何值,返回值就是将参数原样返回,并且其只能接受一个参数,你会怎么做? 你会觉得这很简单,顺手就写出这样的代码: 1const id = (arg) => arg; 有的人可能觉得 id 函数没有什么实际作用。其实不然, id 函数在函数式编程中应用非常广泛。 由于其可以接受任意值,也就是说你的函数的入参和返回值都应该可以是任意类型。 现在让我们给代码增加类型声明: 1234type idBoolean = (arg: boolean) => boolean;type idNumber = (arg: number) => number;type idString = (arg: string) => string;... 一个笨的方法就像上面那样,也就是说 JS 提供多少种类型,就需要复制多少份代码,然后改下类型签名。这对程序员来说是致命的。这种复制粘贴增加了出错的概率,使得代码难以维护,牵一发而动全身。并且将来 JS 新增新的类型,你仍然需要修改代码,也就是说你的代码对修改开放,这样不好。还有一种方式是使用 any 这种“万能语法”。缺点是什么呢?我举个例子: 1234id(\"string\").length; // okid(\"string\").toFixed(2); // okid(null).toString(); // ok... 如果你使用 any 的话,怎么写都是 ok 的, 这就丧失了类型检查的效果。实际上我知道我传给你的是 string,返回来的也一定是 string,而 string 上没有 toFixed 方法,因此需要报错才是我想要的。也就是说我真正想要的效果是:当我用到id的时候,你根据我传给你的类型进行推导。比如我传入的是 string,但是使用了 number 上的方法,你就应该报错。 为了解决上面的这些问题,我们使用泛型对上面的代码进行重构。和我们的定义不同,这里用了一个 类型 T,这个 T 是一个抽象类型,只有在调用的时候才确定它的值,这就不用我们复制粘贴无数份代码了。 123function id<T>(arg: T): T { return arg;} 为什么这样就可以了? 为什么要用这种写法?这个尖括号什么鬼?万物必有因果,之所以这么设计泛型也是有原因的。那么就让我来给大家解释一下,相信很多人都没有从这个角度思考过这个问题。 泛型就是对类型编程上面提到了一个重要的点 平时我们都是对值进行编程,泛型是对类型进行编程。上面我没有给大家解释这句话。现在铺垫足够了,那就让我们开始吧! 继续举一个例子:假如我们定义了一个 Person 类,这个 Person 类有三个属性,并且都是必填的。这个 Person 类会被用于用户提交表单的时候限定表单数据。 12345678910enum Sex { Man, Woman, UnKnow,}interface Person { name: string; sex: Sex; age: number;} 突然有一天,公司运营想搞一个促销活动,也需要用到 Person 这个 shape,但是这三个属性都可以选填,同时要求用户必须填写手机号以便标记用户和接受短信。一个很笨的方法是重新写一个新的类: 123456interface MarketPerson { name?: string; sex?: Sex; age?: number; phone: string;} 还记得我开头讲的重复类型定义么? 这就是! 这明显不够优雅。如果 Person 字段很多呢?这种重复代码会异常多,不利于维护。 TS 的设计者当然不允许这么丑陋的设计存在。那么是否可以根据已有类型,生成新的类型呢?当然可以!答案就是前面我提到了两种对类型的操作:一种是集合操作,另一种是今天要讲的泛型。 先来看下集合操作: 1type MarketPerson = Person & { phone: string }; 这个时候我们虽然添加了一个必填字段 phone,但是没有做到name, sex, age 选填,似乎集合操作做不到这一点呀。我们脑洞一下,假如我们可以像操作函数那样操作类型,是不是有可能呢?比如我定义了一个函数 Partial,这个函数的功能入参是一个类型,返回值是新的类型,这个类型里的属性全部变成可选的。 伪代码: 12345678910function Partial(Type) { type ans = 空类型 for(k in Type) { 空类型[k] = makeOptional(Type, k) } return ans}type PartialedPerson = Partial(Person) 可惜的是上面代码不能运行,也不可能运行。不可能运行的原因有: 这里使用函数 Partial 操作类型,可以看出上面的函数我是没有添加签名的,我是故意的。如果让你给这个函数添加签名你怎么加?没办法加! 这里使用 JS 的语法对类型进行操作,这是不恰当的。首先这种操作依赖了 JS 运行时,而 TS 是静态分析工具,不应该依赖 JS 运行时。其次如果要支持这种操作是否意味者 TS 对 JS 妥协,JS 出了新的语法(比如早几年出的 async await),TS 都要支持其对 TS 进行操作。 因此迫切需要一种不依赖 JS 行为,特别是运行时行为的方式,并且逻辑其实和上面类似的,且不会和现有语法体系冲突的语法。 我们看下 TS 团队是怎么做的: 1234// 可以看成是上面的函数定义,可以接受任意类型。由于是这里的 “Type” 形参,因此理论上你叫什么名字都是无所谓的,就好像函数定义的形参一样。type Partial<Type> = { do something }// 可以看成是上面的函数调用,调用的时候传入了具体的类型 Persontype PartialedPerson = Partial<Person> 先不管功能,我们来看下这两种写法有多像: (定义) (运行) 再来看下上面泛型的功能。上面代码的意思是对 T 进行处理,是返回一个 T 的子集,具体来说就是将 T 的所有属性变成可选。这时 PartialedPerson 就等于 : 12345interface Person { name?: string; sex?: Sex; age?: number;} 功能和上面新建一个新的 interface 一样,但是更优雅。 最后来看下泛型 Partial 的具体实现,可以看出其没有直接使用 JS 的语法,而是自己定义了一套语法,比如这里的 keyof,至此完全应证了我上面的观点。 1type Partial<T> = { [P in keyof T]?: T[P] }; 刚才说了“由于是形参,因此起什么名字无所谓” 。因此这里就起了 T 而不是 Type,更短了。这也算是一种约定俗称的规范,大家一般习惯叫 T, U 等表示泛型的形参。 我们来看下完整的泛型和函数有多像! (定义) (使用) 从外表看只不过是 function 变成了 type,() 变成了 <>而已。 从语法规则上来看, 函数内部对标的是 ES 标准。而泛型对应的是 TS 实现的一套标准。 简单来说,将类型看成值,然后对类型进行编程,这就是泛型的基本思想。泛型类似我们平时使用的函数,只不过其是作用在类型上,思想上和我们平时使用的函数并没有什么太多不同,泛型产生的具体类型也支持类型的操作。比如: 1type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>; 有了上面的知识,我们通过几个例子来巩固一下。 123function id<T, U>(arg1: T, arg2: U): T { return arg1;} 上面定义了泛型 id,其入参分别是 T 和 U,和函数参数一样,使用逗号分隔。定义了形参就可以在函数体内使用形参了。如上我们在函数的参数列表和返回值中使用了形参 T 和 U。 返回值也可以是复杂类型: 123function ids<T, U>(arg1: T, arg2: U): [T, U] { return [arg1, arg2];} (泛型的形参) 和上面类似, 只不过返回值变成了数组而已。 需要注意的是,思想上我们可以这样去理解。但是具体的实现过程会有一些细微差别,比如: 1234type P = [number, string, boolean];type Q = Date;type R = [Q, ...P]; // A rest element type must be an array type. 再比如: 1234567type Lucifer = LeetCode;type LeetCode<T = {}> = { name: T;};const a: LeetCode<string>; //okconst a: Lucifer<string>; // Type 'Lucifer' is not generic. 改成这样是 ok 的: 1type Lucifer<T> = LeetCode<T>; 泛型为什么使用尖括号为什么泛型要用尖括号(<>),而不是别的? 我猜是因为它和 () 长得最像,且在现在的 JS 中不会有语法歧义。但是,它和 JSX 不兼容!比如: 1234567function Form() { // ... return ( <Select<string> options={targets} value={target} onChange={setTarget} /> );} 这是因为 TS 发明这个语法的时候,还没想过有 JSX 这种东西。后来 TS 团队在 TypeScript 2.9 版本修复了这个问题。也就是说现在你可以直接在 TS 中使用带有泛型参数的 JSX 啦(比如上面的代码)。 泛型的种类实际上除了上面讲到的函数泛型,还有接口泛型和类泛型。不过语法和含义基本同函数泛型一样: 1234interface id<T, U> { id1: T; id2: U;} (接口泛型) 123class MyComponent extends React.Component<Props, State> { ...} (类泛型) 总结下就是: 泛型的写法就是在标志符后面添加尖括号(<>),然后在尖括号里写形参,并在 body(函数体, 接口体或类体) 里用这些形参做一些逻辑处理。 泛型的参数类型 - “泛型约束”正如文章开头那样,我们可以对函数的参数进行限定。 1234function t(name: string) { return `hello, ${name}`;}t(\"lucifer\"); 如上代码对函数的形参进行了类型限定,使得函数仅可以接受 string 类型的值。那么泛型如何达到类似的效果呢? 1type MyType = (T: constrain) => { do something }; 还是以 id 函数为例,我们给 id 函数增加功能,使其不仅可以返回参数,还会打印出参数。熟悉函数式编程的人可能知道了,这就是 trace 函数,用于调试程序。 1234function trace<T>(arg: T): T { console.log(arg); return arg;} 假如我想打印出参数的 size 属性呢?如果完全不进行约束 TS 是会报错的: 注意:不同 TS 版本可能提示信息不完全一致,我的版本是 3.9.5。下文的所有测试结果均是使用该版本,不再赘述。 1234function trace<T>(arg: T): T { console.log(arg.size); // Error: Property 'size doesn't exist on type 'T' return arg;} 报错的原因在于 T 理论上是可以是任何类型的,不同于 any,你不管使用它的什么属性或者方法都会报错(除非这个属性和方法是所有集合共有的)。那么直观的想法是限定传给 trace 函数的参数类型应该有 size 类型,这样就不会报错了。如何去表达这个类型约束的点呢?实现这个需求的关键在于使用类型约束。 使用 extends 关键字可以做到这一点。简单来说就是你定义一个类型,然后让 T 实现这个接口即可。 1234567interface Sizeable { size: number;}function trace<T extends Sizeable>(arg: T): T { console.log(arg.size); return arg;} 这个时候 T 就不再是任意类型,而是被实现接口的 shape,当然你也可以继承多个接口。类型约束是非常常见的操作,大家一定要掌握。 有的人可能说我直接将 Trace 的参数限定为 Sizeable 类型可以么?如果你这么做,会有类型丢失的风险,详情可以参考这篇文章A use case for TypeScript Generics。 常见的泛型集合类大家平时写 TS 一定见过类似 Array<String> 这种写法吧? 这其实是集合类,也是一种泛型。 本质上数组就是一系列值的集合,这些值可以可以是任意类型,数组只是一个容器而已。然而平时开发的时候通常数组的项目类型都是相同的,如果不加约束的话会有很多问题。 比如我应该是一个字符串数组,然是却不小心用到了 number 的方法,这个时候类型系统应该帮我识别出这种类型问题。 由于数组理论可以存放任意类型,因此需要使用者动态决定你想存储的数据类型,并且这些类型只有在被调用的时候才能去确定。 Array<String> 就是调用,经过这个调用会产生一个具体集合,这个集合只能存放 string 类型的值。 不调用直接把 Array 是不被允许的: 1const a: Array = [\"1\"]; 如上代码会被错:Generic type 'Array<T>' requires 1 type argument(s).ts 。 有没有觉得和函数调用没传递参数报错很像?像就对了。 这个时候你再去看 Set, Promise,是不是很快就知道啥意思了?它们本质上都是包装类型,并且支持多种参数类型,因此可以用泛型来约束。 React.FC大家如果开发过 React 的 TS 应用,一定知道 React.FC 这个类型。我们来看下它是如何定义的: 123456789type FC<P = {}> = FunctionComponent<P>;interface FunctionComponent<P = {}> { (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null; propTypes?: WeakValidationMap<P>; contextTypes?: ValidationMap<any>; defaultProps?: Partial<P>; displayName?: string;} 可以看出其大量使用了泛型。你如果不懂泛型怎么看得懂呢?不管它多复杂,我们从头一点点分析就行,记住我刚才讲的类比方法,将泛型类比到函数进行理解。· 首先定义了一个泛型类型 FC,这个 FC 就是我们平时用的 React.FC。它是通过另外一个泛型 FunctionComponent 产生的。 因此,实际上第一行代码的作用就是起了一个别名 FunctionComponent 实际上是就是一个接口泛型,它定义了五个属性,其中四个是可选的,并且是静态类属性。 displayName 比较简单,而 propTypes,contextTypes,defaultProps 又是通过其他泛型生成的类型。我们仍然可以采用我的这个分析方法继续分析。由于篇幅原因,这里就不一一分析,读者可以看完我的分析过程之后,自己尝试分析一波。 (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null; 的含义是 FunctionComponent 是一个函数,接受两个参数(props 和 context )返回 ReactElement 或者 null。ReactElement 大家应该比较熟悉了。PropsWithChildren 实际上就是往 props 中插入 children,源码也很简单,代码如下: 1type PropsWithChildren<P> = P & { children?: ReactNode }; 这不就是我们上面讲的集合操作和 可选属性么?至此,React.FC 的全貌我们已经清楚了。读者可以试着分析别的源码检测下自己的学习效果,比如 React.useState 类型的签名。 类型推导与默认参数类型推导和默认参数是 TS 两个重要功能,其依然可以作用到泛型上,我们来看下。 类型推导我们一般常见的类型推导是这样的: 123const a = \"lucifer\"; // 我们没有给 a 声明类型, a 被推导为 stringa.toFixed(); // Property 'toFixed' does not exist on type 'string'.a.includes(\"1\"); // ok 需要注意的是,类型推导是仅仅在初始化的时候进行推导,如下是无法正确推导的: 12345let a = \"lucifer\"; // 我们没有给 a 声明类型, a 被推导为stringa.toFixed(); // Property 'toFixed' does not exist on type 'string'.a.includes(\"1\"); // oka = 1;a.toFixed(); // 依然报错, a 不会被推导 为 number 而泛型也支持类型推导,以上面的 id 函数为例: 12345function id<T>(arg: T): T { return arg;}id<string>(\"lucifer\"); // 这是ok的,也是最完整的写法id(\"lucifer\"); // 基于类型推导,我们可以这样简写 这也就是为什么 useState 有如下两种写法的原因。 12const [name, setName] = useState(\"lucifer\");const [name, setName] = useState<string>(\"lucifer\"); 实际的类型推导要更加复杂和智能。相信随着时间的推进,TS 的类型推导会更加智能。 默认参数和类型推导相同的点是,默认参数也可以减少代码量,让你少些代码。前提是你要懂,不然伴随你的永远是大大的问号。其实你完全可以将其类比到函数的默认参数来理解。 举个例子: 1234type A<T = string> = Array<T>;const aa: A = [1]; // type 'number' is not assignable to type 'string'.const bb: A = [\"1\"]; // okconst cc: A<number> = [1]; // ok 上面的 A 类型默认是 string 类型的数组。你可以不指定,等价于 Array,当然你也可以显式指定数组类型。有一点需要注意:在 JS 中,函数也是值的一种,因此: 1const fn = () => null; // ok 但是泛型这样是不行的,这是和函数不一样的地方(设计缺陷?Maybe): 1type A = Array; // error: Generic type 'Array<T>' requires 1 type argument(s). 其原因在与 Array 的定义是: 123interface Array<T> { ...} 而如果 Array 的类型也支持默认参数的话,比如: 123interface Array<T = string> { ...} 那么 type A = Array; 就是成立的,如果不指定的话,会默认为 string 类型。 什么时候用泛型如果你认真看完本文,相信应该知道什么时候使用泛型了,我这里简单总结一下。 当你的函数,接口或者类: 需要作用到很多类型的时候,比如我们介绍的 id 函数的泛型声明。 需要被用到很多地方的时候,比如我们介绍的 Partial 泛型。 进阶上面说了泛型和普通的函数有着很多相似的地方。普通的函数可以嵌套其他函数,甚至嵌套自己从而形成递归。泛型也是一样! 泛型支持函数嵌套比如: 1type CutTail<Tuple extends any[]> = Reverse<CutHead<Reverse<Tuple>>>; 如上代码中, Reverse 是将参数列表反转,CutHead 是将数组第一项切掉。因此 CutTail 的意思就是将传递进来的参数列表反转,切掉第一个参数,然后反转回来。换句话说就是切掉参数列表的最后一项。 比如,一个函数是 function fn (a: string, b: number, c: boolean):boolean {},那么经过操作type cutTailFn = CutTail<typeof fn>,可以返回(a: string, b:number) => boolean。 具体实现可以参考Typescript 复杂泛型实践:如何切掉函数参数表的最后一个参数?。 在这里,你知道泛型支持嵌套就够了。 泛型支持递归泛型甚至可以嵌套自己从而形成递归,比如我们最熟悉的单链表的定义就是递归的。 1234type ListNode<T> = { data: T; next: ListNode<T> | null;}; (单链表) 再比如 HTMLElement 的定义。 1234declare var HTMLElement: { prototype: HTMLElement; new(): HTMLElement;};。 (HTMLElement) 上面是递归声明,我们再来看一个更复杂一点的递归形式 - 递归调用,这个递归调用的功能是:递归地将类型中所有的属性都变成可选。类似于深拷贝那样,只不过这不是拷贝操作,而是变成可选,并且是作用在类型,而不是值。 1234567type DeepPartial<T> = T extends Function ? T : T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;type PartialedWindow = DeepPartial<Window>; // 现在window 上所有属性都变成了可选啦 TS 泛型工具及实现虽然泛型支持函数的嵌套,甚至递归,但是其语法能力肯定和 JS 没法比, 想要实现一个泛型功能真的不是一件容易的事情。这里提供几个例子,看完这几个例子,相信你至少可以达到比葫芦画瓢的水平。这样多看多练,慢慢水平就上来了。 截止目前(2020-06-21),TS 提供了 16 种工具类型。 (官方提供的工具类型) 除了官方的工具类型,还有一些社区的工具类型,比如type-fest,你可以直接用或者去看看源码看看高手是怎么玩类型的。 我挑选几个工具类,给大家讲一下实现原理。 Partial功能是将类型的属性变成可选。注意这是浅 Partial,DeepPartial 上面我讲过了,只要配合递归调用使用即可。 1type Partial<T> = { [P in keyof T]?: T[P] }; Required功能和Partial 相反,是将类型的属性变成必填, 这里的 -指的是去除。 -? 意思就是去除可选,也就是必填啦。 1type Required<T> = { [P in keyof T]-?: T[P] }; Mutable功能是将类型的属性变成可修改,这里的 -指的是去除。 -readonly 意思就是去除只读,也就是可修改啦。 123type Mutable<T> = { -readonly [P in keyof T]: T[P];}; Readonly功能和Mutable 相反,功能是将类型的属性变成只读, 在属性前面增加 readonly 意思会将其变成只读。 1type Readonly<T> = { readonly [P in keyof T]: T[P] }; ReturnType功能是用来得到一个函数的返回值类型。 12345type ReturnType<T extends (...args: any[]) => any> = T extends ( ...args: any[]) => infer R ? R : any; 下面的示例用 ReturnType 获取到 Func 的返回值类型为 string,所以,foo 也就只能被赋值为字符串了。 123type Func = (value: number) => string;const foo: ReturnType<Func> = \"1\"; 更多参考TS - es5.d.ts 这些泛型可以极大减少大家的冗余代码,大家可以在自己的项目中自定义一些工具类泛型。 Bonus - 接口智能提示最后介绍一个实用的小技巧。如下是一个接口的类型定义: 1234567891011interface Seal { name: string; url: string;}interface API { \"/user\": { name: string; age: number; phone: string }; \"/seals\": { seal: Seal[] };}const api = <URL extends keyof API>(url: URL): Promise<API[URL]> => { return fetch(url).then((res) => res.json());}; 我们通过泛型以及泛型约束,实现了智能提示的功能。使用效果: (接口名智能提示) (接口返回智能提示) 原理很简单,当你仅输入 api 的时候,其会将 API interface 下的所有 key 提示给你,当你输入某一个 key 的时候,其会根据 key 命中 interface 定义的类型,然后给予类型提示。 总结学习 Typescript 并不是一件简单的事情,尤其是没有其他语言背景的情况。而 TS 中最为困难的内容之一恐怕就是泛型了。 泛型和我们平时使用的函数是很像的,如果将两者进行横向对比,会很容易理解,很多函数的都关系可以迁移到泛型,比如函数嵌套,递归,默认参数等等。泛型是对类型进行编程,参数是类型,返回值是一个新的类型。我们甚至可以对泛型的参数进行约束,就类似于函数的类型约束。 最后通过几个高级的泛型用法以及若干使用的泛型工具类帮助大家理解和消化上面的知识。要知道真正的 TS 高手都是玩类型的,高手才不会满足于类型的交叉并操作。 泛型用的好确实可以极大减少代码量,提高代码维护性。如果用的太深入,也可能会团队成员面面相觑,一脸茫然。因此抽象层次一定要合理,不仅仅是泛型,整个软件工程都是如此。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"TypeScript","slug":"前端/TypeScript","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/"},{"name":"泛型","slug":"前端/TypeScript/泛型","permalink":"https://lucifer.ren/blog/categories/前端/TypeScript/泛型/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://lucifer.ren/blog/tags/TypeScript/"},{"name":"泛型","slug":"泛型","permalink":"https://lucifer.ren/blog/tags/泛型/"}]},{"title":"immutablejs 是如何优化我们的代码的?","slug":"immutable-js","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-07T12:34:34.258Z","comments":true,"path":"2020/06/13/immutable-js/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/immutable-js/","excerpt":"前一段时间群里有小伙伴问 lucifer 我一个问题:”immutablejs 是什么?有什么用?“。我当时的回答是:immutablejs 就是 tree + sharing,解决了数据可变性带来的问题,并顺便优化了性能。今天给大家来详细解释一下这句话。","text":"前一段时间群里有小伙伴问 lucifer 我一个问题:”immutablejs 是什么?有什么用?“。我当时的回答是:immutablejs 就是 tree + sharing,解决了数据可变性带来的问题,并顺便优化了性能。今天给大家来详细解释一下这句话。 背景我们还是通过一个例子来进行说明。如下是几个普通地不能再普通的赋值语句: 123456789a = 1;b = 2;c = 3;d = { name: \"lucifer\", age: 17, location: \"西湖\",};e = [\"脑洞前端\", \"力扣加加\"]; 上面代码的内存结构大概是这样的: lucifer 小提示:可以看出,变量名( a,b,c,d,e )只是内存地址的别名而已 由于 d 和 e 的值是引用类型,数据长度不确定,因此实际上数据区域会指向堆上的一块区域。而 a,b,c 由于长度是编译时确定的,因此可以方便地在栈上存储。 lucifer 小提示:d 和 e 的数据长度不确定, 但指针的长度是确定的,因此可以在栈上存储指针,指针指向堆上内存即可。 实际开发我们经常会进行各种赋值操作,比如: 12345const ca = a;const cb = b;const cc = c;const cd = d;const ce = e; 经过上面的操作,此时的内存结构图: 可以看出,ca,cb,cc,cd,ce 的内存地址都变了,但是值都没变。原因在于变量名只是内存的别名而已,而赋值操作传递的是 value。 由于目前 JS 对象操作都是 mutable 的, 因此就有可能会发生这样的 “bug”: 123cd.name = \"azl397985856\";console.log(cd.name); // azl397985856console.log(d.name); // azl397985856 上面的 cd.name 原地修改了 cd 的 name 值,这会影响所有指向 ta 的引用。 比如有一个对象被三个指针引用,如果对象被修改了,那么三个指针都会有影响。 你可以把指针看成线程,对象看成进程资源,资源会被线程共享。 多指针就是多线程,当多个线程同时对一个对象进行读写操作就可能会有问题。 于是很多人的做法是 copy(shallow or deep)。这样多个指针的对象都是不同的,可以看成多进程。 接下来我们进行一次 copy 操作。 12345678const sa = a;const sb = b;const sc = c;const sd = { ...d };const se = [...e];// 有的人还觉得不过瘾const sxbk = JSON.parse(JSON.stringify(e)); 旁观者: 为啥你代码那么多 copy 啊?当事人: 我也不知道为啥要 copy 一下,不过这样做使我安心。 此时引用类型的 value 全部发生了变化,此时内存图是这样的: 上面的 ”bug“ 成功解决。 lucifer 小提示: 如果你使用的是 shallow copy, 其内层的对象 value 是不会变化的。如果此时你对内层对象进行诸如 a.b.c 的操作,也会有”bug“。 完整内存图: (看不清可以尝试放大) 问题如果是 shallow copy 还好, 因为你只 copy 一层,但是随着 key 的增加,性能下降还是比较明显的。 据测量: shallow copy 包含 1w 个 属性的对象大概要 10 ms。 deep copy 一个三层的 1w 个属性的对象大概要 50 ms。 而 immutablejs 可以帮助我们减少这种时间(和内存)开销,这个我们稍后会讲。 数据仅供参考,大家也可以用自己的项目测量一下。 由于普通项目很难达到这个量级,因此基本结论是:如果你的项目对象不会很大, 完全没必要考虑诸如 immutablejs 进行优化,直接手动 copy 实现 immutable 即可。 如果我的项目真的很大呢?那么你可以考虑使用 immutable 库来帮你。 immutablejs 是无数 immutable 库中的一个。我们来看下 immutablejs 是如何解决这个性能难题的。 immutablejs 是什么使用 immutablejs 提供的 API 操作数据,每一次操作都会返回一个新的引用,效果类似 deep copy,但是性能更好。 开头我说了,immutablejs 就是 tree + sharing,解决了数据可变带来的问题,并顺便提供了性能。 其中这里的 tree 就是类似 trie 的一棵树。如果对 trie 不熟悉的,可以看下我之前写的一篇前缀树专题。 immutablejs 就是通过树实现的结构共享。举个例子: 1const words = [\"lucif\", \"luck\"]; 我根据 words 构建了一个前缀树,节点不存储数据, 数据存储在路径上。其中头节点表示的是对象的引用地址。 这样我们就将两个单词 lucif 和 luck存到了树上: 现在我想要将 lucif 改成 lucie,普通的做法是完全 copy 一份,之后修改即可。 12newWords = [...words];newWords[1] = \"lucie\"; (注意这里整棵树都是新的,你看根节点的内存地址已经变了) 而所谓的状态共享是: (注意这里整棵树除了新增的一个节点, 其他都是旧的,你看根节点的内存地址没有变) 可以看出,我们只是增加了一个节点,并改变了一个指针而已,其他都没有变化,这就是所谓的结构共享。 还是有问题仔细观察会发现:使用我们的方法,会造成 words 和 newWords 引用相等(都是 1fe2ab),即 words === newWords。 因此我们需要沿着路径回溯到根节点,并修改沿路的所有节点(绿色部分)。在这个例子,我们仅仅少修改两个节点。但是随着树的节点增加,公共前缀也会随着增加,那时性能提升会很明显。 整个过程类似下面的动图所示: 这个过程非常类似线段树的更新区间信息的过程 取舍之间前面提到了 沿着路径回溯到根节点,并修改沿路的所有节点。由于树的总节点数是固定的,因此当树很高的时候,某一个节点的子节点数目会很少,节点的复用率会很低。想象一个极端的情况,树中所有的节点只有一个子节点,此时退化到链表,每次修改的时间复杂度为 O(P),其中 P 为其祖先节点的个数。如果此时修改的是叶子节点,那么 P 就等于 N,其中 N 为 树的节点总数。 树很矮的情况,树的子节点数目会增加,因此每次回溯需要修改的指针增加。如图是有四个子节点的情况,相比于上面的两个子节点,需要多创建两个指针。 想象一种极端的情况,树只有一层。还是将 lucif 改成 lucie。我们此时只能重新建立一个全新的 lucie 节点,无法利用已有节点,此时和 deep copy 相比没有一点优化。 因此合理选择树的叉数是一个难点,绝对不是简单的二叉树就行了。这个选择往往需要做很多实验才能得出一个相对合理的值。 ReactReact 和 Vue 最大的区别之一就是 React 更加 “immutable”。React 更倾向于数据不可变,而 Vue 则相反。如果你恰好两个框架都使用过,应该明白我的意思。 使用 immutable 的一个好处是未来的操作不会影响之前创建的对象。因此你可以很轻松地将应用的数据进行持久化,以便发送给后端做调试分析或者实现时光旅行(感谢可预测的单向数据流)。 结合 Redux 等状态管理框架,immutablejs 可以发挥更大的作用。这个时候,你的整个 state tree 应该是 immutablejs 对象,不需要使用普通的 JavaScript 对象,并且操作也需要使用 immutablejs 提供的 API 来进行。 并且由于有了 immutablejs,我们可以很方便的使用全等 === 判断。写 SCU 也方便多了。 SCU 是 shouldComponentUpdate 的缩写。 通过我的几年使用经验来看,使用类似 immutablejs 的库,会使得性能有不稳定的提升。并且由于多了一个库,调试成本或多或少有所增加,并且有一定的理解和上手成本。因此我的建议是技术咱先学着,如果项目确实需要使用,团队成员技术也可以 Cover的话,再接入也不迟,不可过早优化。 总结由于数据可变性,当多个指针指向同一个引用,其中一个指针修改了数据可能引发”不可思议“的效果。随着项目规模的增大,这种情况会更加普遍。并且由于未来的操作可能会修改之前创建的对象,因此无法获取中间某一时刻的状态,这样就缺少了中间的链路,很难进行调试 。数据不可变则是未来的操作不会影响之前创建的对象,这就减少了”不可思议“的现象,并且由于我们可以知道任何中间状态,因此调试也会变得轻松。 手动实现”数据不可变“可以应付大多数情况。在极端情况下,才会有性能问题。immutablejs 就是 tree + sharing,解决了数据可变带来的问题,并顺便优化了性能。它不但解决了手动 copy 的性能问题,而且可以在 $O(1)$ 的时间比较一个对象是否发生了变化。因此搭配 React 的 SCU 优化 React 应用会很香。 最后推荐我个人感觉不错的另外两个 immutable 库 seamless-immutable 和 Immer。 关注我大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。 知乎专栏【 Lucifer - 知乎】 点关注,不迷路!","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"React","slug":"React","permalink":"https://lucifer.ren/blog/categories/React/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"React","slug":"React","permalink":"https://lucifer.ren/blog/tags/React/"},{"name":"immutablejs","slug":"immutablejs","permalink":"https://lucifer.ren/blog/tags/immutablejs/"},{"name":"immutable","slug":"immutable","permalink":"https://lucifer.ren/blog/tags/immutable/"}]},{"title":"【LeetCode题解】1186.删除一次得到子数组最大和","slug":"leetcode-1186","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.895Z","comments":true,"path":"2020/06/13/leetcode-1186/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-1186/","excerpt":"LeetCode 1186 题,最大子数组的进阶版本。","text":"LeetCode 1186 题,最大子数组的进阶版本。 题目地址https://leetcode.com/problems/maximum-subarray-sum-with-one-deletion/ 题目描述12345678910111213141516171819202122232425262728293031给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。注意,删除一个元素后,子数组 不能为空。请看示例:示例 1:输入:arr = [1,-2,0,3]输出:4解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。示例 2:输入:arr = [1,-2,-2,3]输出:3解释:我们直接选出 [3],这就是最大和。示例 3:输入:arr = [-1,-1,-1,-1]输出:-1解释:最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。 我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。 提示:1 <= arr.length <= 10^5-10^4 <= arr[i] <= 10^4 思路暴力法符合知觉的做法是求出所有的情况,然后取出最大的。 我们只需要两层循环接口,外循环用于确定我们丢弃的元素,内循环用于计算 subArraySum。 12345678910111213141516class Solution: def maximumSum(self, arr: List[int]) -> int: res = arr[0] def maxSubSum(arr, skip): res = maxSub = float(\"-inf\") for i in range(len(arr)): if i == skip: continue maxSub = max(arr[i], maxSub + arr[i]) res = max(res, maxSub) return res# 这里循环到了len(arr)项,表示的是一个都不删除的情况 for i in range(len(arr) + 1): res = max(res, maxSubSum(arr, i)) return res 空间换时间上面的做法在 LC 上会 TLE, 因此我们需要换一种思路,既然超时了,我们是否可以从空间换时间的角度思考呢?我们可以分别从头尾遍历,建立两个 subArraySub 的数组 l 和 r。 其实这个不难想到,很多题目都用到了这个技巧。 具体做法: 一层遍历, 建立 l 数组,l[i]表示从左边开始的以 arr[i]结尾的 subArraySum 的最大值 一层遍历, 建立 r 数组,r[i]表示从右边开始的以 arr[i]结尾的 subArraySum 的最大值 一层遍历, 计算 l[i - 1] + r[i + 1] 的最大值 l[i - 1] + r[i + 1]的含义就是删除 arr[i]的子数组最大值 上面的这个步骤得到了删除一个的子数组最大值, 不删除的只需要在上面循环顺便计算一下即可 123456789101112131415161718class Solution: def maximumSum(self, arr: List[int]) -> int: n = len(arr) l = [arr[0]] * n r = [arr[n - 1]] * n if n == 1: return arr[0] res = arr[0] for i in range(1, n): l[i] = max(l[i - 1] + arr[i], arr[i]) res = max(res, l[i]) for i in range(n - 2, -1, -1): r[i] = max(r[i + 1] + arr[i], arr[i]) res = max(res, r[i]) for i in range(1, n - 1): res = max(res, l[i - 1] + r[i + 1]) return res 动态规划上面的算法虽然时间上有所改善,但是正如标题所说,空间复杂度是 O(n),有没有办法改进呢?答案是使用动态规划。 具体过程: 定义 max0,表示以 arr[i]结尾且一个都不漏的最大子数组和 定义 max1,表示以 arr[i]或者 arr[i - 1]结尾,可以漏一个的最大子数组和 遍历数组,更新 max1 和 max0(注意先更新 max1,因为 max1 用到了上一个 max0) 其中max1 = max(max1 + arr[i], max0), 即删除 arr[i - 1]或者删除 arr[i] 其中max0 = max(max0 + arr[i], arr[i]), 一个都不删除 12345678910111213141516171819202122232425262728## @lc app=leetcode.cn id=1186 lang=python3## [1186] 删除一次得到子数组最大和## @lc code=startclass Solution: def maximumSum(self, arr: List[int]) -> int: # DP max0 = arr[0] max1 = arr[0] res = arr[0] n = len(arr) if n == 1: return max0 for i in range(1, n): # 先更新max1,再更新max0,因为max1用到了上一个max0 max1 = max(max1 + arr[i], max0) max0 = max(max0 + arr[i], arr[i]) res = max(res, max0, max1) return res# @lc code=end 关键点解析 空间换时间 头尾双数组 动态规划 相关题目 42.trapping-rain-water","categories":[],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"力扣加加闪亮登场~","slug":"leetcode-pp","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-07T12:34:55.832Z","comments":true,"path":"2020/06/13/leetcode-pp/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-pp/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。 ​ 12脑洞前端:你已经长大了,是时候自己写题解了。力扣加加:。。。 独立的原因是让公众号的定位更加清晰,喜欢我的题解的朋友可以关注力扣加加,喜欢我的前端架构剖析,徒手造框架的朋友可以关注脑洞前端。当然如果你同时关注两个,那我会感动到哭。 虽然是新的公众号,但是我的初心不变,依然是力求用清晰直白的方式还原解题过程,努力做西湖区最好的算法题解。最后感谢大家一路以来的支持,我一定不负众望,越做越好。 规划预计力扣加加会推出五个板块。 91 算法 通过在 91 天的集中训练,帮助大家摆脱困境,征服算法。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。我会在结束之后将讲义和题解放到公众号号。 (仓库部分内容) 算法题解就是对力扣中的题目进行讲解,尤其是经典的题目。并且尽量从多个角度思路,帮助大家纵向打开解题思路。目前差不多有几百的题解了。 专题讲解对于共性比较强的,我会抽离成专题形式,帮助大家横向打开解题思路。 (之后会陆续加入更多专题) 视频题解对于一些题目采用视频的方式可能更加直观方便,之后还会考虑直播。 刷题工具初步规划了两个小工具,之后出测试版本会第一时间告诉大家,如果你对开发小工具感兴趣,也可以点击公众号的联系我来和我取得联系。 (力扣加加刷题小助手) 悬赏令大家在日常生活或是在刷题过程中有时一定会产生一些疑问,百度谷歌翻来覆去的找也找不到心中所期待的答案,因此,大家可以通过力扣加加来将这些疑问提出来,不定期从大家的问题中挑选出比较经典的几个问题来供大家一起讨论,大致的问题类型如下: 日常生活中有个问题想用数据结构+算法解决,但苦于思路不清晰 问题本身比较纠结,网上众说纷纭,找不到明确的答案 脑洞问题等等 (比如这种问题) 被抽中的问题对应的朋友及问题回答出色的朋友可以获得准备的小礼物哦! 最后,每周我们会根据公众号后台的阅读量及分享次数最多的小伙伴给予现金奖励奖励哦~。分享最多 1 人 8.88 红包,阅读最多的 1 人 6.66 红包。\u001c 不是第一也没有关系,我们会从分享排名 2-10撒 名的小伙伴中随机抽取2个 8.88 元红包,阅读排名 2-10 名的小伙伴中随机抽取2个 6.88 元红包, 每周日下午,我们会对本周的数据进行统计,并随机抽取幸运的小伙伴,并对中奖结果在力扣加加公众号进行公布,中奖的小伙伴请主动联系我哦。领奖时间截止到每周日的 24:00。 关注我公众号点关注,不迷路。如果再给 ➕ 个星标就更棒啦! 关注加加,星标加加~ 官网 力扣加加官网","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"【LeetCode日记】 1162. 地图分析","slug":"leetcode-island","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:50.002Z","comments":true,"path":"2020/06/13/leetcode-island/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-island/","excerpt":"LeetCode 上有很多小岛题,虽然官方没有这个标签, 但是在我这里都差不多。不管是思路还是套路都比较类似,大家可以结合起来练习。 200.number-of-islands 695.max-area-of-island ​","text":"LeetCode 上有很多小岛题,虽然官方没有这个标签, 但是在我这里都差不多。不管是思路还是套路都比较类似,大家可以结合起来练习。 200.number-of-islands 695.max-area-of-island ​ 原题地址:https://leetcode-cn.com/problems/as-far-from-land-as-possible/ 思路这里我们继续使用上面两道题的套路,即不用 visited,而是原地修改。由于这道题求解的是最远的距离,而距离我们可以使用 BFS 来做。算法: 对于每一个海洋,我们都向四周扩展,寻找最近的陆地,每次扩展 steps 加 1。 如果找到了陆地,我们返回 steps。 我们的目标就是所有 steps 中的最大值。 实际上面算法有很多重复计算,如图中间绿色的区域,向外扩展的时候,如果其周边四个海洋的距离已经计算出来了,那么没必要扩展到陆地。实际上只需要扩展到周边的四个海洋格子就好了,其距离陆地的最近距离就是 1 + 周边四个格子中到达陆地的最小距离。 我们考虑优化。 将所有陆地加入队列,而不是海洋。 陆地不断扩展到海洋,每扩展一次就 steps 加 1,直到无法扩展位置。 最终返回 steps 即可。 图解: 代码12345678910111213141516class Solution: def maxDistance(self, grid: List[List[int]]) -> int: n = len(grid) steps = -1 queue = [(i, j) for i in range(n) for j in range(n) if grid[i][j] == 1] if len(queue) == 0 or len(queue) == n ** 2: return steps while len(queue) > 0: for _ in range(len(queue)): x, y = queue.pop(0) for xi, yj in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]: if xi >= 0 and xi < n and yj >= 0 and yj < n and grid[xi][yj] == 0: queue.append((xi, yj)) grid[xi][yj] = -1 steps += 1 return steps 由于没有 early return,steps 其实会多算一次。 我们可以返回值减去 1,也可以 steps 初始化为-1。这里我选择是 steps 初始化为-1 _复杂度分析_ 时间复杂度:$O(N ^ 2)$ 空间复杂度:$O(N ^ 2)$ 优化由于数组删除第一个元素(上面代码的 queue.pop(0))是$O(N)$的时间复杂度,我们可以使用 deque 优化,代码如下: 12345678910111213141516171819def maxDistance(self, grid: List[List[int]]) -> int: from collections import deque N = len(grid) steps = -1 q = deque([(i, j) for i in range(N) for j in range(N) if grid[i][j] == 1]) if len(q) == 0 or len(q) == N ** 2: return steps move = [(-1, 0), (1, 0), (0, -1), (0, 1)] while len(q) > 0: for _ in range(len(q)): x, y = q.popleft() for dx, dy in move: nx, ny = x + dx, y + dy if 0 <= nx < N and 0 <= ny < N and grid[nx][ny] == 0: q.append((nx, ny)) grid[nx][ny] = -1 steps += 1 return steps 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经接近 30K star 啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"BFS","slug":"算法/BFS","permalink":"https://lucifer.ren/blog/categories/算法/BFS/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"阿里面试题:如何寻找「两个数组」的中位数?","slug":"leetcode-median","date":"2020-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.956Z","comments":true,"path":"2020/06/13/leetcode-median/","link":"","permalink":"https://lucifer.ren/blog/2020/06/13/leetcode-median/","excerpt":"一个数组的中位数很容易求,那两个数组呢? ​","text":"一个数组的中位数很容易求,那两个数组呢? ​ 题目地址(4. 寻找两个正序数组的中位数)https://leetcode-cn.com/problems/median-of-two-sorted-arrays/ 题目描述1234567891011121314151617181920给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。你可以假设 nums1 和 nums2 不会同时为空。 示例 1:nums1 = [1, 3]nums2 = [2]则中位数是 2.0示例 2:nums1 = [1, 2]nums2 = [3, 4]则中位数是 (2 + 3)/2 = 2.5 前置知识 中位数 分治法 二分查找 公司 阿里 百度 腾讯 暴力法思路首先了解一下 Median 的概念,一个数组中 median 就是把数组分成左右等分的中位数。 如下图: 知道了概念,我们先来看下如何使用暴力法来解决。 试了一下,暴力解法也是可以被 Leetcode Accept 的。 暴力解主要是要 merge 两个排序的数组(A,B)成一个排序的数组。 用两个pointer(i,j),i 从数组A起始位置开始,即i=0开始,j 从数组B起始位置, 即j=0开始.一一比较 A[i] 和 B[j], 如果A[i] <= B[j], 则把A[i] 放入新的数组中,i 往后移一位,即 i+1. 如果A[i] > B[j], 则把B[j] 放入新的数组中,j 往后移一位,即 j+1. 重复步骤#1 和 #2,直到i移到A最后,或者j移到B最后。 如果j移动到B数组最后,那么直接把剩下的所有A依次放入新的数组中. 如果i移动到A数组最后,那么直接把剩下的所有B依次放入新的数组中. 整个过程类似归并排序的合并过程 Merge 的过程如下图。 时间复杂度和空间复杂度都是O(m+n), 不符合题中给出O(log(m+n))时间复杂度的要求。 代码代码支持: Java,JS: Java Code: 1234567891011121314151617181920212223242526272829303132333435class MedianTwoSortedArrayBruteForce { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int[] newArr = mergeTwoSortedArray(nums1, nums2); int n = newArr.length; if (n % 2 == 0) { // even return (double) (newArr[n / 2] + newArr[n / 2 - 1]) / 2; } else { // odd return (double) newArr[n / 2]; } } private int[] mergeTwoSortedArray(int[] nums1, int[] nums2) { int m = nums1.length; int n = nums2.length; int[] res = new int[m + n]; int i = 0; int j = 0; int idx = 0; while (i < m && j < n) { if (nums1[i] <= nums2[j]) { res[idx++] = nums1[i++]; } else { res[idx++] = nums2[j++]; } } while (i < m) { res[idx++] = nums1[i++]; } while (j < n) { res[idx++] = nums2[j++]; } return res; }} JS Code: 1234567891011121314151617181920212223242526272829/** * @param {number[]} nums1 * @param {number[]} nums2 * @return {number} */var findMedianSortedArrays = function (nums1, nums2) { // 归并排序 const merged = []; let i = 0; let j = 0; while (i < nums1.length && j < nums2.length) { if (nums1[i] < nums2[j]) { merged.push(nums1[i++]); } else { merged.push(nums2[j++]); } } while (i < nums1.length) { merged.push(nums1[i++]); } while (j < nums2.length) { merged.push(nums2[j++]); } const { length } = merged; return length % 2 === 1 ? merged[Math.floor(length / 2)] : (merged[length / 2] + merged[length / 2 - 1]) / 2;}; 复杂度分析 时间复杂度:$O(max(m, n))$ 空间复杂度:$O(m + n)$ 二分查找思路如果我们把上一种方法的最终结果拿出来单独看的话,不难发现最终结果就是 nums1 和 nums 两个数组交错形成的新数组,也就是说 nums1 和 nums2 的相对位置并不会发生变化,这是本题的关键信息之一。 为了方便描述,不妨假设最终分割后,数组 nums1 左侧部分是 A,数组 nums2 左侧部分是 B。由于题中给出的数组都是排好序的,在排好序的数组中查找很容易想到可以用二分查找(Binary Search)·, 这里对数组长度小的做二分以减少时间复杂度。对较小的数组做二分可行的原因在于如果一个数组的索引 i 确定了,那么另一个数组的索引位置 j 也是确定的,因为 (i+1) + (j+1) 等于 (m + n + 1) / 2,其中 m 是数组 A 的长度, n 是数组 B 的长度。具体来说,我们可以保证数组 A 和 数组 B 做 partition 之后,len(Aleft)+len(Bleft)=(m+n+1)/2 接下来需要特别注意四个指针:leftp1, rightp1, leftp2, rightp2,分别表示 A 数组分割点,A 数组分割点右侧数,B 数组分割点,B 数组分割点右侧数。不过这里有两个临界点需要特殊处理: 如果分割点左侧没有数,即分割点索引是 0,那么其左侧应该设置为无限小。 如果分割点右侧没有数,即分割点索引是数组长度-1,那么其左侧应该设置为无限大。 如果我们二分之后满足:leftp1 < rightp2 and leftp2 < rightp1,那么说明分割是正确的,直接返回max(leftp1, leftp2)+min(rightp1, rightp2) 即可。否则,说明分割无效,我们需要调整分割点。 如何调整呢?实际上只需要判断 leftp1 > rightp2 的大小关系即可。如果 leftp1 > rightp2,那么说明 leftp1 太大了,我们可以通过缩小上界来降低 leftp1,否则我们需要扩大下界。 核心代码: 1234if leftp1 > rightp2: hi = mid1 - 1else: lo = mid1 + 1 上面的调整上下界的代码是建立在对数组 nums1 进行二分的基础上的,如果我们对数组 nums2 进行二分,那么相应地需要改为: 1234if leftp2 > rightp1: hi = mid2 - 1else: lo = mid2 + 1 下面我们通过一个具体的例子来说明。 比如对数组 A 的做 partition 的位置是区间[0,m] 如图: 下图给出几种不同情况的例子(注意但左边或者右边没有元素的时候,左边用INF_MIN,右边用INF_MAX表示左右的元素: 下图给出具体做的 partition 解题的例子步骤, 这个算法关键在于: 要 partition 两个排好序的数组成左右两等份,partition 需要满足len(Aleft)+len(Bleft)=(m+n+1)/2 - m是数组A的长度, n是数组B的长度, 且 partition 后 A 左边最大(maxLeftA), A 右边最小(minRightA), B 左边最大(maxLeftB), B 右边最小(minRightB) 满足(maxLeftA <= minRightB && maxLeftB <= minRightA) 关键点分析 有序数组容易想到二分查找 对小的数组进行二分可降低时间复杂度 根据 leftp1,rightp2,leftp2 和 rightp1 的大小关系确定结束点和收缩方向 代码代码支持:JS,CPP, Python3, JS Code: 1234567891011121314151617181920212223242526272829303132333435/** * 二分解法 * @param {number[]} nums1 * @param {number[]} nums2 * @return {number} */var findMedianSortedArrays = function (nums1, nums2) { // make sure to do binary search for shorten array if (nums1.length > nums2.length) { [nums1, nums2] = [nums2, nums1]; } const m = nums1.length; const n = nums2.length; let low = 0; let high = m; while (low <= high) { const i = low + Math.floor((high - low) / 2); const j = Math.floor((m + n + 1) / 2) - i; const maxLeftA = i === 0 ? -Infinity : nums1[i - 1]; const minRightA = i === m ? Infinity : nums1[i]; const maxLeftB = j === 0 ? -Infinity : nums2[j - 1]; const minRightB = j === n ? Infinity : nums2[j]; if (maxLeftA <= minRightB && minRightA >= maxLeftB) { return (m + n) % 2 === 1 ? Math.max(maxLeftA, maxLeftB) : (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2; } else if (maxLeftA > minRightB) { high = i - 1; } else { low = low + 1; } }}; Java Code: 1234567891011121314151617181920212223242526272829303132333435363738394041class MedianSortedTwoArrayBinarySearch { public static double findMedianSortedArraysBinarySearch(int[] nums1, int[] nums2) { // do binary search for shorter length array, make sure time complexity log(min(m,n)). if (nums1.length > nums2.length) { return findMedianSortedArraysBinarySearch(nums2, nums1); } int m = nums1.length; int n = nums2.length; int lo = 0; int hi = m; while (lo <= hi) { // partition A position i int i = lo + (hi - lo) / 2; // partition B position j int j = (m + n + 1) / 2 - i; int maxLeftA = i == 0 ? Integer.MIN_VALUE : nums1[i - 1]; int minRightA = i == m ? Integer.MAX_VALUE : nums1[i]; int maxLeftB = j == 0 ? Integer.MIN_VALUE : nums2[j - 1]; int minRightB = j == n ? Integer.MAX_VALUE : nums2[j]; if (maxLeftA <= minRightB && maxLeftB <= minRightA) { // total length is even if ((m + n) % 2 == 0) { return (double) (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2; } else { // total length is odd return (double) Math.max(maxLeftA, maxLeftB); } } else if (maxLeftA > minRightB) { // binary search left half hi = i - 1; } else { // binary search right half lo = i + 1; } } return 0.0; }} CPP Code: 123456789101112131415161718class Solution {public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { if (nums1.size() > nums2.size()) swap(nums1, nums2); int M = nums1.size(), N = nums2.size(), L = 0, R = M, K = (M + N + 1) / 2; while (true) { int i = (L + R) / 2, j = K - i; if (i < M && nums2[j - 1] > nums1[i]) L = i + 1; else if (i > L && nums1[i - 1] > nums2[j]) R = i - 1; else { int maxLeft = max(i ? nums1[i - 1] : INT_MIN, j ? nums2[j - 1] : INT_MIN); if ((M + N) % 2) return maxLeft; int minRight = min(i == M ? INT_MAX : nums1[i], j == N ? INT_MAX : nums2[j]); return (maxLeft + minRight) / 2.0; } } }}; Python3 Code: 123456789101112131415161718192021222324252627282930313233class Solution: def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: N = len(nums1) M = len(nums2) if N > M: return self.findMedianSortedArrays(nums2, nums1) lo = 0 hi = N combined = N + M while lo <= hi: mid1 = lo + hi >> 1 mid2 = ((combined + 1) >> 1) - mid1 leftp1 = -float(\"inf\") if mid1 == 0 else nums1[mid1 - 1] rightp1 = float(\"inf\") if mid1 == N else nums1[mid1] leftp2 = -float(\"inf\") if mid2 == 0 else nums2[mid2 - 1] rightp2 = float(\"inf\") if mid2 == M else nums2[mid2] # Check if the partition is valid for the case of if leftp1 <= rightp2 and leftp2 <= rightp1: if combined % 2 == 0: return (max(leftp1, leftp2)+min(rightp1, rightp2)) / 2.0 return max(leftp1, leftp2) else: if leftp1 > rightp2: hi = mid1 - 1 else: lo = mid1 + 1 return -1 复杂度分析 时间复杂度:$O(log(min(m, n)))$ 空间复杂度:$O(log(min(m, n)))$ 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"二分法","slug":"算法/二分法","permalink":"https://lucifer.ren/blog/categories/算法/二分法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"中位数","slug":"中位数","permalink":"https://lucifer.ren/blog/tags/中位数/"},{"name":"二分法","slug":"二分法","permalink":"https://lucifer.ren/blog/tags/二分法/"}]},{"title":"复个盘~","slug":"91algo-4-basic","date":"2020-06-10T16:00:00.000Z","updated":"2023-01-07T12:34:56.098Z","comments":true,"path":"2020/06/11/91algo-4-basic/","link":"","permalink":"https://lucifer.ren/blog/2020/06/11/91algo-4-basic/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 91 天学算法基础篇已经结束了,一共七个小节的内容,这部分是将来的基础,大家务必掌握好。 这里给各位做一个简单的小结,帮助大家理清思路,建立强大的知识体系。 知识快览基础篇我们对几种数据结构进行了讲解,他们分别是: 数组,栈,队列。分别讲了各种操作的复杂度,使用场景。 链表。分别讲了各种操作的复杂度和常见题型。 树。介绍了遍历树的几种方法,实际树的核心指标,这些都是高级算法的基础。 哈希表。注意介绍了哈希表的几种基本题型。 图。图的内容有点多,核心有单源最短距离和多源最短距离以及后面的联通域问题(并查集应用)。 除此之外,还讲了一些基础算法,他们分别是: 双指针。一种非常有用,非常常见的优化技巧。 枚举,模拟与递推(不是递归哦)。 高级的料理往往采用最朴素的烹饪方式。 这些都是后面学习专题篇和进阶篇的基础。万丈高楼平地起,不打好基础,后面的学习自然无从谈起。 知识梳理基础篇我给大家画了重点,并用了一句简单的话进行了描述,希望能帮助大家更好地记住它们。 知识点是有很多联系的。我希望大家学习知识的时候自己去整理一下知识网络,这样不仅理解起来更加顺畅,而且更不容易忘记。 比如针对基础篇的数组,链表和树,我就可以总结出如下知识图。 可踹看出知识之间其实是有关联的,都不是孤立存在的。掌握好知识的内在联系是学习任何知识的捷径。 通过这样的认真归纳,再辅以我们提供的每日一题相信你的算法技能一定会有所提升。另外我也会在讲义或者题解中给出一些类似的题目,如果你做的并不顺利,那么可以把类似题目都尝试一下。这其实和准备高考有点类似。 最后祝大家坚持下来, 91 天后遇见不一样的自己。 专题篇的第一篇《二分》也接近尾声。很多学员都可以拳打二分题目了,给你们点个赞。 专题篇,我来了! 想参与 91 天学算法的可以用浏览器(不要微信内打开)访问 https://leetcode-solution.cn/91?tab=agenda 查看时间和课程安排以及具体的报名方法哦。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"《丢鸡蛋问题》重制版来袭~","slug":"887.super-egg-drop","date":"2020-06-07T16:00:00.000Z","updated":"2023-01-05T12:24:49.488Z","comments":true,"path":"2020/06/08/887.super-egg-drop/","link":"","permalink":"https://lucifer.ren/blog/2020/06/08/887.super-egg-drop/","excerpt":"这是一道 LeetCode 难度为 Hard 的题目,很多大公司都会考,来看看。 ​","text":"这是一道 LeetCode 难度为 Hard 的题目,很多大公司都会考,来看看。 ​ 原题地址:https://leetcode-cn.com/problems/super-egg-drop/ 题目描述你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。 每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。 你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。 每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。 你的目标是确切地知道 F 的值是多少。 无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少? 示例 1: 输入:K = 1, N = 2输出:2解释:鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。如果它没碎,那么我们肯定知道 F = 2 。因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。示例 2: 输入:K = 2, N = 6输出:3示例 3: 输入:K = 3, N = 14输出:4 提示: 1 <= K <= 1001 <= N <= 10000 前置知识 递归 动态规划 思路本题也是 vivo 2020 年提前批的一个笔试题。时间一个小时,一共三道题,分别是本题,合并 k 个链表,以及种花问题。 这道题我在很早的时候做过,也写了题解。现在看来,思路没有讲清楚。没有讲当时的思考过程还原出来,导致大家看的不太明白。今天给大家带来的是 887.super-egg-drop 题解的重制版。思路更清晰,讲解更透彻,如果觉得有用,那就转发在看支持一下?OK,我们来看下这道题吧。 这道题乍一看很复杂,我们不妨从几个简单的例子入手,尝试打开思路。 假如有 2 个鸡蛋,6 层楼。 我们应该先从哪层楼开始扔呢?想了一会,没有什么好的办法。我们来考虑使用暴力的手段。 (图 1. 这种思路是不对的) 既然我不知道先从哪层楼开始扔是最优的,那我就依次模拟从第 1,第 2。。。第 6 层扔。每一层楼丢鸡蛋,都有两种可能,碎或者不碎。由于是最坏的情况,因此我们需要模拟两种情况,并取两种情况中的扔次数的较大值(较大值就是最坏情况)。 然后我们从六种扔法中选择最少次数的即可。 (图 2. 应该是这样的) 而每一次选择从第几层楼扔之后,剩下的问题似乎是一个规模变小的同样问题。嗯哼?递归? 为了方便描述,我将 f(i, j) 表示有 i 个鸡蛋, j 层楼,在最坏情况下,最少的次数。 伪代码: 123456def superEggDrop(K, N): ans = N # 暴力枚举从第 i 层开始扔 for i in range(1, N + 1): ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1)) return ans 如上代码: self.superEggDrop(K - 1, i - 1) 指的是鸡蛋破碎的情况,我们就只剩下 K - 1 个鸡蛋, 并且 i - 1 个楼层需要 check。 self.superEggDrop(K, N - i) + 1 指的是鸡蛋没有破碎的情况,我们仍然有 K 个鸡蛋, 并且剩下 N - i 个楼层需要 check。 接下来,我们增加两行递归的终止条件,这道题就完成了。 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: if K == 1: return N if N == 0 or N == 1: return N ans = N # 暴力枚举从第 i 层开始扔 for i in range(1, N + 1): ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1)) return ans 可是如何这就结束的话,这道题也不能是 hard,而且这道题是公认难度较大的 hard 之一。 上面的代码会 TLE,我们尝试使用记忆化递归来试一下,看能不能 AC。 1234567891011class Solution: @lru_cache() def superEggDrop(self, K: int, N: int) -> int: if K == 1: return N if N == 0 or N == 1: return N ans = N # 暴力枚举从第 i 层开始扔 for i in range(1, N + 1): ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1)) return ans 性能比刚才稍微好一点,但是还是很容易挂。 那只好 bottom-up(动态规划)啦。 (图 3) 我将上面的过程简写成如下形式: (图 4) 与其递归地进行这个过程,我们可以使用迭代的方式。 相比于上面的递归式,减少了栈开销。然而两者有着很多的相似之处。 如果说递归是用函数调用来模拟所有情况, 那么动态规划就是用表来模拟。我们知道所有的情况,无非就是 N 和 K 的所有组合,我们怎么去枚举 K 和 N 的所有组合? 当然是套两层循环啦! (图 5. 递归 vs 迭代) 如上,你将 dp[i][j] 看成 superEggDrop(i, j),是不是和递归是一摸一样? 来看下迭代的代码: 123456789101112class Solution: def superEggDrop(self, K: int, N: int) -> int: for i in range(K + 1): for j in range(N + 1): if i == 1: dp[i][j] = j if j == 1 or j == 0: dp[i][j] == j dp[i][j] = j for k in range(1, j + 1): dp[i][j] = min(dp[i][j], max(dp[i - 1][k - 1] + 1, dp[i][j - k] + 1)) return dp[K][N] 值得注意的是,在这里内外循环的顺序无关紧要,并且内外循坏的顺序对我们写代码来说复杂程度也是类似的,各位客官可以随意调整内外循环的顺序。比如这样也是可以的: 123456789101112131415class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] for i in range(N + 1): for j in range( K + 1): if j == 1: dp[i][j] = i if i == 1 or i == 0: dp[i][j] == i dp[i][j] = i for k in range(1, i + 1): dp[i][j] = min(dp[i][j], max(dp[k - 1][j - 1] + 1, dp[i - k][j] + 1)) return dp[N][K] dp = [[0] * (N + 1) for _ in range(K + 1)] 总结一下,上面的解题方法思路是: 然而这样还是不能 AC。这正是这道题困难的地方。 一道题目往往有不止一种状态转移方程,而不同的状态转移方程往往性能是不同的。 那么这道题有没有性能更好的其他的状态转移方程呢? 把思路逆转! 这是《逆转裁判》 中经典的台词, 主角在深处绝境的时候,会突然冒出这句话,从而逆转思维,寻求突破口。 我们这样来思考这个问题。 既然题目要求最少的扔的次数,假设有一个函数 f(k, i),他的功能是求出 k 个鸡蛋,扔 i 次所能检测的最高楼层。 我们只需要不断进行发问: ”f 函数啊 f 函数,我扔一次可以么?“, 也就是判断 f(k, 1) >= N 的返回值 ”f 函数啊 f 函数,我扔两次呢?“, 也就是判断 f(k, 2) >= N 的返回值 … ”f 函数啊 f 函数,我扔 m 次呢?“, 也就是判断 f(k, m) >= N 的返回值 我们只需要返回第一个返回值为 true 的 m 即可。 想到这里,我条件发射地想到了二分法。 聪明的小朋友们,你们觉得二分可以么?为什么?欢迎评论区留言讨论。 那么这个神奇的 f 函数怎么实现呢?其实很简单。 摔碎的情况,可以检测的最高楼层是f(m - 1, k - 1) + 1。因为碎了嘛,我们多检测了摔碎的这一层。 没有摔碎的情况,可以检测的最高楼层是f(m - 1, k)。因为没有碎,也就是说我们啥都没检测出来(对能检测的最高楼层无贡献)。 我们来看下代码: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: def f(m, k): if k == 0 or m == 0: return 0 return f(m - 1, k - 1) + 1 + f(m - 1, k) m = 0 while f(m, K) < N: m += 1 return m 上面的代码可以 AC。我们来顺手优化成迭代式。 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m 代码代码支持:JavaSCript,Python Python: 123456789class Solution: def superEggDrop(self, K: int, N: int) -> int: dp = [[0] * (K + 1) for _ in range(N + 1)] m = 0 while dp[m][K] < N: m += 1 for i in range(1, K + 1): dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i] return m JavaSCript: 12345678910111213var superEggDrop = function (K, N) { // 不选择dp[K][M]的原因是dp[M][K]可以简化操作 const dp = Array(N + 1) .fill(0) .map((_) => Array(K + 1).fill(0)); let m = 0; while (dp[m][K] < N) { m++; for (let k = 1; k <= K; ++k) dp[m][k] = dp[m - 1][k - 1] + 1 + dp[m - 1][k]; } return m;}; 复杂度分析 时间复杂度:$O(m * K)$,其中 m 为答案。 空间复杂度:$O(K * N)$ 对为什么用加法的同学有疑问的可以看我写的《对《丢鸡蛋问题》的一点补充》。 总结 对于困难,先举几个简单例子帮助你思考。 递归和迭代的关系,以及如何从容地在两者间穿梭。 如果你还不熟悉动态规划,可以先从递归做起。多画图,当你做多了题之后,就会越来越从容。 对于动态规划问题,往往有不止一种状态转移方程,而不同的状态转移方程往往性能是不同的。 友情提示: 大家不要为了这个题目高空抛物哦。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"【LeetCode日记】 874. 模拟行走机器人","slug":"874.walking-robot-simulation","date":"2020-06-03T16:00:00.000Z","updated":"2023-01-07T12:20:39.930Z","comments":true,"path":"2020/06/04/874.walking-robot-simulation/","link":"","permalink":"https://lucifer.ren/blog/2020/06/04/874.walking-robot-simulation/","excerpt":"这是一道 LeetCode 难度为 easy 的题目,没有高深的算法,有的只是套路,我们来看下。 ​","text":"这是一道 LeetCode 难度为 easy 的题目,没有高深的算法,有的只是套路,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/walking-robot-simulation/submissions/ 题目描述12345678910111213141516171819202122232425262728293031323334机器人在一个无限大小的网格上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令:-2:向左转 90 度-1:向右转 90 度1 <= x <= 9:向前移动 x 个单位长度在网格上有一些格子被视为障碍物。第 i 个障碍物位于网格点 (obstacles[i][0], obstacles[i][1])如果机器人试图走到障碍物上方,那么它将停留在障碍物的前一个网格方块上,但仍然可以继续该路线的其余部分。返回从原点到机器人的最大欧式距离的平方。 示例 1:输入: commands = [4,-1,3], obstacles = []输出: 25解释: 机器人将会到达 (3, 4)示例 2:输入: commands = [4,-1,4,-2,4], obstacles = [[2,4]]输出: 65解释: 机器人在左转走到 (1, 8) 之前将被困在 (1, 4) 处 提示:0 <= commands.length <= 100000 <= obstacles.length <= 10000-30000 <= obstacle[i][0] <= 30000-30000 <= obstacle[i][1] <= 30000答案保证小于 2 ^ 31 思路这道题之所以是简单难度,是因为其没有什么技巧。你只需要看懂题目描述,然后把题目描述转化为代码即可。 唯一需要注意的是查找障碍物的时候如果你采用的是线形查找会很慢,很可能会超时。 我实际测试了一下,确实会超时 一种方式是使用排序,然后二分查找,如果采用基于比较的排序算法,那么这种算法的瓶颈在于排序本身,也就是$O(NlogN)$。 另一种方式是使用集合,将 obstacles 放入集合,然后需要的时候进行查询,查询的时候的时间复杂度为$O(1)$。 这里我们采用第二种方式。 接下来我们来“翻译”一下题目。 由于机器人只能往前走。因此机器人往东西南北哪个方向走取决于它的朝向。 我们使用枚举来表示当前机器人的朝向。 题目只有两种方式改变朝向,一种是左转(-2),另一种是右转(-1)。 题目要求的是机器人在运动过程中距离原点的最大值,而不是最终位置距离原点的距离。 为了代码书写简单,我建立了一个直角坐标系。用机器人的朝向和 x 轴正方向的夹角度数来作为枚举值,并且这个度数是 0 <= deg < 360。我们不难知道,其实这个取值就是0, 90,180,270 四个值。那么当 0 度的时候,我们只需要不断地 x+1,90 度的时候我们不断地 y + 1 等等。 关键点解析 理解题意,这道题容易理解错题意,求解为最终位置距离原点的距离 建立坐标系 使用集合简化线形查找的时间复杂度。 代码代码支持: Python3 Python3 Code: 1234567891011121314151617181920212223242526272829303132333435class Solution: def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int: pos = [0, 0] deg = 90 ans = 0 obstaclesSet = set(map(tuple, obstacles)) for command in commands: if command == -1: deg = (deg + 270) % 360 elif command == -2: deg = (deg + 90) % 360 else: if deg == 0: i = 0 while i < command and not (pos[0] + 1, pos[1]) in obstaclesSet: pos[0] += 1 i += 1 if deg == 90: i = 0 while i < command and not (pos[0], pos[1] + 1) in obstaclesSet: pos[1] += 1 i += 1 if deg == 180: i = 0 while i < command and not (pos[0] - 1, pos[1]) in obstaclesSet: pos[0] -= 1 i += 1 if deg == 270: i = 0 while i < command and not (pos[0], pos[1] - 1) in obstaclesSet: pos[1] -= 1 i += 1 ans = max(ans, pos[0] ** 2 + pos[1] ** 2) return ans","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"刷题效率低?或许你就差这么一个插件","slug":"algo-chrome-extension","date":"2020-06-03T16:00:00.000Z","updated":"2023-01-07T12:20:39.937Z","comments":true,"path":"2020/06/04/algo-chrome-extension/","link":"","permalink":"https://lucifer.ren/blog/2020/06/04/algo-chrome-extension/","excerpt":"这两天我写了一个浏览器插件,这个插件的定位就是帮助大家提高刷题效率。 获取方式关注公众号《力扣加加》回复 刷题插件 即可。","text":"这两天我写了一个浏览器插件,这个插件的定位就是帮助大家提高刷题效率。 获取方式关注公众号《力扣加加》回复 刷题插件 即可。 功能介绍目前主要有四个部分组成: 前置知识。 如果你想刷这道题,你需要掌握的知识是什么?如果没掌握这个前置知识,你是刷不出来的。提醒你去复习什么东西。 关键点。有了前置知识,并不代表你就能想到,并不代表你就能做出来。有一些关键点你是要想到,不然要么就是做不出来,要么就是解法效率很低。 题解。 这里精选一些好的题解,帮助大家少走弯路。目前只放了我的题解,后续可能会陆续增加其他优秀题解。 代码。 大家可以直接复制进行调试。 计划中的功能: 国内哪个公司出过这道题, 这对于想进某一家公司的人很有用 可视化调试 复杂度分析小工具 具体做什么,等出来之后再和大家同步 如何使用 下载插件 用 chrome 浏览器,访问 chrome://extensions/ 点击 load unpackd,选择刚刚下载好的插件 现在去 leetcode 就随便找一个题看看吧。 PS: 对于收录的题,展示效果类似上面的截图。对于未收录的题,展示效果如下图 视频我这里录制了一个视频,关于这个插件的。https://www.bilibili.com/video/BV1UK4y1x7zj/ 如何贡献 关注公众号《力扣加加》,点击更多 - 联系我,添加我为好友,备注插件开发。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"扩展程序","slug":"扩展程序","permalink":"https://lucifer.ren/blog/tags/扩展程序/"},{"name":"Chrome","slug":"Chrome","permalink":"https://lucifer.ren/blog/tags/Chrome/"}]},{"title":"【异议!】第一期 《这个🦅题的复杂度怎么分析?》","slug":"over-fancy01","date":"2020-06-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.790Z","comments":true,"path":"2020/06/03/over-fancy01/","link":"","permalink":"https://lucifer.ren/blog/2020/06/03/over-fancy01/","excerpt":"力扣加加,努力做西湖区最好的算法题解。 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 \b 于是【异议!】系列应运而生。它是个什么东西呢? 我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,力扣加加 来回答你。 我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列第一篇。","text":"力扣加加,努力做西湖区最好的算法题解。 去年的一年时间,我在群里每天都会出题给大家做。但是就在 2020-03 开始,力扣也开展了每日一题活动。我突然觉得这个每日一题的必要性变得小了很多,并且逐渐减少了出题频率。但是我还是不愿意放弃大家一起集中进行交流学习的机会。于是我打算新开辟一个专题,这个专题一方面要和力扣官方的每日一题重合度低,另一方面要让大家有参与的热情。 \b 于是【异议!】系列应运而生。它是个什么东西呢? 我相信大家一定在平时刷算法的过程中,一定遇到过“这解法怎么想到的?”,“这解法不对吧?”的情况,并且可悲的是没有人能够回答你。来这里,力扣加加 来回答你。 我们会对大家提出的问题进行筛选,将有意义的问题开放出来给大家讨论和学习。 本次给大家带来的/是【异议!】系列第一篇。 事情的起源昨天有人在我的力扣题解下留言,说我的时间复杂度解释有问题。思考再三,决定将这个问题抛出来大家一起讨论一下,我会在明天的公众号给大家公布参考答案。对于回答正确且点赞数最高的,我会送出 8.88 的现金红包,参与方式以及要求在文末。 其实这是一道前几天的力扣官方每日一题,我们先来看一下题目描述: 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。 示例: 输入: [2,1,5,6,2,3] 输出: 10 那么问题来了这个题目我给出了四个解法,其中前两个是超时的,后两个是可以 AC 的。 而后两个可以 AC 的有一个是单调栈的解法,这个单调栈的解法有两个精妙的地方。第一是哨兵元素的选取,第二是是用了一个栈而不是两个。 另外一个可以 AC 的解法,也就是今天我们要讨论的解法,这个解法使用了两个数组,相对于单调栈的复杂度,其常系数更大,但是其思路同样巧妙。 为了大家更好的理解这个解法,我这里贴一下它的未被优化版本。思路为: 暴力尝试所有可能的矩形。从中心向两边进行扩展。对于每一个 i,我们计算出其左边第一个高度小于它的索引 p,同样地,计算出右边第一个高度小于它的索引 q。那么以 i 为最低点能够构成的面积就是(q - p - 1) * heights[i]。 这种算法毫无疑问也是正确的。 假设 f(i) 表示求以 i 为最低点的情况下,所能形成的最大矩阵面积。那么原问题转化为max(f(0), f(1), f(2), ..., f(n - 1))。 具体算法如下: 我们使用 l 和 r 数组。l[i] 表示 左边第一个高度小于它的索引,r[i] 表示 右边第一个高度小于它的索引。 我们从前往后求出 l,再从后往前计算出 r。 再次遍历求出所有的可能面积,并取出最大的。 代码: 1234567891011121314151617class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j -= 1 l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j += 1 r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 其实 while 循环内部是没有必要一格一格移动的。 举例来说,对于数组[1,2,3,4,5],我们要建立 r 数组。我们从 4 开始,4 的右侧第一个小于它索引的是 n(表示不存在)。同样 3 的右侧第一个小于它索引的也是 n(表示不存在),以此类推。如果用上面的解法的话,我们每次都需要从当前位置遍历到尾部,时间复杂度为$O(N^2)$。 实际上,比如遍历到 2 的时候,我们拿 2 和前面的 3 比较,发现 3 比 2 大,并且我们之前计算出了比 3 大的右侧第一个小于它索引的是 n,也就是说我们可以直接移动到 n 继续搜索,因为这中间的都比 3 大,自然比 2 大了,没有比较的意义。 这样看来时间复杂度就被优化到了$O(N)$。 代码: 123456789101112131415161718class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j = l[j] l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j = r[j] r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 这位读者看到这里产生了一个疑问,这个疑问就是我开篇所讲的。我们来看下他是怎么说的。 这位读者提到交替的这种情况时间复杂度会退化到$O(N^2)$,那么实际情况真的是这样么? 悬赏大家对上面的优化后的算法复杂度是怎么看的?请留言告诉我! 需要注意的是: 我们所说的复杂度是渐进复杂度,也就是说是忽略常数项的。而这里我要求你带上常系数。 这里要求计算的是整个完整算法的复杂度。 请分别说出该算法在最差情况,最好情况下的复杂度。 参与方式:复制链接,并在浏览器打开,然后在里面评论即可。链接地址:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/84-zhu-zhuang-tu-zhong-zui-da-de-ju-xing-duo-chong/ 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解。","categories":[{"name":"异议!","slug":"异议!","permalink":"https://lucifer.ren/blog/categories/异议!/"}],"tags":[{"name":"异议!","slug":"异议!","permalink":"https://lucifer.ren/blog/tags/异议!/"}]},{"title":"《黑客与画家》摘抄","slug":"hacker-drawer","date":"2020-06-01T16:00:00.000Z","updated":"2023-01-05T12:24:49.820Z","comments":true,"path":"2020/06/02/hacker-drawer/","link":"","permalink":"https://lucifer.ren/blog/2020/06/02/hacker-drawer/","excerpt":"","text":"14. 梦寐以求的编程语言让我们试着描述黑客心中梦寐以求的语言来为以上内容做个小结。 这种语言干净简练,具有最高层次的抽象和互动性,而且很容易装备,可以只用很少的代码就解决常见的问题。不管是什么程序,你真正要写的代码几乎都与你自己的特定设置有关,其他具有普遍性的问题都有现成的函数库可以调用。 这种语言的句法短到令人生疑。你输入的命令中,没有任何一个字是多余的,甚至用到 shift 键的机会也很少。 这种语言的抽象程度很高,甚至你可以快速写出一个程序的原型。然后,等到你开始优化的时候,它还提供一个真正出色的性能分析器,告诉你应该重点关注什么地方。你能让多重循环快得难以置信,并且在需要的地方还能直接嵌入字节码。 这种语言有大量优秀的范例可供学习,并且非常符合直觉,你只需要花几分钟阅读范例就能领会应该如何使用此种语言。你偶尔才需要查阅操作手册, 它很薄,里面关于限定条件和例外情况的警告寥寥无几。 这种语言内核很小,但很强大。各个函数库高度独立,并且和内核一样经过精心设计,它们都能很好地协同工作。语言的每个部分就像精密照相机的各种零件一样完美契合,不需要为了兼容性问题放弃或者保留某些功能。所有的函数库的源码都能很容易得到。这种语言能很轻松地与操作系统和其他语言开发的应用程序对话。 这种语言以层的方式构建。较高的抽象层透明地构建在较低的抽象层上。如果有需要的话,你可以直接使用较低的抽象层。 除了一些绝对必要隐藏的东西。这种语言的所有细节对使用者都是透明的。它提供的抽象能力只是为了方便你开发,而不是强迫你按照它的方式行事。事实上,它鼓励你参与它的设计,给你提供与语言创作者平等的权利。你能够对它的任何部分加以改变, 甚至包括它的语法。它尽可能让你自己定义的部分与它本身定义的部分处于同等地位,这种梦幻般的编程语言不仅开放源码,更开放自身的设计。","categories":[{"name":"书摘","slug":"书摘","permalink":"https://lucifer.ren/blog/categories/书摘/"}],"tags":[{"name":"书摘","slug":"书摘","permalink":"https://lucifer.ren/blog/tags/书摘/"}]},{"title":"【LeetCode日记】 312. 戳气球","slug":"312.burst-balloons","date":"2020-05-31T16:00:00.000Z","updated":"2023-01-05T12:24:49.391Z","comments":true,"path":"2020/06/01/312.burst-balloons/","link":"","permalink":"https://lucifer.ren/blog/2020/06/01/312.burst-balloons/","excerpt":"​","text":"​ 312. 戳气球 作者:dp 加加 这是一道比较难且巧妙的动态规划题目;这道题目并不适合初学者看,比较适合 dp 进阶选手研究。好了,废话不多说,直接上菜。 题目描述12345678910111213141516有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。求所能获得硬币的最大数量。说明:你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100示例:输入: [3,1,5,8]输出: 167解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> [] coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167 思路回溯法分析一下这道题,就是要截破所有的气球,获得硬币的最大数量,然后左右两边的气球相邻了。那就截呗,我的第一反应就是暴力,回溯法;但是肯定会超时,为什么呢?因为题目给的气球数量有点多,最多 500 个;500 的阶乘,会超时爆栈;但是我们依然写一下代码,找下突破口,小伙伴们千万不要看不起暴力,暴力是优化的突破口;如果小伙伴对回溯法不太熟悉,我建议你记住下面的模版,也可以看我之前写的文章,回溯法基本可以使用以下的模版写。回溯法省心省力,0 智商负担,懂的朋友都懂,QAQ。 代码12345678910111213141516171819202122232425var maxCoins = function (nums) { let res = Number.MIN_VALUE; backtrack(nums, 0); return res; // 回溯法,状态树很大 function backtrack(nums, score) { if (nums.length == 0) { res = Math.max(res, score); return; } for (let i = 0, n = nums.length; i < n; i++) { let point = (i - 1 < 0 ? 1 : nums[i - 1]) * nums[i] * (i + 1 >= n ? 1 : nums[i + 1]); let tempNums = [].concat(nums); // 做选择 在 nums 中删除元素 nums[i] nums.splice(i, 1); // 递归回溯 backtrack(nums, score + point); // 撤销选择 nums = [...tempNums]; } }}; 动态规划回溯法的缺点也很明显,复杂度很高,对应本题截气球;小伙伴们可以脑补一下执行过程的状态树,这里我偷个懒就不画了;通过仔细观察这个状态树,我们会发现这个状态树的【选择】上,会有一些重复的选择分支;很明显存在了重复子问题;自然我就想到了能不能用动态规划来解决; 判读能不能用动态规划解决,还有一个问题,就是必须存在最优子结构;什么意思呢?其实就是根据局部最优,推导出答案;假设我们截破第 k 个气球是最优策略的最后一步,和上一步有没有联系呢?根据题目意思,截破第 k 个,前一个和后一个就变成相邻的了,看似是会有联系,其实是没有的。因为截破第 k 个和 k-1 个是没有联系的,脑补一下回溯法的状态树就更加明确了; 既然用动态规划,那就老套路了,把动态规划的三个问题想清楚定义好;然后找出题目的【状态】和【选择】,然后根据【状态】枚举,枚举的过程中根据【选择】计算递推就能得到答案了。 那本题的【选择】是什么呢?就是截哪一个气球。那【状态】呢?就是题目给的气球数量。 定义状态 这里有个细节,就是题目说明有两个虚拟气球,nums[-1] = nums[n] = 1;如果当前截破的气球是最后一个或者第一个,前面/后面没有气球了,不能乘以 0,而是乘以 1。 定义状态的最关键两个点,往子问题(问题规模变小)想,最后一步最优策略是什么;我们假设最后截破的气球是 k,截破 k 获得最大数量的银币就是 nums[i] _ nums[k] _ nums[j] 再加上前面截破的最大数量和后面的最大数量,即:nums[i] _ nums[k] _ nums[j] + 前面最大数量 + 后面最大数量,就是答案。 而如果我们不考虑两个虚拟气球而直接定义状态,截到最后两个气球的时候又该怎么定义状态来避免和前面的产生联系呢?这两个虚拟气球就恰到好处了,太细节了;这也是本题的一个难点之一。 那我们可以这样来定义状态,dp[i][j] = x 表示,戳破气球 i 和气球 j 之间(开区间,不包括 i 和 j)的所有气球,可以获得的最大硬币数为 x。为什么开区间?因为不能和已经计算过的产生联系,我们这样定义之后,利用两个虚拟气球,截到最后两个气球的时候就完美的避开了所有状态的联系,太细节了。 状态转移方程 而对于 dp[i][j],i 和 j 之间会有很多气球,到底该截哪个先呢?我们直接设为 k,枚举选择最优的 k 就可以了。 所以,最终的状态转移方程为:dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[k] + nums[i] + nums[j]) 初始值和边界 由于我们利用了两个虚拟气球,边界就是气球数 n + 2 初始值,当 i == j 时,很明显两个之间没有气球,所有为 0; 如何枚举状态 因为我们最终要求的答案是 dp[0][n + 1],就是截破虚拟气球之间的所有气球获得的最大值; 当 i == j 时,i 和 j 之间是没有气球的,所以枚举的状态很明显是 dp table 的左上部分,也就是 j 大于 i,如下图所示,只给出一部分方便思考。 从上图可以看出,我们需要从下到上,从左到右进行遍历。 代码12345678910111213141516171819var maxCoins = function (nums) { let n = nums.length; // 添加两侧的虚拟气球 let points = [1, ...nums, 1]; let dp = Array.from(Array(n + 2), () => Array(n + 2).fill(0)); // 最后一行开始遍历,从下往上 for (let i = n; i >= 0; i--) { // 从左往右 for (let j = i + 1; j < n + 2; j++) { for (let k = i + 1; k < j; k++) { dp[i][j] = Math.max( dp[i][j], points[j] * points[k] * points[i] + dp[i][k] + dp[k][j] ); } } } return dp[0][n + 1];}; 总结简单的 dp 题目会直接告诉你怎么定义状态,告诉你怎么选择计算,你只需要根据套路判断一下能不能用 dp 解题即可,而判断能不能,往往暴力就是突破口。而困难点的 dp,我觉的都是细节问题了,要注意的细节太多了。感觉力扣加加,路西法大佬,把我领进了动态规划的大门,共勉。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"回溯","slug":"算法/回溯","permalink":"https://lucifer.ren/blog/categories/算法/回溯/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"【LeetCode日记】 101. 对称二叉树","slug":"101.symmetric-tree","date":"2020-05-30T16:00:00.000Z","updated":"2023-01-05T12:24:50.000Z","comments":true,"path":"2020/05/31/101.symmetric-tree/","link":"","permalink":"https://lucifer.ren/blog/2020/05/31/101.symmetric-tree/","excerpt":"​","text":"​ 题目地址(101. 对称二叉树)https://leetcode-cn.com/problems/symmetric-tree/ 题目描述12345678910111213141516171819202122232425给定一个二叉树,检查它是否是镜像对称的。 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \\ 2 2 / \\ / \\3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 1 / \\ 2 2 \\ \\ 3 3 进阶:你可以运用递归和迭代两种方法解决这个问题吗? 思路看到这题的时候,我的第一直觉是 DFS。然后我就想:如果左子树是镜像,并且右子树也是镜像,是不是就说明整体是镜像?。经过几秒的思考, 这显然是不对的,不符合题意。 很明显其中左子树中的节点会和右子树中的节点进行比较,我把比较的元素进行了颜色区分,方便大家看。 这里我的想法是:遍历每一个节点的时候,我都可以通过某种方法知道它对应的对称节点是谁。这样的话我直接比较两者是否一致就行了。 最初我的想法是两次遍历,第一次遍历的同时将遍历结果存储到哈希表中,然后第二次遍历去哈希表取。 这种方法可行,但是需要 N 的空间(N 为节点总数)。我想到如果两者可以同时进行遍历,是不是就省去了哈希表的开销。 如果不明白的话,我举个简单例子: 1给定一个数组,检查它是否是镜像对称的。例如,数组 [1,2,2,3,2,2,1] 是对称的。 如果用哈希表的话大概是: 1234567seen = dict()for i, num in enumerate(nums): seen[i] = numfor i, num in enumerate(nums): if seen[len(nums) - 1 - i] != num: return Falsereturn True 而同时遍历的话大概是这样的: 12345678l = 0r = len(nums) - 1while l < r: if nums[l] != nums[r]: return False l += 1 r -= 1return True 其实更像本题一点的话应该是从中间分别向两边扩展 😂 代码12345678910class Solution: def isSymmetric(self, root: TreeNode) -> bool: def dfs(root1, root2): if root1 == root2: return True if not root1 or not root2: return False if root1.val != root2.val: return False return dfs(root1.left, root2.right) and dfs(root1.right, root2.left) if not root: return True return dfs(root.left, root.right) _复杂度分析_ 时间复杂度:$O(N)$,其中 N 为节点数。 空间复杂度:递归的深度最高为节点数,因此空间复杂度是 $O(N)$,其中 N 为节点数。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"DFS","slug":"算法/DFS","permalink":"https://lucifer.ren/blog/categories/算法/DFS/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"倒计时两天~91算法,儿童节发车!","slug":"91algo-05-30","date":"2020-05-29T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/05/30/91algo-05-30/","link":"","permalink":"https://lucifer.ren/blog/2020/05/30/91algo-05-30/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 91 算法第一期,从2020-06-01 到 2020-08-30。 马上就要发车了哦,大家准备好了么?儿童节,我们来啦。如果还有没参与的小伙伴,想要参与的可以在下方更多部分寻找参与方式,目前支持 QQ 和微信加入。 简介共分为三篇,基础篇,进阶篇和专题篇。 让你: 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 第一阶段基础篇(30 天)。预计五个子栏目,每个子栏目 6 天。到时候发讲义给大家,题目的话天一道。 讲义的内容大概是我在下方讲义部分放出的链接那样哦。 规则大家的问题,打卡题目,讲义都在这里更新哦,冲鸭 🦆 。91 天见证更好的自己!不过要注意一周不打卡会被强制清退。 需要提前准备些什么? 数据结构与算法的基础知识。 推荐看一下大学里面的教材讲义,或者看一些入门的图书,视频等,比如《图解算法》,邓俊辉的《数据结构与算法》免费视频课程。总之, 至少你要知道有哪些常见的数据结构与算法以及他们各自的特点。 有 Github 账号,且会使用 Github 常用操作。 比如提 issue,留言等。 有 LeetCode 账号,且会用其提交代码。 语言不限,大家可以用自己喜欢的任何语言。同时我也希望你不要纠结于语言本身。 具体形式是什么样的? 总共三个大的阶段 每个大阶段划分为几个小阶段 每个小阶段前会将这个小阶段的资料发到群里 每个小阶段的时间内,每天都会出关于这个阶段的题目,第二天进行解答 比如: 第一个大阶段是基础 基础中第一个小阶段是数组,栈和队列。 数组,栈和队列正式开始前,会将资料发到群里,大家可以提前预习。 之后的每天都会围绕数组,栈和队列出一道题,第二天进行解答。大家可以在出题当天上 Github 上打卡。 大家遇到问题可以在群里回答,对于比较好的问题,会记录到 github issue 中,让更多的人看到。Github 仓库地址届时会在群里公布。 奖励对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书等连续打卡七天可以获得补签卡一张哦 讲义 【91 算法-基础篇】05.双指针 课程列表 回炉重铸, 91 天见证不一样的自己 更多 如何加入微信群?","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"【91算法-基础篇】05.双指针","slug":"91algo-basic-05.two-pointer","date":"2020-05-25T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/05/26/91algo-basic-05.two-pointer/","link":"","permalink":"https://lucifer.ren/blog/2020/05/26/91algo-basic-05.two-pointer/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 什么是双指针顾名思议,双指针就是两个指针,但是不同于 C,C++中的指针, 其是一种算法思想。 如果说,我们迭代一个数组,并输出数组每一项,我们需要一个指针来记录当前遍历的项,这个过程我们叫单指针(index)的话。 123for(int i = 0;i < nums.size(); i++) { 输出(nums[i]);} (图 1) 那么双指针实际上就是有两个这样的指针,最为经典的就是二分法中的左右双指针啦。 12345678910int l = 0;int r = nums.size() - 1;while (l < r) { if(一定条件) return 合适的值,一般是 l 和 r 的中点 if(一定条件) l++ if(一定条件) r--}// 因为 l == r,因此返回 l 和 r 都是一样的return l (图 2) 读到这里,你发现双指针是一个很宽泛的概念,就好像数组,链表一样,其类型会有很多很多, 比如二分法经常用到左右端点双指针。滑动窗口会用到快慢指针和固定间距指针。 因此双指针其实是一种综合性很强的类型,类似于数组,栈等。 但是我们这里所讲述的双指针,往往指的是某几种类型的双指针,而不是“只要有两个指针就是双指针了”。 有了这样一个算法框架,或者算法思维,有很大的好处。它能帮助你理清思路,当你碰到新的问题,在脑海里进行搜索的时候,双指针这个词就会在你脑海里闪过,闪过的同时你可以根据双指针的所有套路和这道题进行穷举匹配,这个思考解题过程本来就像是算法,我会在进阶篇《搜索算法》中详细阐述。 那么究竟我们算法中提到的双指针指的是什么呢?我们一起来看下算法中双指针的常见题型吧。 常见题型有哪些?这里我将其分为三种类类型,分别是: 快慢指针(两个指针步长不同) 左右端点指针(两个指针分别指向头尾,并往中间移动,步长不确定) 固定间距指针(两个指针间距相同,步长相同) 上面是我自己的分类,没有参考别人。可以发现我的分类标准已经覆盖了几乎所有常见的情况。 大家在平时做题的时候一定要养成这样的习惯,将题目类型进行总结,当然这个总结可以是别人总结好的,也可以是自己独立总结的。不管是哪一种,都要进行一定的消化吸收,把它们变成真正属于自己的知识。 不管是哪一种双指针,只考虑双指针部分的话 ,由于最多还是会遍历整个数组一次,因此时间复杂度取决于步长,如果步长是 1,2 这种常数的话,那么时间复杂度就是 O(N),如果步长是和数据规模有关(比如二分法),其时间复杂度就是 O(logN)。并且由于不管规模多大,我们都只需要最多两个指针,因此空间复杂度是 O(1)。下面我们就来看看双指针的常见套路有哪些。 常见套路快慢指针 判断链表是否有环 这里给大家推荐两个非常经典的题目,一个是力扣 287 题,一个是 142 题。其中 142 题我在我的 LeetCode 题解仓库中的每日一题板块出过,并且给了很详细的证明和解答。而 287 题相对不直观,比较难以想到,这道题曾被官方选定为每日一题,也是相当经典的。 287. 寻找重复数 【每日一题】- 2020-01-14 - 142. 环形链表 II · Issue #274 · azl397985856/leetcode 读写指针。典型的是删除重复元素 这里推荐我仓库中的一道题, 我这里写了一个题解,横向对比了几个相似题目,并剖析了这种题目的本质是什么,让你看透题目本质,推荐阅读。 80.删除排序数组中的重复项 II 左右端点指针 二分查找。 二分查找会在专题篇展开,这里不多说,大家先知道就行了。 暴力枚举中“从大到小枚举”(剪枝) 一个典型的题目是我之前参加官方每日一题的时候给的一个解法,大家可以看下。这种解法是可以 AC 的。同样地,这道题我也给出了三种方法,帮助大家从多个纬度看清这个题目。强烈推荐大家做到一题多解。这对于你做题很多帮助。除了一题多解,还有一个大招是多题同解,这部分我们放在专题篇介绍。 find-the-longest-substring-containing-vowels-in-even 有序数组。 区别于上面的二分查找,这种算法指针移动是连续的,而不是跳跃性的,典型的是 LeetCode 的两数和,以及N数和系列问题。 固定间距指针 一次遍历(One Pass)求链表的中点 一次遍历(One Pass)求链表的倒数第 k 个元素 固定窗口大小的滑动窗口 模板(伪代码)我们来看下上面三种题目的算法框架是什么样的。这个时候我们没必要纠结具体的语言,这里我直接使用了伪代码,就是防止你掉进细节。 当你掌握了这种算法的细节,就应该找几个题目试试。一方面是检测自己是否真的掌握了,另一方面是“细节”,”细节“是人类,尤其是软件工程师最大的敌人,毕竟我们都是差不多先生。 快慢指针 1234567l = 0r = 0while 没有遍历完 if 一定条件 l += 1 r += 1return 合适的值 左右端点指针 12345678910l = 0r = n - 1while l < r if 找到了 return 找到的值 if 一定条件1 l += 1 else if 一定条件2 r -= 1return 没找到 固定间距指针 1234567l = 0r = kwhile 没有遍历完 自定义逻辑 l += 1 r += 1return 合适的值 题目推荐如果你差不多理解了上面的东西,那么可以拿下面的题练练手。Let’s Go! 左右端点指针 16.3Sum Closest (Medium) 713.Subarray Product Less Than K (Medium) 977.Squares of a Sorted Array (Easy) Dutch National Flag Problem 下面是二分类型 33.Search in Rotated Sorted Array (Medium) 875.Koko Eating Bananas(Medium) 881.Boats to Save People(Medium) 快慢指针 26.Remove Duplicates from Sorted Array(Easy) 141.Linked List Cycle (Easy) 142.Linked List Cycle II(Medium) 287.Find the Duplicate Number(Medium) 202.Happy Number (Easy) 固定间距指针 1456.Maximum Number of Vowels in a Substring of Given Length(Medium) 固定窗口大小的滑动窗口见专题篇的滑动窗口专题(暂未发布) 其他有时候也不能太思维定式,比如 https://leetcode-cn.com/problems/consecutive-characters/ 这道题根本就没必要双指针什么的。 再比如:https://lucifer.ren/blog/2020/05/31/101.symmetric-tree/","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"【LeetCode日记】 343. 整数拆分","slug":"343.integer-break","date":"2020-05-15T16:00:00.000Z","updated":"2023-01-07T12:20:39.929Z","comments":true,"path":"2020/05/16/343.integer-break/","link":"","permalink":"https://lucifer.ren/blog/2020/05/16/343.integer-break/","excerpt":"希望通过这篇题解让大家知道“题解区的水有多深”,让大家知道“什么才是好的题解”。 ​","text":"希望通过这篇题解让大家知道“题解区的水有多深”,让大家知道“什么才是好的题解”。 ​ 原题地址: https://leetcode-cn.com/problems/integer-break/ 题目描述给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 示例 1: 输入: 2输出: 1解释: 2 = 1 + 1, 1 × 1 = 1。示例 2: 输入: 10输出: 36解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。说明: 你可以假设 n 不小于 2 且不大于 58。 思路我看了很多人的题解直接就是两句话,然后跟上代码: 1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] 这种题解说实话,只针对那些”自己会, 然后去题解区看看有没有新的更好的解法的人“。但是大多数看题解的人是那种自己没思路,不会做的人。那么这种题解就没什么用了。 我认为好的题解应该是新手友好的,并且能够将解题人思路完整展现的题解。比如看到这个题目,我首先想到了什么(对错没有关系),然后头脑中经过怎么样的筛选将算法筛选到具体某一个或某几个。我的最终算法是如何想到的,有没有一些先行知识。 当然我也承认自己有很多题解也是直接给的答案,这对很多人来说用处不大,甚至有可能有反作用,给他们一种”我已经会了“的假象。实际上他们根本不懂解题人本身原本的想法, 也许是写题解的人觉得”这很自然“,也可能”只是为了秀技“。 Ok,下面来讲下我是如何解这道题的。 抽象首先看到这道题,自然而然地先对问题进行抽象,这种抽象能力是必须的。LeetCode 实际上有很多这种穿着华丽外表的题,当你把这个衣服扒开的时候,会发现都是差不多的,甚至两个是一样的,这样的例子实际上有很多。 就本题来说,就有一个剑指 Offer 的原题《剪绳子》和其本质一样,只是换了描述方式。类似的有力扣 137 和 645 等等,大家可以自己去归纳总结。 137 和 645 我贴个之前写的题解 https://leetcode-cn.com/problems/single-number/solution/zhi-chu-xian-yi-ci-de-shu-xi-lie-wei-yun-suan-by-3/ 培养自己抽象问题的能力,不管是在算法上还是工程上。 务必记住这句话! 数学是一门非常抽象的学科,同时也很方便我们抽象问题。为了显得我的题解比较高级,引入一些你们看不懂的数学符号也是很有必要的(开玩笑,没有什么高级数学符号啦)。 实际上这道题可以用纯数学角度来解,但是我相信大多数人并不想看。即使你看了,大多人的感受也是“好 nb,然而并没有什么用”。 这道题抽象一下就是: 令:(图 1)求:(图 2) 第一直觉经过上面的抽象,我的第一直觉这可能是一个数学题,我回想了下数学知识,然后用数学法 AC 了。 数学就是这么简单平凡且枯燥。 然而如果没有数学的加持的情况下,我继续思考怎么做。我想是否可以枚举所有的情况(如图 1),然后对其求最大值(如图 2)。 问题转化为如何枚举所有的情况。经过了几秒钟的思考,我发现这是一个很明显的递归问题。 具体思考过程如下: 我们将原问题抽象为 f(n) 那么 f(n) 等价于 max(1 * fn(n - 1), 2 * f(n - 2), …, (n - 1) * f(1))。 用数学公式表示就是: (图 3) 截止目前,是一点点数学 + 一点点递归,我们继续往下看。现在问题是不是就很简单啦?直接翻译图三为代码即可,我们来看下这个时候的代码: 1234567class Solution: def integerBreak(self, n: int) -> int: if n == 2: return 1 res = 0 for i in range(1, n): res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) return res 毫无疑问,超时了。原因很简单,就是算法中包含了太多的重复计算。如果经常看我的题解的话,这句话应该不陌生。我随便截一个我之前讲过这个知识点的图。 (图 4) 原文链接:https://github.com/azl397985856/leetcode/blob/master/thinkings/dynamic-programming.md 大家可以尝试自己画图理解一下。 看到这里,有没有种殊途同归的感觉呢? 考虑优化如上,我们可以考虑使用记忆化递归的方式来解决。只是用一个 hashtable 存储计算过的值即可。 12345678class Solution: @lru_cache() def integerBreak(self, n: int) -> int: if n == 2: return 1 res = 0 for i in range(1, n): res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) return res 为了简单起见(偷懒起见),我直接用了 lru_cache 注解, 上面的代码是可以 AC 的。 动态规划看到这里的同学应该发现了,这个套路是不是很熟悉?下一步就是将其改造成动态规划了。 如图 4,我们的思考方式是从顶向下,这符合人们思考问题的方式。将其改造成如下图的自底向上方式就是动态规划。 (图 5) 现在再来看下文章开头的代码: 1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] dp table 存储的是图 3 中 f(n)的值。一个自然的想法是令 dp[i] 等价于 f(i)。而由于上面分析了原问题等价于 f(n),那么很自然的原问题也等价于 dp[n]。 而 dp[i]等价于 f(i),那么上面针对 f(i) 写的递归公式对 dp[i] 也是适用的,我们拿来试试。 12// 关键语句res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) 翻译过来就是: 1dp[i] = max(dp[i], max(i * dp(n - i),i * (n - i))) 而这里的 n 是什么呢?我们说了dp是自底向下的思考方式,那么在达到 n 之前是看不到整体的n 的。因此这里的 n 实际上是 1,2,3,4… n。 自然地,我们用一层循环来生成上面一系列的 n 值。接着我们还要生成一系列的 i 值,注意到 n - i 是要大于 0 的,因此 i 只需要循环到 n - 1 即可。 思考到这里,我相信上面的代码真的是不难得出了。 关键点 数学抽象 递归分析 记忆化递归 动态规划 代码1234567class Solution: def integerBreak(self, n: int) -> int: dp = [1] * (n + 1) for i in range(3, n + 1): for j in range(1, i): dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) return dp[n] 总结培养自己的解题思维很重要, 不要直接看别人的答案。而是要将别人的东西变成自己的, 而要做到这一点,你就要知道“他们是怎么想到的”,“想到这点是不是有什么前置知识”,“类似题目有哪些”。 最优解通常不是一下子就想到了,这需要你在不那么优的解上摔了很多次跟头之后才能记住的。因此在你没有掌握之前,不要直接去看最优解。 在你掌握了之后,我不仅鼓励你去写最优解,还鼓励去一题多解,从多个解决思考问题。 到了那个时候, 萌新也会惊讶地呼喊“哇塞, 这题还可以这么解啊?”。 你也会低调地发出“害,解题就是这么简单平凡且枯燥。”的声音。 扩展正如我开头所说,这种套路实在是太常见了。希望大家能够识别这种问题的本质,彻底掌握这种套路。另外我对这个套路也在我的新书《LeetCode 题解》中做了介绍,本书目前刚完成草稿的编写,如果你想要第一时间获取到我们的题解新书,那么请发送邮件到 azl397985856@gmail.com,标题著明“书籍《LeetCode 题解》预定”字样。。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/categories/Medium/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Medium","slug":"Medium","permalink":"https://lucifer.ren/blog/tags/Medium/"}]},{"title":"为什么eslint没有 no-magic-string?","slug":"why-no-magic-string","date":"2020-05-04T16:00:00.000Z","updated":"2023-01-05T12:24:49.978Z","comments":true,"path":"2020/05/05/why-no-magic-string/","link":"","permalink":"https://lucifer.ren/blog/2020/05/05/why-no-magic-string/","excerpt":"最近参加了几次公司组内的 Code Review, 发现了一些问题。其中一些问题可以通过工具(比如 eslint)解决。 我们就想着通过工具自动化的方式进行解决。 而这些工具中有一些是现成的,比如 魔法数。 大家对魔法数的看法也是莫衷一是。本文通过讲解什么是魔法数,eslint 是怎么检查魔法数的,以及思考为什么eslint 偏爱数字,而不是偏爱字符串来 来深入剖析一下魔法数。","text":"最近参加了几次公司组内的 Code Review, 发现了一些问题。其中一些问题可以通过工具(比如 eslint)解决。 我们就想着通过工具自动化的方式进行解决。 而这些工具中有一些是现成的,比如 魔法数。 大家对魔法数的看法也是莫衷一是。本文通过讲解什么是魔法数,eslint 是怎么检查魔法数的,以及思考为什么eslint 偏爱数字,而不是偏爱字符串来 来深入剖析一下魔法数。 计算机科学中的魔法数什么是魔法数? 这里我摘取了维基百科的解释: 程序设计中所谓的魔术数字(magic number)可能有以下含意: • 缺乏解释或命名的独特数值 • 用于识别一个文件格式或协议的一段常量或字符串,例如UNIX的特征签名 • 防止误作他用的一段数值,例如UUID 实际上魔法数真的是一个很古老的话题了。从我刚从业开始,就不停听到这样的词语。大家都对此深恶痛绝,不想看到别人的魔法数,但是时不时自己也会写一些魔法数。 如果你经常做算法题,肯定对此深有感受。 很多人(或许包括我自己)喜欢通过魔法数来炫技。当然我的心理倒不仅仅是炫技这么简单,也掺杂了诸如“这个很显然易见”,“这个我也不知道怎么起名字”的想法。 eslint 中的魔法数eslint 有一个 rule 是 no-magic-number. 为什么没有类似的比如 no-magic-string? 我们首先来看下什么是”no-magic-number”。 根据 eslint 官方描述来看,其是通过确保数字被显式赋予一个常量,从而增加代码可读性和可维护性。 如下代码被认为是不好的: 123/*eslint no-magic-numbers: “error”*/var dutyFreePrice = 100, finalPrice = dutyFreePrice + dutyFreePrice * 0.25; 而这段代码被认为是好的: 12345/*eslint no-magic-numbers: “error”*/var TAX = 0.25;var dutyFreePrice = 100, finalPrice = dutyFreePrice + dutyFreePrice * TAX; 这两段代码有什么不同?为什么下面的代码被认为是好的? 一窥 eslint 源码通过阅读源码,我发现代码有这样一句: 1234567891011// utils/ast-utils.jsfunction isNumericLiteral(node) { return ( node.type === \"Literal\" && (typeof node.value === \"number\" || Boolean(node.bigint)) );}// no-magic-numbers.jsif (!isNumericLiteral(node)) { return;} 也就是说如果字面量不是数字会被忽略。这和我们的想法这条规则只会检查魔法数字,而不会检查诸如魔法字符串等。 让我们时光倒流,将代码回退到 eslint 官方首次关于”no-magic-rule”的提交。 代码大概意思是: 如果是变量声明语句,就去检查是否强制使用 const。 如果是则观察语句是否为 const 声明。 对于其他情况,直接检查父节点的类型。2.1. 如果检查对象内部的魔法数字,则直接报错。2.2. 如果不需要检查对象类型,则进行规则过滤,即如果是[“ObjectExpression”,“Property”,“AssignmentExpression”] 中的一种的话也是没问题的。其他情况报错。 那么上面的三种类型是什么呢? 从名字不难发现,其中 AssignmentExpression 和 ObjectExpression 属于表达式。 而 Property 则是对象属性。 ObjectExpression 的例子: 1a = {} 思考题:为什么 ObjectExpression 也是不被允许的?AssignmentExpression 的例子: 1a = b = 2; Property 的例子: 1a = { number: 1 } 也就是说,如果设置了检查对象,那么上面三种情况都会报错。否则不进行报错处理。 AST-Explorer大家使用AST explorer 来可视化 AST。 由于 eslint 使用的 ast 转化工具是 espree, 推荐大家使用 espree。 如果大家写 babel 插件的话,相应的引擎最好修改一下。 值得注意的是,上面我们用的 parent 是不会在 ast-explorer 进行显示的。原因在于其不是 ast 结构的一部分,而如果要做到,也非常容易。 前提是你懂得递归,尤其是树的递归。 如果你还不太懂树的递归, 可以关注我的 B 站LeetCode 加加的个人空间 - 哔哩哔哩 ( ゜- ゜)つロ 乾杯~ Bilibili 通过观察 eslint/espree 的源码也可以发现,其过程是多么的自然和简单。 Magic String实际上一个第三方的 tslint 仓库的一个issue 明确指出了想要增加”no-magic-string” 的想法,只不过被仓库成员给否掉了,原因是其不够通用,除非能证明大家都需要这样一个规则。 那么问题是“为什么 no-magic-string 没有 no-magic-number 通用呢”?【每日一题】- 2020-05-05 - no-magic-string · Issue #122 · azl397985856/fe-interview · GitHub 有一个回答,小漾给出了很好的回答。但是还没有解决疑问“为什么没有 no-magic-string”这样的规则呢? 我的观点是魔法字符串也是不好的,只不过没有不好的那么明显。 我们来看个例子: 12345name = \"lucifer\";a = \"hello, \" + name;if (type === \"add\") {} else if (type == \"edit\") {} 如上代码,就可以被认定为 magic string。但是其在现实代码中是非常普遍的,并且不对我们造成很大的困扰。如果对其改写会是这样的: 12345678name = \"lucifer\";const PRFIX = \"hello, \";const TYPE_ADD = \"add\";const TYPE_EDIT = \"edit\";a = prefix + name;if (type === TYPE_ADD) {} else if (type == TYPE_EDIT) {} 再来看看数字: 1234567if (type === 1) { //}if (total > 5) { //} 如上是我实际工作中见到过的例子,还算有代表性。 上面的代码,如果不通读代码或者事先有一些背景知识,我们根本无从知晓代码的准确含义。 还有一个地方,是数字不同于字符串的。 那就是数字可能是一个无限小数。计算机无法精确表示。 那么程序就不得不进行合理近似,而如果同一个程序不同地方采用的近似手段不同,那么就会有问题。而不使用魔法数就可以就可以避免这个问题。 举个例子: 我们需要计算一个圆的面积,可能会这样做: 1area = 3.1415 * R ** 2 1area = 3.141516 * R ** 2 这样就会有问题了。 而如果我们将 PI 的值抽离到一个变量去维护,任何代码都取这个变量的值就不会有问题。那么有人可能有这样的疑问字符串如果拼写错了,是不是也是一样的么? 比如: 1234// a.jsif (type == 'add') {...}// b.jsif (type == 'addd') {...} 事实上,这样的事情很有可能发生。 只不过这种问题相比于数字来说更容易被发现而已。 这么看来魔法数字确实给大家带来了很大的困扰,那么我们是否应该全面杜绝魔法数呢? 取舍之间真的魔法数字(字符串吧)就是不好的么?其实也不见得。 下面再来看一个我实际工作中碰到的例子: 1234567MS = 0;if (type === \"day\") { MS = 24 * 60 * 60 * 1000;}if (type === \"week\") { MS = 7 * 24 * 60 * 60 * 1000;} 这种代码我不知道看了多少遍了。 或许这在大家眼中已然成为了一种共识,那么这种所谓的魔法数字代码的不可读问题就不存在了。 我们仍可以轻易知道代码的含义。 如果将其进行改造: 1234567891011121314151617MS = 0;const HOURS_PER_DAY = 24;const MINUTES_PER_HOUR = 60;const SECONDs_PER_MINUTE = 60;const MS_PER_SECOND = 1000;const DAYS_PER_WEEK = 7;if (type === \"day\") { MS = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDs_PER_MINUTE * MS_PER_SECOND;}if (type === \"week\") { MS = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDs_PER_MINUTE * MS_PER_SECOND;} 上面的代码不见的要比上面的好到哪里。 《程序设计实践》提到了一点“变量命令不是越长越好,越具体越好,而是根据具体的限定范围”。 比如你在 queue 的 class 中定义的 size 字段可以直接叫 size ,而不是 queue_size。 那么一些社会或者编码常识何尝不是一种限定呢? 如果是的话, 我们是否可以不用特殊限定,而回归到“魔法数”呢?","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"eslint","slug":"前端/eslint","permalink":"https://lucifer.ren/blog/categories/前端/eslint/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"eslint","slug":"eslint","permalink":"https://lucifer.ren/blog/tags/eslint/"}]},{"title":"【LeetCode日记】 1371. 每个元音包含偶数次的最长子字符串","slug":"1371.find-the-longest-substring-containing-vowels-in-even-count","date":"2020-05-03T16:00:00.000Z","updated":"2023-01-05T12:24:50.003Z","comments":true,"path":"2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/","link":"","permalink":"https://lucifer.ren/blog/2020/05/04/1371.find-the-longest-substring-containing-vowels-in-even-count/","excerpt":"这道题还是蛮有意思的,我用了多种方法来解决,干货满满,点进来看看?","text":"这道题还是蛮有意思的,我用了多种方法来解决,干货满满,点进来看看? 题目地址(1371. 每个元音包含偶数次的最长子字符串)https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/ 题目描述12345678910111213141516171819202122232425给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。 示例 1:输入:s = "eleetminicoworoep"输出:13解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。示例 2:输入:s = "leetcodeisgreat"输出:5解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。示例 3:输入:s = "bcbcbc"输出:6解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。 提示:1 <= s.length <= 5 x 10^5s 只包含小写英文字母。 暴力法 + 剪枝思路首先拿到这道题的时候,我想到第一反应是滑动窗口行不行。 但是很快这个想法就被我否定了,因为滑动窗口(这里是可变滑动窗口)我们需要扩张和收缩窗口大小,而这里不那么容易。因为题目要求的是奇偶性,而不是类似“元音出现最多的子串”等。 突然一下子没了思路。那就试试暴力法吧。暴力法的思路比较朴素和直观。 那就是双层循环找到所有子串,然后对于每一个子串,统计元音个数,如果子串的元音个数都是偶数,则更新答案,最后返回最大的满足条件的子串长度即可。 这里我用了一个小的 trick。枚举所有子串的时候,我是从最长的子串开始枚举的,这样我找到一个满足条件的直接返回就行了(early return),不必维护最大值。这样不仅减少了代码量,还提高了效率。 代码代码支持:Python3 Python3 Code: 12345678910111213class Solution: def findTheLongestSubstring(self, s: str) -> int: for i in range(len(s), 0, -1): for j in range(len(s) - i + 1): sub = s[j:j + i] has_odd_vowel = False for vowel in ['a', 'e', 'i', 'o', 'u']: if sub.count(vowel) % 2 != 0: has_odd_vowel = True break if not has_odd_vowel: return i return 0 复杂度分析 时间复杂度:双层循环找出所有子串的复杂度是$O(n^2)$,统计元音个数复杂度也是$O(n)$,因此这种算法的时间复杂度为$O(n^3)$。 空间复杂度:$O(1)$ 前缀和 + 剪枝思路上面思路中对于每一个子串,统计元音个数,我们仔细观察的话,会发现有很多重复的统计。那么优化这部分的内容就可以获得更好的效率。 对于这种连续的数字问题,这里我们考虑使用前缀和来优化。 经过这种空间换时间的策略之后,我们的时间复杂度会降低到$O(n ^ 2)$,但是相应空间复杂度会上升到$O(n)$,这种取舍在很多情况下是值得的。 代码代码支持:Python3,Java Python3 Code: 12345678910111213141516171819202122232425262728293031class Solution: i_mapper = { \"a\": 0, \"e\": 1, \"i\": 2, \"o\": 3, \"u\": 4 } def check(self, s, pre, l, r): for i in range(5): if s[l] in self.i_mapper and i == self.i_mapper[s[l]]: cnt = 1 else: cnt = 0 if (pre[r][i] - pre[l][i] + cnt) % 2 != 0: return False return True def findTheLongestSubstring(self, s: str) -> int: n = len(s) pre = [[0] * 5 for _ in range(n)] # pre for i in range(n): for j in range(5): if s[i] in self.i_mapper and self.i_mapper[s[i]] == j: pre[i][j] = pre[i - 1][j] + 1 else: pre[i][j] = pre[i - 1][j] for i in range(n - 1, -1, -1): for j in range(n - i): if self.check(s, pre, j, i + j): return i + 1 return 0 Java Code: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364class Solution { public int findTheLongestSubstring(String s) { int len = s.length(); if (len == 0) return 0; int[][] preSum = new int[len][5]; int start = getIndex(s.charAt(0)); if (start != -1) preSum[0][start]++; // preSum for (int i = 1; i < len; i++) { int idx = getIndex(s.charAt(i)); for (int j = 0; j < 5; j++) { if (idx == j) preSum[i][j] = preSum[i - 1][j] + 1; else preSum[i][j] = preSum[i - 1][j]; } } for (int i = len - 1; i >= 0; i--) { for (int j = 0; j < len - i; j++) { if (checkValid(preSum, s, i, i + j)) return i + 1 } } return 0 } public boolean checkValid(int[][] preSum, String s, int left, int right) { int idx = getIndex(s.charAt(left)); for (int i = 0; i < 5; i++) if (((preSum[right][i] - preSum[left][i] + (idx == i ? 1 : 0)) & 1) == 1) return false; return true; } public int getIndex(char ch) { if (ch == 'a') return 0; else if (ch == 'e') return 1; else if (ch == 'i') return 2; else if (ch == 'o') return 3; else if (ch == 'u') return 4; else return -1; }} 复杂度分析 时间复杂度:$O(n^2)$。 空间复杂度:$O(n)$ 前缀和 + 状态压缩思路前面的前缀和思路,我们通过空间(prefix)换取时间的方式降低了时间复杂度。但是时间复杂度仍然是平方,我们是否可以继续优化呢? 实际上由于我们只关心奇偶性,并不关心每一个元音字母具体出现的次数。因此我们可以使用是奇数,是偶数两个状态来表示,由于只有两个状态,我们考虑使用位运算。 我们使用 5 位的二进制来表示以 i 结尾的字符串中包含各个元音的奇偶性,其中 0 表示偶数,1 表示奇数,并且最低位表示 a,然后依次是 e,i,o,u。比如 10110 则表示的是包含偶数个 a 和 o,奇数个 e,i,u,我们用变量 cur 来表示。 为什么用 0 表示偶数?1 表示奇数? 回答这个问题,你需要继续往下看。 其实这个解法还用到了一个性质,这个性质是小学数学知识: 如果两个数字奇偶性相同,那么其相减一定是偶数。 如果两个数字奇偶性不同,那么其相减一定是奇数。 看到这里,我们再来看上面抛出的问题为什么用 0 表示偶数?1 表示奇数?。因为这里我们打算用异或运算,而异或的性质是: 如果对两个二进制做异或,会对其每一位进行位运算,如果相同则位 0,否则位 1。这和上面的性质非常相似。上面说奇偶性相同则位偶数,否则为奇数。因此很自然地用 0 表示偶数?1 表示奇数会更加方便。 代码代码支持:Python3 Python3 Code: 12345678910111213141516171819202122class Solution: def findTheLongestSubstring(self, s: str) -> int: mapper = { \"a\": 1, \"e\": 2, \"i\": 4, \"o\": 8, \"u\": 16 } seen = {0: -1} res = cur = 0 for i in range(len(s)): if s[i] in mapper: cur ^= mapper.get(s[i]) # 全部奇偶性都相同,相减一定都是偶数 if cur in seen: res = max(res, i - seen.get(cur)) else: seen[cur] = i return res 复杂度分析 时间复杂度:$O(n)$。 空间复杂度:$O(n)$ 关键点解析 前缀和 状态压缩 相关题目 掌握前缀表达式真的可以为所欲为!","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"前缀和","slug":"算法/前缀和","permalink":"https://lucifer.ren/blog/categories/算法/前缀和/"},{"name":"状态压缩","slug":"算法/状态压缩","permalink":"https://lucifer.ren/blog/categories/算法/状态压缩/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"},{"name":"状态压缩","slug":"状态压缩","permalink":"https://lucifer.ren/blog/tags/状态压缩/"}]},{"title":"零基础的前端开发初学者应如何系统地学习?","slug":"how-am-I-learn-fe","date":"2020-04-30T16:00:00.000Z","updated":"2023-01-07T12:34:34.209Z","comments":true,"path":"2020/05/01/how-am-I-learn-fe/","link":"","permalink":"https://lucifer.ren/blog/2020/05/01/how-am-I-learn-fe/","excerpt":"回想四年前我刚入行的时候,那时候很多人对于前端的看法是“切图,画页面,有个编辑器+浏览器就能干,门槛低”,现在已经完全不是那样了,可以说现在的前端这个职业的门槛虽然还是没怎么变,但是整个行业的门槛提升了,换句话说就是整个行业对于前端这个职位要求更高了,对于前端小白的需求量降低,对于高级前端的需求量还在上升,甚至是供小于求的局面。从市场经济学角度上讲你只有进入到高级级别,才能真正吃到行业的红利。 因此想要入行的朋友要先想清楚,不要头脑发热,如果你想清楚了,那么请继续往下看。说实话,现在的前端大环境对初学者来说实在有点不友好,学习资料鱼龙混杂,良莠不齐,有质量很高的学习资料,也有谬论,前后不一,观点错误,或者讲述不清晰的。 更可怕的是质量低下的文章有时候更受欢迎,因此需要大家有很好的甄别能力,但这对于初学者来说实在有些困难,我在这里就来谈一下 初学者如何少走弯路,并且系统性地学习前端。","text":"回想四年前我刚入行的时候,那时候很多人对于前端的看法是“切图,画页面,有个编辑器+浏览器就能干,门槛低”,现在已经完全不是那样了,可以说现在的前端这个职业的门槛虽然还是没怎么变,但是整个行业的门槛提升了,换句话说就是整个行业对于前端这个职位要求更高了,对于前端小白的需求量降低,对于高级前端的需求量还在上升,甚至是供小于求的局面。从市场经济学角度上讲你只有进入到高级级别,才能真正吃到行业的红利。 因此想要入行的朋友要先想清楚,不要头脑发热,如果你想清楚了,那么请继续往下看。说实话,现在的前端大环境对初学者来说实在有点不友好,学习资料鱼龙混杂,良莠不齐,有质量很高的学习资料,也有谬论,前后不一,观点错误,或者讲述不清晰的。 更可怕的是质量低下的文章有时候更受欢迎,因此需要大家有很好的甄别能力,但这对于初学者来说实在有些困难,我在这里就来谈一下 初学者如何少走弯路,并且系统性地学习前端。 兴趣是最好的老师兴趣不管对于学习什么来说都是最好的老师。当然前端也不例外,如果你对这一门感兴趣,绝对会对你有很大的帮助。 关于如何培养兴趣,我提一点,你可以尝试去做一些小的“发明创造”,从而激发自己内心的“成就感”。这些小发明可以是一些小工具,小页面。你可以从开源社区,比如 Github 或者一些论坛,甚至自己的生活中收集一些创作素材。对于我来说,我就做过一个“前端开发工作流”的软件,“siri”, “小门神”等,从而带来成就感,提升自己的兴趣。 权威,权威,还是权威其实技术越往上走,越会关注标准,关注协议等更上层和抽象的东西。而制定这些协议和标准的人往往都是世界上的“殿堂级”程序员,因此关注这些东西对于他们来说就是权威,对他们来说就非常很重要,但是这对于初学者来说似乎还比较遥远。那么初学者如何对接“权威”呢?刚才提到了网上的学习资料参差不齐,这其实对于入门学习来说是很不利的,就像童年时期对于整个人生的影响一样,入门阶段对于整个前端开发生涯的影响也是巨大的。关于如何初学者如何对接权威,我这里总结了以下三点:看一些权威的书籍,包括前端基础,软件工程以及算法等。这里不太建议看太老的,毕竟技术的发展是很快的,以前非常经典的书并一定适合看了,尤其是初学者而言。这里前端方面我重点推荐两本书,一本是《你不知道的 JS》,一本是《JavaScript 语言精粹》。除了前端,你还可以看一些软工类的书,我个人比较喜欢的有《程序员修炼之道》等,算法类的有《图解算法》,《编程之美》等。其他的我就不一一赘述了,想要更多书单的可以私信我。 查权威资料。 这里我推荐两个,一个是MDN的文章,真的是又全面又专业,绝对是前端开发必备神器,哪里不会点哪里。 另外推荐一个Google 开发者 , 里面干货很多,绝对权威。这里顺便再安利一个软件,用来查文档什么的,简直如虎添翼,这个软件的名字是 Dash,大家可以把自己常用的框架,类库等导进去,想用的时候直接查询即可,比去网上搜更快更高效,这个软件对于定制的支持度也是蛮高的,谁用谁知道。 (大家可以看到我下载了很多 documentation) (你可以直达某一个 documentation 搜索,也可以全局搜索,甚至可以搜 goole 和 stackoverflow,是不是很贴心?) 关注一些圈内权威人士。 我一般会关注几个圈内比较知名的人的知乎,微博和 twitter。这是我关注的Github 的权威人士列表。其实这些都是公开的,你也可以点开我的知乎,微博资料看我或者大佬们关注了谁。 做一些完整的简单项目大家可以尝试做一些简单的项目,不要嫌简单。 在做的过程往往能发现很多问题,如果有问题那这正好是自己提高的机会。 如果你觉得很简单,也没有关系,你可以思考一下,我有没有可能做的更好?我能不能把这些东西封装起来,建立更高一层的抽象(A New Level of Abstraction),做到 DRY(Don’t Repeat Yourself)。接下来就是关于怎么找项目。 你可以找个正式工作或者实习来做,也可以自己找一些小项目来练手, 比较常见的练手项目有模仿某个网站,APP 或者搭建自己的个人主页,博客系统等。做好了不仅可以当敲门砖,说不定会收益很长时间呢。实在没有什么项目练手,这里再推荐一个网站,你可以再上面打怪升级。freecodecamp现在你已经掌握了前端开发的基本概念和技能,那么如何做到更进一步,持续成长,做到高级呢?我相信这是很多人的疑问,下面我们就来看一下。 学习路线你可能已经听过过大前端这个词,我这里不是劝退你哦。以下内容很高能,不过很多知识点不知道没关系,因为就算是工作了很多年了老手也很难了解其中的大半知识点。我个人为了让自己巩固知识,同时也为了帮助他人,总结了大前端下的 30 多个主题内容,内容覆盖大前端的方方面面,虽然是从面试角度出发,但是你用来提升自己,查缺补漏也是很有用的。 多图预警: 拿《编程题》主题来说,我总结了各大公司常考的几十道题目。 对于其他主题也是一样,我都会尽可能地深度讲解和剖析,并且从多方面理解,我相信这是在市面上很少见的。 而且我还画了很多图,来帮助大家理解一些抽象的知识点。 项目地址: https://lucifer.ren/fe-interview/#/ 欢迎围观。 开源项目 实话实说,很多优秀的思想,规范,写法我都是从优秀的开源项目中学来的。 我会不定期阅读一些优秀的开源项目源码,也会参与到开源的工作中去,这给我自己带来了很大的提升。 不仅技术得到了提升,团队协作,规范化等方面也有了质的提高,另外还认识了一些优秀的人。四年来,我阅读了很多优秀的源码,也尝试自己去造一些轮子,并开源出去,回馈社区。 输入 + 输出前面重点讲述的是输入。 其实学习的过程不仅仅是输入,输出也是很好的学习方法。 输出的形式有很多,比如写博客,讲给别人,开源出去让别人用等。 这其实是很好的学习机会,这种学习方法可以让你的成长速度呈指数型增长,因此千万不要小看它。 我会通过以练代学的方式来学习,比如我学习 React,我会迅速看文档,然后写 demo,最后我会自己《从零实现 React》来内化它。 我还会定期做总结,写文章,写开源项目,做分享等,目的一方面是影响他人,另一方面是成长自己。 持续学习选择了技术这条路,就要做好持续学习,每日精进的准备,跟上时代潮流是很有必要的。 日报周报。 大家可以订阅一些前端方面的日报周报,这方面其实有很多,这里只推荐一个我常看的一个JavaScript 周刊。我自己也出了一款《每日一荐》, 每天推荐一个优秀的开源项目,优秀文章, 周一到周五我们不见不散。 深度参与开源项目。 关于如何参与开源项目其实可以另起一篇文章了,这里不再赘述,感性的话,我会再写一篇文章,大家记得关注我就好。 定期总结,技术输出。 我的习惯是对于学习的内容定期和不定期地进行总结。 比如最近我在总结的[《leetcode 题解》](现在有 18k+ ✨ 了)(https://github.com/azl397985856/leetcode),[《大前端面试宝典》](https://github.com/azl397985856/fe-interview) 千万不要觉得算法对前端不重要,算法,网络,操作系统属于基础,从事相关工作的都应该认真学习,打好基础才行。 关注我大家可以关注我的公众号《脑洞前端》,公众号后台回复“大前端”,拉你进《大前端面试宝典 - 图解前端群》。回复“leetcode”,拉你进《leetcode 题解交流群》 最后祝大家在前端的路上越走越远。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/categories/学习方法/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/tags/学习方法/"}]},{"title":"纪念LeetCode项目Star突破3W","slug":"thanksGiving3","date":"2020-04-13T16:00:00.000Z","updated":"2023-01-05T12:24:50.001Z","comments":true,"path":"2020/04/14/thanksGiving3/","link":"","permalink":"https://lucifer.ren/blog/2020/04/14/thanksGiving3/","excerpt":"差不多一年的时间,项目收获了第 3W 个 Star,平均差不多一天 100 左右的 star,非常感谢大家的关注和支持。","text":"差不多一年的时间,项目收获了第 3W 个 Star,平均差不多一天 100 左右的 star,非常感谢大家的关注和支持。 30k 截图 Star 曲线Start 曲线上来看,有一点放缓。但是整体仍然是明显的上升趋势。 (star 增长曲线图) 在力扣宣传当力扣官方也开始做每日一题的时候,我的心情是复杂的。怎么官方也开始学我搞每日一题了么?为了信仰(蹭热度),我也毅然决然参加了每日一题活动,贡献了几十篇题解。 三月份是满勤奖,四月份有一次忘记了,缺卡一天。 新书即将上线新书详情戳这里:《或许是一本可以彻底改变你刷 LeetCode 效率的题解书》,目前正在申请书号。 点名感谢各位作者,审阅,以及行政小姐姐。 视频题解最近开始做视频题解了,目前更新了五个视频。和文字题解不同,视频题解可以承载的内容会更多。 https://space.bilibili.com/519510412 我计划更新一些文字题解很难表述的内容,当然还会提供 PPT,如果你喜欢文字,直接看 PPT 即可。 视频题解部分,我会带你拆解 LeetCode 题目,识别常见问题,掌握常见套路。 注意:这不是教你解决某一道题的题解,而是掌握解题方法和思路的题解。 《力扣加加》上线啦我们的官网力扣加加上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/ 点名感谢@三天 @CYL @Josephjinn 朋友的支持很多朋友也在关注我的项目,非常开心。点名感谢 @被单-加加 @童欧巴。 交流群交流群人数也有了很大的提升。 粉丝人数也扩充到了 7000+。交流群数目也增加到了 10 个。其中 QQ 群人数最多,有将近 1800 人。为了限制人数,我开启了收费模式,希望大家不要打我 😂。 非常感谢大家一直以来的陪伴和支持,Fighting 💪。","categories":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/categories/日记/"},{"name":"技术","slug":"日记/技术","permalink":"https://lucifer.ren/blog/categories/日记/技术/"}],"tags":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/tags/日记/"}]},{"title":"提前批算法工程师面试之路","slug":"interview-log-tqp","date":"2020-04-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.853Z","comments":true,"path":"2020/04/13/interview-log-tqp/","link":"","permalink":"https://lucifer.ren/blog/2020/04/13/interview-log-tqp/","excerpt":"作者:宝石 校对&整理:lucifer","text":"作者:宝石 校对&整理:lucifer vivo(已拿 offer)技术面(30min) 自我介绍 讲实习 讲比赛 问了些大数据的问题 spark transform 和 action 的区别 手撕了个归并排序 hr 面(30min,技术只有一面) 自我介绍 家庭情况 讲一下实习亮点 有女朋友么 父母同意你去深圳么 讲一下优缺点 常规问题等等 昆仑万维(已拿 offer)一面(1h): 上来自我介绍 讲一下判断回文链表的思路,多方法 用纸写一下反转链表 说说 python shuffle 怎么实现的,O(N)时间,O(1)空间 看你计算机专业,知道哈夫曼树吧,w2v 有用,你能说说么(我就记得分层 softmax 了,实在是不会) 说说传统机器学习你都了解哪些?推一下 LR,说一下原理 知道 kmeans 吧,说下迭代过程,簇心随机不好,怎么才能更稳定(类 kmeans++) 说说深度学习你都知道哪些模型?我说了 LSTM RNN,还没等说推荐的模型。。。 讲一下 LSTM 吧,门都是怎么迭代的 各个激活函数的优缺点,sigmoid relu leaklyrelu tanh,又说了下梯度消失 Sigmoid 求导是什么? 了解推荐么,说说都知道啥,嘴贱说了个 CF 讲一下 Item-CF,怎么计算相似度(用交并,也就是 Jaccard 距离,速度比 cos 快),用什么优化呢(倒排索引) 讲讲数据倾斜怎么处理吧?(用 key+随机前后缀) 聊一下你这个项目里 LDA 吧?我直接说这个我用过没细研究(他也认同,说数学原理很复杂) 聊一下你这个项目 query title 统计特征都提了啥,跟他聊了一下,他和我说了一下他的业务理解 反问 做什么的(啥都做,业务很广。。。) 用哪些技术(啥都用,技术栈很广。。。) 昆仑万维二面(就是确认时间 不算面试) 和一面面试官聊的如何 知道他们部门做什么的么 接下来约一下 boss 面,确认时间 结束 昆仑万维三面(不到二十分钟,压力面): 上来就问比赛,两个比赛都问了,和第一差距在哪 下面问题都是快问快答,都没深问,问题可能记不全了: 说下你实习吧,没说几句。。 你怎么解决样本不均衡的 kmeans 适用于什么情况 python dict 怎么用 为什么会产生哈希冲突 python set dict list 啥场景用 过拟合有哪些解决方法 牛顿法和拟牛顿法 200w 不重复单词一口气放内存中,怎么放最省内存(不会) 你除了学习之外还做什么项目 平常刷算法题么,刷多少了 另一个面试官的问题不是压力测试 你希望做什么种类的工作(大概就是这个意思) 没得反问 京东一面(40min) 很好的年轻女面试官 自我介绍 跟我聊了一下,然后说看你挺直率,我就直接说了,你想找推荐,我们是机器学习+组合优化,偏向运筹学,考虑么,(我说只要不是 cvnlp,我全要) 考虑那就开始问些问题吧: 你讲讲你的实习,最亮点,给他分析了一波我采样策略。 你知道 gbdt 和 xgboost 吧,给我讲讲(疯狂吹逼 xgboost) 你知道最大似然估计和最大后验概率估计么,或者挑一个你熟悉的说下(闭着眼睛推 LR,啥伯努利分布,似然函数,疯狂扯) 来做个题吧,1000w 个数,数范围[-1000, 1000],写个排序(闭着眼睛桶排) 你能提前来实习么 反问(京东零售部的,技术栈 balabala) 复试待安排 二面(30min) 自我介绍 找个比赛说说创新点,你主要做了哪些创新,最后模型为什么用 CNN 系不用 RNN 系 由于上面我说我工作有训练词向量了,让我讲 word2vec 的架构,和一些细节问题 为什么 w2v 向量在语义空间内有很好的数学性质,比如相加减 数学问题:M 个样本有放回采样 N 次,问某条样本一次没被采中概率 给你均值方差,让你利用正态分布随机生成 1000 个点(不能用库,说的不是很好) 反问:哪个部门哪个组(零售-技术中台下) 为什么选择京东,京东有什么核心竞争力(疯狂扯,我说我不太看好那些不能落地的,因为 jd 是电商,整个算法流程系统化工程化一定很健全,也有实际落地,带来实际效益,面试官非常赞同) 最后手里有啥 offer 啊 没了 hr 面(20min) 小姐姐人很好,迟到了四分钟,上来道歉一波 自我介绍 遇到压力大的时候,比如在实习问题解决不了了,你会怎么办 与 mentor 产生意见分歧要怎么做 未来如果入职京东,对领导有什么要求呢 你平常有什么学习习惯么 你平常会定期做总结么 反问 当时问应届生入职需要做出啥改变,给小姐姐问懵了,我又补充说比如思想啥的需要有啥改变么,她给我讲了五六分钟,说的很直白,没啥官腔,说在学校如何如何,你来公司了又会如何如何 啥时候有结果:她也不知道,她拿到的是乱序的面试名单,可能后面会统一安排通知 一点资讯一面(不到 40min) 自我介绍 讲你的论文,这块一直问,问得特别细节,也问了好久,估计面试官比较清楚这块东西。 讲实习,都怎么做的,遇到啥问题,怎么解决。 讲一下 FM,DeepFM 这一系列(我从 LR,POLY2,FM,FFM,Wide&Deep,DeepFm 说了个遍) 做了个算法题,A 了 反问: 部门:算法部信息流,和我微博实习的比较类似 技术:做推荐 Java 和 Scala 用的多一些 个人感觉像不招人。。。 大华一面 - 大数据工程师(数据挖掘)(不到 40min) 面试官很有礼貌 自我介绍 着重问了好久实习 着重问了好久比赛 linux 查指定文件前缀有哪些命令 讲一下 hive 和 hadoop 关系 hadoop mapreduce 执行流程 java 类和对象关系 百度一面(1h) 自我介绍 介绍实习,然后疯狂挖实习,问的很深 问如果模型区分不开一些样本,要怎么采样?业界有哪些常用采样策略。我真是懵了。。 问了一堆 fm,比如表达式,复杂度,改进复杂度的推导 了解深度学习么,从 wide&deep 出发疯狂问,有的是真不会,要再复习一下 面试官说了个 DCN(深度交叉网络),我还以为深度卷积神经网络。。。,结果深度交叉网络的细节也给忘了 我主动给介绍了下阿里的 DIN 和 DIEN,他问这模型可以用在新闻推荐场景么(答不可以,因为新闻类实时性比较强 balabala。。。不知道对不对) 如果想让你加入一个用户短期兴趣,比如刚发布的新闻打分低,要怎么改,(我记得在 YouTube 有个说了关于这个,我说加了个时间维度特征,或者时间衰减) 让我讲 BN,为什么提出 BN(好久没看 nn 的东西了,直说了个表象,容易收敛,面试官说为了解决输入分布不一致,bn 可以拉回来分布,我把这个忘了) 从 LR 出发问了我 sgd,如何改进,说了个 momentum,再怎么改进,我说我了解个 FTRL 说一下 boosting bagging ,lgb 为什么并行化效率高(答单边梯度抽样+直方图计算+互斥特征捆绑) 怎么分析并解过拟合问题的 算法题:三数之和 反问 部门是推荐策略部 主要场景是百度直播和贴吧推荐 用 Python 和 C++,不用 Java 触宝一面(1h) 自我介绍 数据结构基础 数组和链表区别,应用场景 疯狂问排序算法,最优最坏平均复杂度,稳定排序有哪些(好长时间没复习这个了,答得比较差) 一个剪枝题,口述算法过程,分析时空复杂度 说说面向过程、对象、切片编程的区别(我。。。。。。) 机器学习基础 讲一下你了解哪些分类模型 说说 SVM 讲讲 id3 和 c4.5 讲讲 xgboost 和 gbdt 讲讲怎么判断 kmeans 的 k,聚类效果的好坏 k 可以用肘部法则 SSE(误差平方和)和轮廓系数 讲讲分类,回归,推荐,搜索的评价指标 讲讲 lr 和 fm,fm 的后续(ffm) 讲讲你知道的各种损失函数 讲讲 l1 l2 正则,各自的特点 深度学习基础 说说 deepfm,说说 fm 在 nn 中还有哪些(FNN,NFM,AFM) 说说类似 l1,l2 正则化降低模型过拟合,还有什么别的方法 说一下 sgd→adam 的过程(下面是面试后简单复盘,本身答的一般) sgd momentum 利用了历史信息,意味着参数更新方向不仅由当前的梯度决定,也与此前累积的下降方向有关。这使得参数中那些梯度方向变化不大的维度可以加速更新,并减少梯度方向变化较大的维度上的更新幅度。由此产生了加速收敛和减小震荡的效果。 rmsprop 在 Adagrad 中, 问题是学习率逐渐递减至 0,可能导致训练过程提前结束。为了改进这一缺点,可以考虑在计算二阶动量时不累积全部历史梯度,而只关注最近某一时间窗口内的下降梯度。根据此思想有了 RMSprop,采用的指数移动平均公式计算 adam 可以认为是 RMSprop 和 Momentum 结合并加了偏差校正,因为初始化是 0,有一个向初值的偏移(过多的偏向了 0)。因此,可以对一阶和二阶动量做偏置校正 (bias correction), 介绍下梯度消失和梯度爆炸 都有哪些方法解决这两个问题 你了解多目标优化,迁移学习么(不知道) 场景问题 让你加一个兴趣类型特征 你要怎么做 如何处理年龄类特征 你了解相似向量快速计算的方法吗(就记得个啥哈希分桶,没做过) 局部哈希计算,高维相近的点低维也相近,但是高维较远的点低维可能也相近,将 embedding 应设成 1 维,若担心把远的也算进来可以多设置几个 hash 函数等等。 如何判断你模型上线的好坏 给你个 nn 模型,如何调参,如何修改架构 如何解决冷启动问题 用户侧,物品侧 推荐系统的整体架构 线上推断这部分再具体点怎么实现的 反问 触宝内容推荐(小说) 主要用 python 等后续 hr 通知吧 二面(45min) 面试官人很好,和善可亲 自我介绍 讲下实习做了哪些优化,问了些问题(我都没介绍实习,面试官已经直接点破我每一点实际都在做什么) 讨论了一下抽样,作为一个算法工程师如何将抽样导致的得分分布变化给拉回来? 因为实习模型是 FM,详细讲了下 FM,讨论了下 FM 的泛化性 用的什么优化算法,顺便介绍下 sgd 后续的优化,sgd→momentun→rmsprop→adam,一面问过的,复盘过 实习有没有除错过导致线上有点问题(还真有。。。) hadoop shuffle 干啥的,为啥 key 相同的要放在一起 python 深拷贝和浅拷贝的区别 linux 替换文件中所有的 a,我说的 awk 或者 tr 算法题:给两个字符串 S 和 T,计算 S 的子序列中 T 出现的次数(dfs A 了) 反问:竟然和一面面试官不是一个部门。。。二面面试官给我介绍了算法在他们公司都有哪些应用。。。 总之要有工程师顶层思维,不能局限于模型优化啥的。 大家可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 35K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/categories/校招/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/categories/面经/"},{"name":"百度","slug":"百度","permalink":"https://lucifer.ren/blog/categories/百度/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"面经","slug":"面经","permalink":"https://lucifer.ren/blog/tags/面经/"},{"name":"校招","slug":"校招","permalink":"https://lucifer.ren/blog/tags/校招/"},{"name":"百度","slug":"百度","permalink":"https://lucifer.ren/blog/tags/百度/"}]},{"title":"贪婪策略系列 - 覆盖篇","slug":"leetcode-greedy","date":"2020-04-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.770Z","comments":true,"path":"2020/04/13/leetcode-greedy/","link":"","permalink":"https://lucifer.ren/blog/2020/04/13/leetcode-greedy/","excerpt":"贪婪策略是一种常见的算法思想,具体是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这点和动态规划一样。 LeetCode 上对于贪婪策略有 73 道题目。我们将其分成几个类型来讲解,截止目前我们暂时只提供覆盖问题,其他的可以期待我的新书或者之后的题解文章。","text":"贪婪策略是一种常见的算法思想,具体是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这点和动态规划一样。 LeetCode 上对于贪婪策略有 73 道题目。我们将其分成几个类型来讲解,截止目前我们暂时只提供覆盖问题,其他的可以期待我的新书或者之后的题解文章。 覆盖我们挑选三道来讲解,这三道题除了使用贪婪法,你也可以尝试动态规划来解决。 45. 跳跃游戏 II,困难 1024. 视频拼接,中等 1326. 灌溉花园的最少水龙头数目,困难 覆盖问题的一大特征,我们可以将其抽象为给定数轴上的一个大区间 I 和 n 个小区间 i[0], i[1], ..., i[n - 1],问最少选择多少个小区间,使得这些小区间的并集可以覆盖整个大区间。 我们来看下这三道题吧。 45. 跳跃游戏 II题目描述给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 示例: 输入: [2,3,1,1,4]输出: 2解释: 跳到最后一个位置的最小跳跃数是 2。 从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。说明: 假设你总是可以到达数组的最后一个位置。 思路贪婪策略,即我们每次在可跳范围内选择可以使得跳的更远的位置,由于题目保证了你总是可以到达数组的最后一个位置,因此这种算法是完备的。 如下图,开始的位置是 2,可跳的范围是橙色的。然后因为 3 可以跳的更远,所以跳到 3 的位置。 如下图,然后现在的位置就是 3 了,能跳的范围是橙色的,然后因为 4 可以跳的更远,所以下次跳到 4 的位置。 写代码的话,我们用 end 表示当前能跳的边界,对于上边第一个图的橙色 1,第二个图中就是橙色的 4,遍历数组的时候,到了边界,我们就重新更新新的边界。 图来自 https://leetcode-cn.com/u/windliang/ 代码代码支持:Python3 Python3 Code: 12345678910class Solution: def jump(self, nums: List[int]) -> int: n, cnt, furthest, end = len(nums), 0, 0, 0 for i in range(n - 1): furthest = max(furthest, nums[i] + i) if i == end: cnt += 1 end = furthest return cnt 复杂度分析 时间复杂度:$O(N)$。 空间复杂度:$O(1)$。 1024. 视频拼接题目描述你将会获得一系列视频片段,这些片段来自于一项持续时长为 T 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。 视频片段 clips[i] 都用区间进行表示:开始于 clips[i][0] 并于 clips[i][1] 结束。我们甚至可以对这些片段自由地再剪辑,例如片段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。 我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, T])。返回所需片段的最小数目,如果无法完成该任务,则返回 -1 。 示例 1: 输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 10输出:3解释:我们选中 [0,2], [8,10], [1,9] 这三个片段。然后,按下面的方案重制比赛片段:将 [1,9] 再剪辑为 [1,2] + [2,8] + [8,9] 。现在我们手上有 [0,2] + [2,8] + [8,10],而这些涵盖了整场比赛 [0, 10]。示例 2: 输入:clips = [[0,1],[1,2]], T = 5输出:-1解释:我们无法只用 [0,1] 和 [0,2] 覆盖 [0,5] 的整个过程。示例 3: 输入:clips = [[0,1],[6,8],[0,2],[5,6],[0,4],[0,3],[6,7],[1,3],[4,7],[1,4],[2,5],[2,6],[3,4],[4,5],[5,7],[6,9]], T = 9输出:3解释:我们选取片段 [0,4], [4,7] 和 [6,9] 。示例 4: 输入:clips = [[0,4],[2,8]], T = 5输出:2解释:注意,你可能录制超过比赛结束时间的视频。 提示: 1 <= clips.length <= 1000 <= clips[i][0], clips[i][1] <= 1000 <= T <= 100 思路贪婪策略,我们选择满足条件的最大值。和上面的不同,这次我们需要手动进行一次排序,实际上贪婪策略经常伴随着排序,我们按照 clip[0]从小到大进行排序。 如图: 1 不可以,因此存在断层 2 可以 3 不行,因为不到 T 我们当前的 clip 开始结束时间分别为 s,e。 上一段 clip 的结束时间是 t1,上上一段 clip 结束时间是 t2。 那么这种情况下 t1 实际上是不需要的,因为 t2 完全可以覆盖它: 那什么样 t1 才是需要的呢?如图: 用代码来说的话就是s > t2 and t2 <= t1 代码代码支持:Python3 Python3 Code: 12345678910111213141516class Solution: def videoStitching(self, clips: List[List[int]], T: int) -> int: # t1 表示选取的上一个clip的结束时间 # t2 表示选取的上上一个clip的结束时间 t2, t1, cnt = -1, 0, 0 clips.sort(key=lambda a: a[0]) for s, e in clips: # s > t1 已经确定不可以了, t1 >= T 已经可以了 if s > t1 or t1 >= T: break if s > t2 and t2 <= t1: cnt += 1 t2 = t1 t1 = max(t1,e) return cnt if t1 >= T else - 1 复杂度分析 时间复杂度:由于使用了排序(假设是基于比较的排序),因此时间复杂度为 $O(NlogN)$。 空间复杂度:$O(1)$。 1326. 灌溉花园的最少水龙头数目题目描述在 x 轴上有一个一维的花园。花园长度为 n,从点 0 开始,到点 n 结束。 花园里总共有 n + 1 个水龙头,分别位于 [0, 1, …, n] 。 给你一个整数 n 和一个长度为 n + 1 的整数数组 ranges ,其中 ranges[i] (下标从 0 开始)表示:如果打开点 i 处的水龙头,可以灌溉的区域为 [i - ranges[i], i + ranges[i]] 。 请你返回可以灌溉整个花园的 最少水龙头数目 。如果花园始终存在无法灌溉到的地方,请你返回 -1 。 示例 1: 输入:n = 5, ranges = [3,4,1,1,0,0]输出:1解释:点 0 处的水龙头可以灌溉区间 [-3,3]点 1 处的水龙头可以灌溉区间 [-3,5]点 2 处的水龙头可以灌溉区间 [1,3]点 3 处的水龙头可以灌溉区间 [2,4]点 4 处的水龙头可以灌溉区间 [4,4]点 5 处的水龙头可以灌溉区间 [5,5]只需要打开点 1 处的水龙头即可灌溉整个花园 [0,5] 。示例 2: 输入:n = 3, ranges = [0,0,0,0]输出:-1解释:即使打开所有水龙头,你也无法灌溉整个花园。示例 3: 输入:n = 7, ranges = [1,2,1,0,2,1,0,1]输出:3示例 4: 输入:n = 8, ranges = [4,0,0,0,0,0,0,0,4]输出:2示例 5: 输入:n = 8, ranges = [4,0,0,0,4,0,0,0,4]输出:1 提示: 1 <= n <= 10^4ranges.length == n + 10 <= ranges[i] <= 100 思路贪心策略,我们尽量找到能够覆盖最远(右边)位置的水龙头,并记录它最右覆盖的土地。 我们使用 furthest[i] 来记录经过每一个水龙头 i 能够覆盖的最右侧土地。 一共有 n+1 个水龙头,我们遍历 n + 1 次。 对于每次我们计算水龙头的左右边界,[i - ranges[i], i + ranges[i]] 我们更新左右边界范围内的水龙头的 furthest 最后从土地 0 开始,一直到土地 n ,记录水龙头数目 代码代码支持:Python3 Python3 Code: 123456789101112131415class Solution: def minTaps(self, n: int, ranges: List[int]) -> int: furthest, cnt, cur = [0] * n, 0, 0 for i in range(n + 1): l = max(0, i - ranges[i]) r = min(n, i + ranges[i]) for j in range(l, r): furthest[j] = max(furthest[j], r) while cur < n: if furthest[cur] == 0: return -1 cur = furthest[cur] cnt += 1 return cnt 复杂度分析 时间复杂度:时间复杂度取决 l 和 r,也就是说取决于 ranges 数组的值,假设 ranges 的平均大小为 Size 的话,那么时间复杂度为 $O(N * Size)$。 空间复杂度:我们使用了 furthest 数组, 因此空间复杂度为 $O(N)$。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"贪婪","slug":"贪婪","permalink":"https://lucifer.ren/blog/categories/贪婪/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"},{"name":"贪婪","slug":"贪婪","permalink":"https://lucifer.ren/blog/tags/贪婪/"}]},{"title":"『不要再问我头像如何变灰了,试试这几种滤镜吧!』","slug":"canvas-filter","date":"2020-04-11T16:00:00.000Z","updated":"2023-01-07T12:20:39.957Z","comments":true,"path":"2020/04/12/canvas-filter/","link":"","permalink":"https://lucifer.ren/blog/2020/04/12/canvas-filter/","excerpt":"在实际的工作中,有时候会有一些需求,让你做一些图片的滤镜效果,比如将图片变成黑白,调整图片亮度等。本文手把手教你如何实现五种滤镜效果,核心代码总共不到 70 行。 笔者所在的公司就有一个需求需要用到图片处理的知识,大概场景我来描述一下: 用户可以手动上传印章,并且支持给印章设置不同的显示效果,这里的效果具体指的是“线条的清晰程度”,如下图所示: 这里我们使用 Canvas 来实现。如果你对 Canvas 不熟悉,建议看下之前我写的一篇文章100 * 100 Canvas 占用内存多大,花上几分钟看完,基本上够看懂这篇文章了。","text":"在实际的工作中,有时候会有一些需求,让你做一些图片的滤镜效果,比如将图片变成黑白,调整图片亮度等。本文手把手教你如何实现五种滤镜效果,核心代码总共不到 70 行。 笔者所在的公司就有一个需求需要用到图片处理的知识,大概场景我来描述一下: 用户可以手动上传印章,并且支持给印章设置不同的显示效果,这里的效果具体指的是“线条的清晰程度”,如下图所示: 这里我们使用 Canvas 来实现。如果你对 Canvas 不熟悉,建议看下之前我写的一篇文章100 * 100 Canvas 占用内存多大,花上几分钟看完,基本上够看懂这篇文章了。 准备工作首先我们先将图片绘制到 Canvas 画布上,为了简单起见,图片大小固定为 300 x 300。 1<canvas id=\"canvas\" width=\"300px\" height=\"300px\"></canvas> (html) 12345678910//获取canvas元素ctx = document.getElementById(\"canvas\").getContext(\"2d\");//创建image对象var img = new Image();img.src = require(\"./seal.png\");//待图片加载完后,将其显示在canvas上img.onload = () => { ctx.drawImage(img, 0, 0); this.imgData = ctx.getImageData(0, 0, 300, 300);}; (js) 效果是这样的: 操作像素熟悉 Canvas 的应该知道上面的 this.imgData 实际上就是ImageData类的实例,其中 imgData.data 是一个 Uint8ClampedArray, 其描述了一个一维数组,包含以 RGBA 顺序的数据,数据使用 0 至 255(包含)的整数表示。 简单来说,就是图片像素信息,每四位表示一个像素单元。其中每四位的信息分别是 RGBA。即第一个 Bit 标记 R,第二个 Bit 表示 G,第三个 Bit 表示 B,第四个 Bit 表示 A,第五个 Bit 又是 R…,依次类推。 接下来,我们就要操作 imgData,来实现滤镜的效果。简单起见,我这里对超过 200 的值进行了一次提高亮度的操作。实际上这个值是 200,还是别的数字,需要我们化身”调参工程师”,不断实验才行。 并且粗暴地对 RGB 执行同样的逻辑是不合理的。更为合理的做法是对 RGB 的阀值分别进行度量,由于比较麻烦,我这里没有实现。但是如果你对效果要求比较高,那么最好可以分开度量。 123456789101112131415const data = this.imgData.data;for (let i = 0; i < data.length; i += 4) { if (data[i] < 200) { data[i] = data[i] + brightness > 255 ? 255 : data[i] + brightness; } if (data[i + 1] < 200) { data[i + 1] = data[i + 1] + brightness > 255 ? 255 : data[i + 1] + brightness; } if (data[i + 2] < 200) { data[i + 2] = data[i + 2] + brightness > 255 ? 255 : data[i + 2] + brightness; }} 如上,我们对图片的像素进行了处理,以达到我们的目的,这样从用户感官上来看,显示效果发生了变化,大概效果如图: (清晰版) (模糊版) 如果你愿意的话,你也可以将处理好的图片进行导出,也很简单,直接调用 Canvas 实例的 toDataURL 方法即可,图片保存的格式也可以在这个方法中进行指定。 日常开发中,我们还可能碰到很多其他的滤镜效果。下面介绍几个比较现常见的效果。 如果你正好用到了不妨作为参考。如果遇到了新的滤镜效果, 不妨在文末向我留言,看到后会及时回答,提前感谢你的参与。 下面介绍其他四种滤镜效果。这里只贴出核心代码,完整代码可以访问我的 Github Repo 进行查看。如果你嫌下载到本地麻烦,也可以在这里在线安装并访问,打开这个链接,分别执行yarn和yarn start即可。 以下效果均以下图为原图制作: 如何实现黑白效果 12345for (let i = 0; i < data.length; i += 4) { // 将红黄蓝按照一定比例混合,具体比例为0.299 : 0.587 : 0.114, 这个比例需要慢慢调制。 const avg = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; data[i] = data[i + 1] = data[i + 2] = avg;} 如何实现反色效果 12345for (let i = 0; i < data.length; i += 4) { data[i] = 255 - data[i]; //r data[i + 1] = 255 - data[i + 1]; //g data[i + 2] = 255 - data[i + 2]; //b} 如何给图片增加噪音 123456const random = ((Math.random() * 70) >>> 0) - 35;for (let i = 0; i < data.length; i += 4) { data[i] = data[i] + random; data[i + 1] = data[i + 1] + random; data[i + 2] = data[i + 2] + random;} 如何提高图片亮度 123456const brightness = +e.target.value;for (let i = 0; i < data.length; i += 4) { data[i] = data[i] + brightness > 255 ? 255 : data[i] + brightness; data[i + 1] = data[i + 1] + brightness > 255 ? 255 : data[i + 1] + brightness; data[i + 2] = data[i + 2] + brightness > 255 ? 255 : data[i + 2] + brightness;} 总结本文通过不到 70 行代码实现了五种滤镜效果,对于其他滤镜效果也可以参考这种方式来实现。还不赶紧拿上小姐姐的照片来秀一手么?","categories":[],"tags":[{"name":"Canvas","slug":"Canvas","permalink":"https://lucifer.ren/blog/tags/Canvas/"},{"name":"图片处理","slug":"图片处理","permalink":"https://lucifer.ren/blog/tags/图片处理/"},{"name":"滤镜","slug":"滤镜","permalink":"https://lucifer.ren/blog/tags/滤镜/"}]},{"title":"《82 年生的金智英》","slug":"82-jinzhiying","date":"2020-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.401Z","comments":true,"path":"2020/04/06/82-jinzhiying/","link":"","permalink":"https://lucifer.ren/blog/2020/04/06/82-jinzhiying/","excerpt":"《82 年生的金智英》(韩文原始名: 82년생 김지영)是一部由同名小说改编,于 2019 年 10 月 23 号在韩国上映的韩国电影。由金度英执导,郑裕美、孔刘主演。该片讲述出生于 1982 年的三十多岁平凡女性金智英,在产子后因为周围人事变化,以及家庭中婆婆等家人的言行一度造成其心理疾病,以及在其丈夫和家人的帮助下寻找自我恢复。 ​","text":"《82 年生的金智英》(韩文原始名: 82년생 김지영)是一部由同名小说改编,于 2019 年 10 月 23 号在韩国上映的韩国电影。由金度英执导,郑裕美、孔刘主演。该片讲述出生于 1982 年的三十多岁平凡女性金智英,在产子后因为周围人事变化,以及家庭中婆婆等家人的言行一度造成其心理疾病,以及在其丈夫和家人的帮助下寻找自我恢复。 ​ 评分 观后感金智英的生活状况和很多人一样。片子着重讲述的是女人的社会生活状况,包括但不限于被工作歧视,家庭重男轻女,社会中处于弱势位置。 工作中,由于是女性,会被歧视,调侃,连升职也会变得不顺利。借用片中的台词就是“和我一起的男同事,早就升到了 xx”。 重男轻女在韩国就好像之前的中国一样。是整个社会的意识,很难从根本上得到改观。”女生就应该相夫教子等“观念已经深入人心,尤其是老一辈。 社会中处弱势位置。片中讲述了小女孩被一个男孩子盯上,吓得给公交车路人发暗号,并向爸爸发信息求救。爸爸知道了还责怪女孩子不小心,这一幕既真实又令人深思。上厕所被偷拍,以至于上厕所不得不小心翼翼。 带孩子的种种艰辛,或许带过孩子的人会感同身受吧。 我看的时候弹幕一直在刷”我一定要对我的老婆好“,”只恋爱不结婚“。但是话容易说,两个人只有相互理解,尊重,才是对另一方好。否则也只是自己的一厢情愿,强加于人罢了。这样的话,与其说是对别人好,倒不如说是”给自己赎罪,减轻自己的心灵负担“的自私行为罢了。 总的来说,由于社会阅历等原因,这部片子没有给我感同身受的感觉。但是确是一部反映现实,控诉社会的好电影。","categories":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/categories/电影/"},{"name":"观后感","slug":"电影/观后感","permalink":"https://lucifer.ren/blog/categories/电影/观后感/"}],"tags":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/tags/电影/"}]},{"title":"《饥饿站台》","slug":"movie-eager-game","date":"2020-04-05T16:00:00.000Z","updated":"2023-01-05T12:24:49.768Z","comments":true,"path":"2020/04/06/movie-eager-game/","link":"","permalink":"https://lucifer.ren/blog/2020/04/06/movie-eager-game/","excerpt":"《饥饿站台》 (西班牙语:El hoyo)是一部 2019 年的西班牙科幻惊悚电影。导演为加尔德图.加兹特鲁—乌鲁蒂亚里,编剧为佩德罗.里书罗、大卫・狄索拉;由伊万·马萨格、安东尼亚·圣·胡安、佐里昂・埃奎勒、埃米利奥·布阿勒、亚莉珊卓·马桑凯主演。电影情节设于一个塔状的监狱中,囚犯从监狱中间逐渐下降的大平台拿取食物。2019 年 9 月 6 日于 2019 年多伦多国际影展举行首映。","text":"《饥饿站台》 (西班牙语:El hoyo)是一部 2019 年的西班牙科幻惊悚电影。导演为加尔德图.加兹特鲁—乌鲁蒂亚里,编剧为佩德罗.里书罗、大卫・狄索拉;由伊万·马萨格、安东尼亚·圣·胡安、佐里昂・埃奎勒、埃米利奥·布阿勒、亚莉珊卓·马桑凯主演。电影情节设于一个塔状的监狱中,囚犯从监狱中间逐渐下降的大平台拿取食物。2019 年 9 月 6 日于 2019 年多伦多国际影展举行首映。 评分影评网站烂番茄的 54 条评论中,其中 45 篇给出了“新鲜”的正面评价,“新鲜度”为 83%,平均分数 7.43 分(满分 10 分)。 观后感这部片子是少有的可以在国内放映的”限制级“电影。其中有很多暴力血腥以及色情内容。本部片子的主线很简单,简单到“很多人看几分钟的简介就可以了解到整部片子的内容”。 这是一个具有讽刺意味的电影 - “世间只有三类人,一类高层人,一类底层人,还有一类正在坠落。” 片中用楼层的来反应阶级,片中多次有人从上面掉下来,其中掉落的时间都是月末。或许是过惯了好日子,无法再忍受底层的艰苦,而选择了死亡。 片子有一个设定: 每个月都会重新洗牌,交换一次楼层。 片中有合作,背叛,猜忌等人性面,这在平常的生活中很难显现。这让我想起了之前看过的《欺诈游戏》,《下一层》,以及玩过的游戏《999 逃脱系列》。人与人之间,最难建立的是信任,并且信任一旦失去便很难重新建立。如果每个人都能足够信任,就不会存在下层人被饿死的局面。 实际上,这种阶层的观念是很难消除的,这是群体意识决定的。 《乌合之众》中也反复强调过群体意识和个人意识的不同,提到“群体往往呈现出“盲目”、“冲动”、“狂热”、“轻信”的特点,而统治者又是如何利用群体的这些特点建立和巩固自身统治的”。群体意识是会被”利用“的,这种利用可能是好的方向,也可能是不好的方向。群体的力量过于巨大,如同没有被驯化的野兽一般。 我不怕鬼,但是我怕扮成“鬼”的人。","categories":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/categories/电影/"},{"name":"观后感","slug":"电影/观后感","permalink":"https://lucifer.ren/blog/categories/电影/观后感/"}],"tags":[{"name":"电影","slug":"电影","permalink":"https://lucifer.ren/blog/tags/电影/"}]},{"title":"回炉重铸, 91 天见证不一样的自己(2)","slug":"91-algo2","date":"2020-03-24T16:00:00.000Z","updated":"2023-01-07T12:34:56.018Z","comments":true,"path":"2020/03/25/91-algo2/","link":"","permalink":"https://lucifer.ren/blog/2020/03/25/91-algo2/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。前天想加入却没能加入的小伙伴可以进来啦,直接扫描文末二维码即可。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。前天想加入却没能加入的小伙伴可以进来啦,直接扫描文末二维码即可。 ​ 自从前天开始,就接到了非常多的入群申请, 我一个人忙不过来, 就暂停了入群,希望大家理解。今天除了宣布微信群今天开启入群 之外,还给大家带来了几个大家问的多的问题的答案。分别是我需要提前准备什么?,具体形式是怎么样的?,我还可以入群么?。 qq 群开放时间待定,开放后会第一时间在朋友圈和微信交流群进行告知 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。1 活动时间2020-06-01 至 2020-08-30 需要提前准备些什么? 数据结构与算法的基础知识。 推荐看一下大学里面的教材讲义,或者看一些入门的图书,视频等,比如《图解算法》,邓俊辉的《数据结构与算法》免费视频课程。总之, 至少你要知道有哪些常见的数据结构与算法以及他们各自的特点。 有 Github 账号,且会使用 Github 常用操作。 比如提 issue,留言等。 有 LeetCode 账号,且会用其提交代码。 语言不限,大家可以用自己喜欢的任何语言。同时我也希望你不要纠结于语言本身。 具体形式是什么样的? 总共三个大的阶段 每个大阶段划分为几个小阶段 每个小阶段前会将这个小阶段的资料发到群里 每个小阶段的时间内,每天都会出关于这个阶段的题目,第二天进行解答 比如: 第一个大阶段是基础 基础中第一个小阶段是数组,栈和队列。 数组,栈和队列正式开始前,会将资料发到群里,大家可以提前预习。 之后的每天都会围绕数组,栈和队列出一道题,第二天进行解答。大家可以在出题当天上 Github 上打卡。 大家遇到问题可以在群里回答,对于比较好的问题,会记录到 github issue 中,让更多的人看到。Github 仓库地址届时会在群里公布。 有微信群么?有很多小伙伴反应没有 qq,或者平时不用 qq,能否提供微信群供学习。其实我的内心是拒绝的,这会增加系统复杂度。但是随着反应的人数越来越多,我决定开发微信群。还是和 qq 群一样的收费标准,,具体看下方。 如何加入微信群?添加我的微信,备注“91 算法”。那么怎么添加我呢?大家可以关注公众号脑洞前端,然后点击更多,在弹出的菜单中选择联系我即可。 冲鸭课程大纲可以点这里查看 收费标准: 前 50 人免费 51 - 100 收费 5 元 101 - 500 收费 10 元 目前已经满 100 人了。 本次活动并不是为了赚钱,而是为了给想学习的人营造一个良好的学习氛围,并且我们会对活跃的群员进行抽奖,活动基金就来源于大家的入群费。 入群截止时间: 2020-05-31 24:00:00","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"位运算","slug":"bit","date":"2020-03-23T16:00:00.000Z","updated":"2023-01-05T12:24:49.491Z","comments":true,"path":"2020/03/24/bit/","link":"","permalink":"https://lucifer.ren/blog/2020/03/24/bit/","excerpt":"我这里总结了几道位运算的题目分享给大家,分别是 136和137, 260 和 645, 总共加起来四道题。 四道题全部都是位运算的套路,如果你想练习位运算的话,不要错过哦~~","text":"我这里总结了几道位运算的题目分享给大家,分别是 136和137, 260 和 645, 总共加起来四道题。 四道题全部都是位运算的套路,如果你想练习位运算的话,不要错过哦~~ 前菜开始之前我们先了解下异或,后面会用到。 异或的性质 两个数字异或的结果a^b是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是果同一位的数字相同则为 0,不同则为 1 异或的规律 任何数和本身异或则为0 任何数和 0 异或是本身 异或运算满足交换律,即: a ^ b ^ c = a ^ c ^ b OK,我们来看下这三道题吧。 136. 只出现一次的数字1题目大意是除了一个数字出现一次,其他都出现了两次,让我们找到出现一次的数。我们执行一次全员异或即可。 123456class Solution: def singleNumber(self, nums: List[int]) -> int: single_number = 0 for num in nums: single_number ^= num return single_number 复杂度分析 时间复杂度:$O(N)$,其中N为数组长度。 空间复杂度:$O(1)$ 137. 只出现一次的数字2题目大意是除了一个数字出现一次,其他都出现了三次,让我们找到出现一次的数。 灵活运用位运算是本题的关键。 Python3: 1234567891011121314class Solution: def singleNumber(self, nums: List[int]) -> int: res = 0 for i in range(32): cnt = 0 # 记录当前 bit 有多少个1 bit = 1 << i # 记录当前要操作的 bit for num in nums: if num & bit != 0: cnt += 1 if cnt % 3 != 0: # 不等于0说明唯一出现的数字在这个 bit 上是1 res |= bit return res - 2 ** 32 if res > 2 ** 31 - 1 else res 为什么Python最后需要对返回值进行判断? 如果不这么做的话测试用例是[-2,-2,1,1,-3,1,-3,-3,-4,-2] 的时候,就会输出 4294967292。 其原因在于Python是动态类型语言,在这种情况下其会将符号位置的1看成了值,而不是当作符号“负数”。 这是不对的。 正确答案应该是 - 4,-4的二进制码是 1111…100,就变成 2^32-4=4294967292,解决办法就是 减去 2 ** 32 。 之所以这样不会有问题的原因还在于题目限定的数组范围不会超过 2 ** 32 JavaScript: 123456789101112var singleNumber = function(nums) { let res = 0; for (let i = 0; i < 32; i++) { let cnt = 0; let bit = 1 << i; for (let j = 0; j < nums.length; j++) { if (nums[j] & bit) cnt++; } if (cnt % 3 != 0) res = res | bit; } return res;}; 复杂度分析 时间复杂度:$O(N)$,其中N为数组长度。 空间复杂度:$O(1)$ 645. 错误的集合和上面的137. 只出现一次的数字2思路一样。这题没有限制空间复杂度,因此直接hashmap 存储一下没问题。 不多说了,我们来看一种空间复杂度$O(1)$的解法。 由于和137. 只出现一次的数字2思路基本一样,我直接复用了代码。具体思路是,将nums的所有索引提取出一个数组idx,那么由idx和nums组成的数组构成singleNumbers的输入,其输出是唯二不同的两个数。 但是我们不知道哪个是缺失的,哪个是重复的,因此我们需要重新进行一次遍历,判断出哪个是缺失的,哪个是重复的。 123456789101112131415161718192021222324252627282930class Solution: def singleNumbers(self, nums: List[int]) -> List[int]: ret = 0 # 所有数字异或的结果 a = 0 b = 0 for n in nums: ret ^= n # 找到第一位不是0的 h = 1 while(ret & h == 0): h <<= 1 for n in nums: # 根据该位是否为0将其分为两组 if (h & n == 0): a ^= n else: b ^= n return [a, b] def findErrorNums(self, nums: List[int]) -> List[int]: nums = [0] + nums idx = [] for i in range(len(nums)): idx.append(i) a, b = self.singleNumbers(nums + idx) for num in nums: if a == num: return [a, b] return [b, a] 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(1)$ 260. 只出现一次的数字3题目大意是除了两个数字出现一次,其他都出现了两次,让我们找到这个两个数。 我们进行一次全员异或操作,得到的结果就是那两个只出现一次的不同的数字的异或结果。 我们刚才讲了异或的规律中有一个任何数和本身异或则为0, 因此我们的思路是能不能将这两个不同的数字分成两组 A 和 B。分组需要满足两个条件. 两个独特的的数字分成不同组 相同的数字分成相同组 这样每一组的数据进行异或即可得到那两个数字。 问题的关键点是我们怎么进行分组呢? 由于异或的性质是,同一位相同则为 0,不同则为 1. 我们将所有数字异或的结果一定不是 0,也就是说至少有一位是 1. 我们随便取一个, 分组的依据就来了, 就是你取的那一位是 0 分成 1 组,那一位是 1 的分成一组。这样肯定能保证2. 相同的数字分成相同组, 不同的数字会被分成不同组么。 很明显当然可以, 因此我们选择是 1,也就是说两个独特的的数字在那一位一定是不同的,因此两个独特元素一定会被分成不同组。 12345678910111213141516171819class Solution: def singleNumbers(self, nums: List[int]) -> List[int]: ret = 0 # 所有数字异或的结果 a = 0 b = 0 for n in nums: ret ^= n # 找到第一位不是0的 h = 1 while(ret & h == 0): h <<= 1 for n in nums: # 根据该位是否为0将其分为两组 if (h & n == 0): a ^= n else: b ^= n return [a, b] 复杂度分析 时间复杂度:$O(N)$,其中N为数组长度。 空间复杂度:$O(1)$ 更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经接近30K star啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解","categories":[],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"位运算","slug":"位运算","permalink":"https://lucifer.ren/blog/tags/位运算/"}]},{"title":"回炉重铸, 91 天见证不一样的自己","slug":"91-algo","date":"2020-03-22T16:00:00.000Z","updated":"2023-01-07T13:56:10.277Z","comments":true,"path":"2020/03/23/91-algo/","link":"","permalink":"https://lucifer.ren/blog/2020/03/23/91-algo/","excerpt":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​","text":"力扣加加,一个努力做西湖区最好的算法题解的团队。就在今天它给大家带来了《91 天学算法》,帮助大家摆脱困境,征服算法。 ​ 初衷为了让想学习的人能够真正学习到东西, 我打算新开一个栏目《91 天学算法》,在 91 天内来帮助那些想要学习算法,提升自己算法能力的同学,帮助大家建立完整的算法知识体系。 群里每天都会有题目,推荐大家讨论当天的题目。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。 活动时间2020-06-01 至 2020-08-30 你能够得到什么? 显著提高你的刷题效率,让你少走弯路 掌握常见面试题的思路和解法 掌握常见套路,了解常见算法的本质,横向对比各种题目 纵向剖析一道题,多种方法不同角度解决同一题目 要求 🈲️ 不允许经常闲聊 🈲️ 不允许发广告,软文(只能发算法相关的技术文章) ✅ 一周至少参与一次打卡 违反上述条件的人员会被强制清退 课程大纲基础篇(30 天) 数组,队列,栈 链表 树与递归 哈希表 双指针 进阶篇(30 天) 堆 前缀树 并查集 跳表 剪枝技巧 RK 和 KMP 高频面试题 … 专题篇(31 天) 二分法 滑动窗口 位运算 背包问题 搜索(BFS,DFS,回溯) 动态规划 分治 贪心 … 游戏规则 每天会根据课程大纲的规划,出一道相关题目。 大家可以在指定的 Github 仓库中打卡(不可以抄作业哦),对于不会做的题目可以在群里提问。 第二天会对前一天的题目进行讲解。 奖励 对于坚持打卡满一个月的同学,可以参加抽奖,奖品包括算法模拟面试,算法相关的图书等 连续打卡七天可以获得补签卡一张哦 冲鸭为了大家的学习体验,防止不相关人员进群,同时为了更好地展开工作,我们决定采用 QQ 群的方式进行,希望大家能够理解哦。 对了,还有一句话,前 50 个进群的小伙伴免费哦 ~,50 名之后的小伙伴采取阶梯收费的形式。 收费标准: 前 50 人免费 51 - 100 收费 5 元 101 - 500 收费 10 元 目前已经满 100 人了。 想要参与的小伙伴加我的 QQ:695694307,发红包拉你进群。","categories":[{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/categories/力扣加加/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/categories/91天学算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"算法提高班","slug":"算法提高班","permalink":"https://lucifer.ren/blog/tags/算法提高班/"},{"name":"91天学算法","slug":"91天学算法","permalink":"https://lucifer.ren/blog/tags/91天学算法/"},{"name":"力扣加加","slug":"力扣加加","permalink":"https://lucifer.ren/blog/tags/力扣加加/"}]},{"title":"一文带你看懂二叉树的序列化","slug":"serialize","date":"2020-03-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.976Z","comments":true,"path":"2020/03/20/serialize/","link":"","permalink":"https://lucifer.ren/blog/2020/03/20/serialize/","excerpt":"我们先来看下什么是序列化,以下定义来自维基百科: 序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 可见,序列化和反序列化在计算机科学中的应用还是非常广泛的。就拿 LeetCode 平台来说,其允许用户输入形如: 1[1,2,3,null,null,4,5] 这样的数据结构来描述一颗树: ([1,2,3,null,null,4,5] 对应的二叉树) 其实序列化和反序列化只是一个概念,不是一种具体的算法,而是很多的算法。并且针对不同的数据结构,算法也会不一样。本文主要讲述的是二叉树的序列化和反序列化。看完本文之后,你就可以放心大胆地去 AC 以下两道题: 449. 序列化和反序列化二叉搜索树(中等) 297. 二叉树的序列化与反序列化(困难)","text":"我们先来看下什么是序列化,以下定义来自维基百科: 序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 可见,序列化和反序列化在计算机科学中的应用还是非常广泛的。就拿 LeetCode 平台来说,其允许用户输入形如: 1[1,2,3,null,null,4,5] 这样的数据结构来描述一颗树: ([1,2,3,null,null,4,5] 对应的二叉树) 其实序列化和反序列化只是一个概念,不是一种具体的算法,而是很多的算法。并且针对不同的数据结构,算法也会不一样。本文主要讲述的是二叉树的序列化和反序列化。看完本文之后,你就可以放心大胆地去 AC 以下两道题: 449. 序列化和反序列化二叉搜索树(中等) 297. 二叉树的序列化与反序列化(困难) 前置知识阅读本文之前,需要你对树的遍历以及 BFS 和 DFS 比较熟悉。如果你还不熟悉,推荐阅读一下相关文章之后再来看。或者我这边也写了一个总结性的文章二叉树的遍历,你也可以看看。 前言我们知道:二叉树的深度优先遍历,根据访问根节点的顺序不同,可以将其分为前序遍历,中序遍历, 后序遍历。即如果先访问根节点就是前序遍历,最后访问根节点就是后序遍历,其它则是中序遍历。而左右节点的相对顺序是不会变的,一定是先左后右。 当然也可以设定为先右后左。 并且知道了三种遍历结果中的任意两种即可还原出原有的树结构。这不就是序列化和反序列化么?如果对这个比较陌生的同学建议看看我之前写的《构造二叉树系列》 有了这样一个前提之后算法就自然而然了。即先对二叉树进行两次不同的遍历,不妨假设按照前序和中序进行两次遍历。然后将两次遍历结果序列化,比如将两次遍历结果以逗号“,” join 成一个字符串。 之后将字符串反序列即可,比如将其以逗号“,” split 成一个数组。 序列化: 1234567891011121314class Solution: def preorder(self, root: TreeNode): if not root: return [] return [str(root.val)] +self. preorder(root.left) + self.preorder(root.right) def inorder(self, root: TreeNode): if not root: return [] return self.inorder(root.left) + [str(root.val)] + self.inorder(root.right) def serialize(self, root): ans = '' ans += ','.join(self.preorder(root)) ans += '$' ans += ','.join(self.inorder(root)) return ans 反序列化: 这里我直接用了力扣 105. 从前序与中序遍历序列构造二叉树 的解法,一行代码都不改。 1234567891011121314151617class Solution: def deserialize(self, data: str): preorder, inorder = data.split('$') if not preorder: return None return self.buildTree(preorder.split(','), inorder.split(',')) def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 实际上inorder 和 preorder 一定是同时为空的,因此你无论判断哪个都行 if not preorder: return None root = TreeNode(preorder[0]) i = inorder.index(root.val) root.left = self.buildTree(preorder[1:i + 1], inorder[:i]) root.right = self.buildTree(preorder[i + 1:], inorder[i+1:]) return root 实际上这个算法是不一定成立的,原因在于树的节点可能存在重复元素。也就是说我前面说的知道了三种遍历结果中的任意两种即可还原出原有的树结构是不对的,严格来说应该是如果树中不存在重复的元素,那么知道了三种遍历结果中的任意两种即可还原出原有的树结构。 聪明的你应该发现了,上面我的代码用了 i = inorder.index(root.val),如果存在重复元素,那么得到的索引 i 就可能不是准确的。但是,如果题目限定了没有重复元素则可以用这种算法。但是现实中不出现重复元素不太现实,因此需要考虑其他方法。那究竟是什么样的方法呢? 接下来进入正题。 DFS序列化我们来模仿一下力扣的记法。 比如:[1,2,3,null,null,4,5](本质上是 BFS 层次遍历),对应的树如下: 选择这种记法,而不是 DFS 的记法的原因是看起来比较直观。并不代表我们这里是要讲 BFS 的序列化和反序列化。 序列化的代码非常简单, 我们只需要在普通的遍历基础上,增加对空节点的输出即可(普通的遍历是不处理空节点的)。 比如我们都树进行一次前序遍历的同时增加空节点的处理。选择前序遍历的原因是容易知道根节点的位置,并且代码好写,不信你可以试试。 因此序列化就仅仅是普通的 DFS 而已,直接给大家看看代码。 Python 代码: 123456789101112class Codec: def serialize_dfs(self, root, ans): # 空节点也需要序列化,否则无法唯一确定一棵树,后不赘述。 if not root: return ans + '#,' # 节点之间通过逗号(,)分割 ans += str(root.val) + ',' ans = self.serialize_dfs(root.left, ans) ans = self.serialize_dfs(root.right, ans) return ans def serialize(self, root): # 由于最后会添加一个额外的逗号,因此需要去除最后一个字符,后不赘述。 return self.serialize_dfs(root, '')[:-1] Java 代码: 12345678910111213141516public class Codec { public String serialize_dfs(TreeNode root, String str) { if (root == null) { str += \"None,\"; } else { str += str.valueOf(root.val) + \",\"; str = serialize_dfs(root.left, str); str = serialize_dfs(root.right, str); } return str; } public String serialize(TreeNode root) { return serialize_dfs(root, \"\"); }} [1,2,3,null,null,4,5] 会被处理为1,2,#,#,3,4,#,#,5,#,# 我们先看一个短视频: (动画来自力扣) 反序列化反序列化的第一步就是将其展开。以上面的例子来说,则会变成数组:[1,2,#,#,3,4,#,#,5,#,#],然后我们同样执行一次前序遍历,每次处理一个元素,重建即可。由于我们采用的前序遍历,因此第一个是根元素,下一个是其左子节点,下下一个是其右子节点。 Python 代码: 1234567891011121314def deserialize_dfs(self, nodes): if nodes: if nodes[0] == '#': nodes.pop(0) return None root = TreeNode(nodes.pop(0)) root.left = self.deserialize_dfs(nodes) root.right = self.deserialize_dfs(nodes) return root return Nonedef deserialize(self, data: str): nodes = data.split(',') return self.deserialize_dfs(nodes) Java 代码: 12345678910111213141516171819public TreeNode deserialize_dfs(List<String> l) { if (l.get(0).equals(\"None\")) { l.remove(0); return null; } TreeNode root = new TreeNode(Integer.valueOf(l.get(0))); l.remove(0); root.left = deserialize_dfs(l); root.right = deserialize_dfs(l); return root;}public TreeNode deserialize(String data) { String[] data_array = data.split(\",\"); List<String> data_list = new LinkedList<String>(Arrays.asList(data_array)); return deserialize_dfs(data_list);} 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:空间复杂度取决于栈深度,因此空间复杂度为 $O(h)$,其中 $h$ 为树的深度。 BFS序列化实际上我们也可以使用 BFS 的方式来表示一棵树。在这一点上其实就和力扣的记法是一致的了。 我们知道层次遍历的时候实际上是有层次的。只不过有的题目需要你记录每一个节点的层次信息,有些则不需要。 这其实就是一个朴实无华的 BFS,唯一不同则是增加了空节点。 Python 代码: 1234567891011121314class Codec: def serialize(self, root): ans = '' queue = [root] while queue: node = queue.pop(0) if node: ans += str(node.val) + ',' queue.append(node.left) queue.append(node.right) else: ans += '#,' return ans[:-1] 反序列化如图有这样一棵树: 那么其层次遍历为 [1,2,3,#,#, 4, 5]。我们根据此层次遍历的结果来看下如何还原二叉树,如下是我画的一个示意图: 容易看出: level x 的节点一定指向 level x + 1 的节点,如何找到 level + 1 呢? 这很容易通过层次遍历来做到。 对于给的的 level x,从左到右依次对应 level x + 1 的节点,即第 1 个节点的左右子节点对应下一层的第 1 个和第 2 个节点,第 2 个节点的左右子节点对应下一层的第 3 个和第 4 个节点。。。 接上,其实如果你仔细观察的话,实际上 level x 和 level x + 1 的判断是无需特别判断的。我们可以把思路逆转过来:即第 1 个节点的左右子节点对应第 1 个和第 2 个节点,第 2 个节点的左右子节点对应第 3 个和第 4 个节点。。。(注意,没了下一层三个字) 因此我们的思路也是同样的 BFS,并依次连接左右节点。 Python 代码: 123456789101112131415161718192021222324252627282930def deserialize(self, data: str): if data == '#': return None # 数据准备 nodes = data.split(',') if not nodes: return None # BFS root = TreeNode(nodes[0]) queue = [root] # 已经有 root 了,因此从 1 开始 i = 1 while i < len(nodes) - 1: node = queue.pop(0) # lv = nodes[i] rv = nodes[i + 1] i += 2 # 对于给的的 level x,从左到右依次对应 level x + 1 的节点 # node 是 level x 的节点,l 和 r 则是 level x + 1 的节点 if lv != '#': l = TreeNode(lv) node.left = l queue.append(l) if rv != '#': r = TreeNode(rv) node.right = r queue.append(r) return root 复杂度分析 时间复杂度:每个节点都会被处理一次,因此时间复杂度为 $O(N)$,其中 $N$ 为节点的总数。 空间复杂度:$O(N)$,其中 $N$ 为节点的总数。 总结除了这种方法还有很多方案, 比如括号表示法。 关于这个可以参考力扣606. 根据二叉树创建字符串,这里就不再赘述了。 本文从 BFS 和 DFS 角度来思考如何序列化和反序列化一棵树。 如果用 BFS 来序列化,那么相应地也需要 BFS 来反序列化。如果用 DFS 来序列化,那么就需要用 DFS 来反序列化。 我们从马后炮的角度来说,实际上对于序列化来说,BFS 和 DFS 都比较常规。对于反序列化,大家可以像我这样举个例子,画一个图。可以先在纸上,电脑上,如果你熟悉了之后,也可以画在脑子里。 (Like This) 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"算法,序列化","slug":"算法,序列化","permalink":"https://lucifer.ren/blog/categories/算法,序列化/"},{"name":"数据结构,二叉树","slug":"数据结构,二叉树","permalink":"https://lucifer.ren/blog/categories/数据结构,二叉树/"}],"tags":[{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/tags/二叉树/"},{"name":"序列化","slug":"序列化","permalink":"https://lucifer.ren/blog/tags/序列化/"}]},{"title":"纪念LeetCode项目Star突破2W","slug":"thanksGaving-2","date":"2020-03-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.415Z","comments":true,"path":"2020/03/20/thanksGaving-2/","link":"","permalink":"https://lucifer.ren/blog/2020/03/20/thanksGaving-2/","excerpt":"假期这几天我买了《逆转裁判 123》合集,面对着这香喷喷的冷饭吃了半天。从 GBA 玩到 NDS,从 NDS 玩到 3DS, 现在 NS 虽然没有出新作有点遗憾。不过有了高清重制,也当是个回忆和收藏了 🎉🎉","text":"假期这几天我买了《逆转裁判 123》合集,面对着这香喷喷的冷饭吃了半天。从 GBA 玩到 NDS,从 NDS 玩到 3DS, 现在 NS 虽然没有出新作有点遗憾。不过有了高清重制,也当是个回忆和收藏了 🎉🎉 目前打通了第一第二关,剩下的过一段时间再玩好啦 😁 回到正题,就在今天,我的《leetcode 题解》项目成功突破 2w star, 并且现在 Github 搜索关键字”LeetCode”我的项目已经排名第一啦,这是继 1W star 之后的第二个巨大突破,非常感谢大家一路以来的支持和陪伴。 最近在写一本关于 LeetCode 题解的书,有很多人表示想买,这无形之中给了我很大的压力,名字还没定,暂时给它取一个代号《攻克 LeetCode》。 新书《攻克 LeetCode》 这里是《攻克 LeetCode》的草稿目录,目前有 20 章的内容,本书要讲的内容就是 LeetCode 上反复出现的算法,经过我进一步提炼,抽取数百道题目在这里进行讲解,帮助大家理清整体思绪,从而高效率地刷题,做到事半功倍。我这里总结了 7 个常见的数据结构和 7 个常见的算法以及 5 个常见的算法思想。 7 个数据结构分别是: 数组,栈,队列,链表,二叉树,散列表,图 7 个算法分别是:二分法,递归,回溯法,排序,双指针,滑动窗口,并查集 5 个算法思想分别是:分治,贪心,深度优先遍历,广度优先遍历,动态规划 只有掌握了这些基础的数据结构和算法,更为复杂的算法才能得心应手,事半功倍。而 LeetCode 的题目虽然不断出新,但是最终用到的算法永远是那几个,很多题目都是穿着新的衣服的老面孔了。大家学好这些基础套路之后会更加明白这个道理。 后期可能会有大幅度修改,希望大家提出宝贵意见,以特别的方式参与到这本书的编写中来。 2W star 截图 Star 曲线 (star 增长曲线图) 知乎引流上次主要讲了项目从开始建立到拥有 1W star 的经历,本次书接前文,继续讲一下后面的故事。 上回提到知乎上的“量子位”在帮我做宣传,引入了不小的流量。 我就想为什么不自己去拉流量呢?我自己以作者的角度去回答一些问题岂不是更好,更受欢迎么?于是我就开始在知乎上回答问题,很开心其中一个还获得了专业认可。 事实上并没有我想的那么好,我回答了两个 LeetCode 话题的内容,虽然也有几百的点赞和感谢,但是这离我的目标还差很远。 但是转念一想,我知乎刚起步,也没什么粉丝,并且写答案的时间也就一个月左右,这样想就好多了。 我相信将来会有更多的人看到我的答案,然后加入进来。 建立自己的博客现在我发表的文章都是在各大平台。这有一个很大的问题就是各个平台很难满足你的需求,比如按照标签,按照日期进行归档。 甚至很多平台的阅读体验很差,比如没有导航功能,广告太多等。因此我觉得自己搭建一个博客还是很有必要的,这个渠道也为我吸引了少部分的流量,目前添加的主要内容大概有: 总体上来说效果还是不错的,之后的文章会在博客首发,各个平台也会陆续更新,感兴趣的可以来个 RSS 订阅,订阅方式已经在《每日一荐 - 九月刊》里面介绍了。 GithubDaily 的 推荐GithubDaily 转载了量子位的文章也为我的仓库涨了至少几百的 star,非常感谢。GithubDaily 是一个拥有 3W 多读者的公众号,大家有兴趣的可以关注一波。 其他自媒体的推荐一些其他自媒体也会帮忙推广我的项目 口耳相传我后来才知道竟然有海外华侨和一些华人社区都能看到我了。 (一亩三分地是一个集中讨论美国加拿大留学的论坛) 另外通过朋友之间口耳相传的介绍也变得越来越多。 非常感谢大家一直以来的陪伴和支持,我们一起努力,加油 💪。 如果你还没有加入我们,看了这篇文章想加入,那么可以访问我的项目主页 leetcode 题解我在这里等着你。","categories":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/categories/日记/"},{"name":"技术","slug":"日记/技术","permalink":"https://lucifer.ren/blog/categories/日记/技术/"}],"tags":[{"name":"日记","slug":"日记","permalink":"https://lucifer.ren/blog/tags/日记/"}]},{"title":"一文带你 AC 十道题【滑动窗口】","slug":"slide-window","date":"2020-03-15T16:00:00.000Z","updated":"2023-01-05T12:24:49.788Z","comments":true,"path":"2020/03/16/slide-window/","link":"","permalink":"https://lucifer.ren/blog/2020/03/16/slide-window/","excerpt":"笔者最早接触滑动窗口是滑动窗口协议,滑动窗口协议(Sliding Window Protocol),属于 TCP 协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。 发送方和接收方分别有一个窗口大小 w1 和 w2。窗口大小可能会根据网络流量的变化而有所不同,但是在更简单的实现中它们是固定的。窗口大小必须大于零才能进行任何操作。 我们算法中的滑动窗口也是类似,只不过包括的情况更加广泛。实际上上面的滑动窗口在某一个时刻就是固定窗口大小的滑动窗口,随着网络流量等因素改变窗口大小也会随着改变。接下来我们讲下算法中的滑动窗口。","text":"笔者最早接触滑动窗口是滑动窗口协议,滑动窗口协议(Sliding Window Protocol),属于 TCP 协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。 发送方和接收方分别有一个窗口大小 w1 和 w2。窗口大小可能会根据网络流量的变化而有所不同,但是在更简单的实现中它们是固定的。窗口大小必须大于零才能进行任何操作。 我们算法中的滑动窗口也是类似,只不过包括的情况更加广泛。实际上上面的滑动窗口在某一个时刻就是固定窗口大小的滑动窗口,随着网络流量等因素改变窗口大小也会随着改变。接下来我们讲下算法中的滑动窗口。 介绍滑动窗口是一种解决问题的思路和方法,通常用来解决一些连续问题。 比如 LeetCode 的 209. 长度最小的子数组。更多滑动窗口题目见下方题目列表。 常见套路滑动窗口主要用来处理连续问题。比如题目求解“连续子串 xxxx”,“连续子数组 xxxx”,就应该可以想到滑动窗口。能不能解决另说,但是这种敏感性还是要有的。 从类型上说主要有: 固定窗口大小 窗口大小不固定,求解最大的满足条件的窗口 窗口大小不固定,求解最小的满足条件的窗口(上面的 209 题就属于这种) 后面两种我们统称为可变窗口。当然不管是哪种类型基本的思路都是一样的,不一样的仅仅是代码细节。 固定窗口大小对于固定窗口,我们只需要固定初始化左右指针 l 和 r,分别表示的窗口的左右顶点,并且保证: l 初始化为 0 初始化 r,使得 r - l + 1 等于窗口大小 同时移动 l 和 r 判断窗口内的连续元素是否满足题目限定的条件 4.1 如果满足,再判断是否需要更新最优解,如果需要则更新最优解 4.2 如果不满足,则继续。 可变窗口大小对于可变窗口,我们同样固定初始化左右指针 l 和 r,分别表示的窗口的左右顶点。后面有所不同,我们需要保证: l 和 r 都初始化为 0 r 指针移动一步 判断窗口内的连续元素是否满足题目限定的条件 4.1 如果满足,再判断是否需要更新最优解,如果需要则更新最优解。并尝试通过移动 l 指针缩小窗口大小。循环执行 4.1 4.2 如果不满足,则继续。 形象地来看的话,就是 r 指针不停向右移动,l 指针仅仅在窗口满足条件之后才会移动,起到窗口收缩的效果。 模板代码以下是 209 题目的代码,使用 Python 编写,大家意会即可。 1234567891011class Solution: def minSubArrayLen(self, s: int, nums: List[int]) -> int: l = total = 0 ans = len(nums) + 1 for r in range(len(nums)): total += nums[r] while total >= s: ans = min(ans, r - l + 1) total -= nums[l] l += 1 return 0 if ans == len(nums) + 1 else ans 题目列表以下题目有的信息比较直接,有的题目信息比较隐蔽,需要自己发掘 【Python,JavaScript】滑动窗口(3. 无重复字符的最长子串) 76. 最小覆盖子串 209. 长度最小的子数组 【Python】滑动窗口(438. 找到字符串中所有字母异位词) 【904. 水果成篮】(Python3) 【930. 和相同的二元子数组】(Java,Python) 【992. K 个不同整数的子数组】滑动窗口(Python) 【1004. 最大连续 1 的个数 III】滑动窗口(Python3) 【1234. 替换子串得到平衡字符串】[Java/C++/Python] Sliding Window 【1248. 统计「优美子数组」】滑动窗口(Python) 扩展阅读 LeetCode Sliding Window Series Discussion","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"算法,滑动窗口","slug":"算法,滑动窗口","permalink":"https://lucifer.ren/blog/categories/算法,滑动窗口/"},{"name":"数据结构,数组","slug":"数据结构,数组","permalink":"https://lucifer.ren/blog/categories/数据结构,数组/"},{"name":"数据结构,字符串","slug":"数据结构,字符串","permalink":"https://lucifer.ren/blog/categories/数据结构,字符串/"}],"tags":[{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"}]},{"title":"【LeetCode 日记】 84. 柱状图中最大的矩形","slug":"84.largest-rectangle-in-histogram","date":"2020-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.542Z","comments":true,"path":"2020/03/04/84.largest-rectangle-in-histogram/","link":"","permalink":"https://lucifer.ren/blog/2020/03/04/84.largest-rectangle-in-histogram/","excerpt":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​","text":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​ 原题地址: https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ 题目描述`给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。 示例: 输入:[2,1,5,6,2,3]输出:10 暴力枚举 - 左右端点法(TLE)思路我们暴力尝试所有可能的矩形。由于矩阵是二维图形, 我我们可以使用左右两个端点来唯一确认一个矩阵。因此我们使用双层循环枚举所有的可能性即可。 而矩形的面积等于(右端点坐标 - 左端点坐标 + 1) * 最小的高度,最小的高度我们可以在遍历的时候顺便求出。 代码1234567891011class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, ans = len(heights), 0 if n != 0: ans = heights[0] for i in range(n): height = heights[i] for j in range(i, n): height = min(height, heights[j]) ans = max(ans, (j - i + 1) * height) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(1)$ 暴力枚举 - 中心扩展法(TLE)思路我们仍然暴力尝试所有可能的矩形。只不过我们这一次从中心向两边进行扩展。对于每一个 i,我们计算出其左边第一个高度小于它的索引 p,同样地,计算出右边第一个高度小于它的索引 q。那么以 i 为最低点能够构成的面积就是(q - p - 1) * heights[i]。 这种算法毫无疑问也是正确的。 我们证明一下,假设 f(i) 表示求以 i 为最低点的情况下,所能形成的最大矩阵面积。那么原问题转化为max(f(0), f(1), f(2), ..., f(n - 1))。 具体算法如下: 我们使用 l 和 r 数组。l[i] 表示 左边第一个高度小于它的索引,r[i] 表示 右边第一个高度小于它的索引。 我们从前往后求出 l,再从后往前计算出 r。 再次遍历求出所有的可能面积,并取出最大的。 代码1234567891011121314151617class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j -= 1 l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j += 1 r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N^2)$ 空间复杂度:$O(N)$ 优化中心扩展法(Accepted)思路实际上我们内层循环没必要一步一步移动,我们可以直接将j -= 1 改成 j = l[j], j += 1 改成 j = r[j]。 代码123456789101112131415161718class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n = len(heights) l, r, ans = [-1] * n, [n] * n, 0 for i in range(1, n): j = i - 1 while j >= 0 and heights[j] >= heights[i]: j = l[j] l[i] = j for i in range(n - 2, -1, -1): j = i + 1 while j < n and heights[j] >= heights[i]: j = r[j] r[i] = j for i in range(n): ans = max(ans, heights[i] * (r[i] - l[i] - 1)) return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 单调栈(Accepted)思路实际上,读完第二种方法的时候,你应该注意到了。我们的核心是求左边第一个比 i 小的和右边第一个比 i 小的。 如果你熟悉单调栈的话,那么应该会想到这是非常适合使用单调栈来处理的场景。 为了简单起见,我在 heights 首尾添加了两个哨兵元素,这样可以减少边界处理的额外代码。 代码12345678class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans 复杂度分析 时间复杂度:$O(N)$ 空间复杂度:$O(N)$ 欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构,单调栈","slug":"数据结构,单调栈","permalink":"https://lucifer.ren/blog/categories/数据结构,单调栈/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/categories/Hard/"}],"tags":[{"name":"数据结构,算法,LeetCode 日记,Hard","slug":"数据结构,算法,LeetCode-日记,Hard","permalink":"https://lucifer.ren/blog/tags/数据结构,算法,LeetCode-日记,Hard/"}]},{"title":"【LeetCode 日记】85. 最大矩形","slug":"85.maximal-rectangle","date":"2020-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.911Z","comments":true,"path":"2020/03/04/85.maximal-rectangle/","link":"","permalink":"https://lucifer.ren/blog/2020/03/04/85.maximal-rectangle/","excerpt":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​","text":"这是一道 Hard 难度的题目,本题的解法很多,让我们来看一下。 ​ 原题地址: https://leetcode-cn.com/problems/maximal-rectangle/ 题目描述给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 示例: 输入: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 输出:6 思路我在 【84. 柱状图中最大的矩形】多种方法(Python3) 使用了多种方法来解决。 然而在这道题,我们仍然可以使用完全一样的思路去完成。 不熟悉的可以看下我的题解。本题解是基于那道题的题解来进行的。 拿题目给的例子来说: 123456[ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 我们逐行扫描得到 84. 柱状图中最大的矩形 中的 heights 数组: 这样我们就可以使用84. 柱状图中最大的矩形 中的解法来进行了,这里我们使用单调栈来解。 代码1234567891011121314151617181920212223class Solution: def largestRectangleArea(self, heights: List[int]) -> int: n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 for i in range(n + 2): while st and heights[st[-1]] > heights[i]: ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) st.append(i) return ans def maximalRectangle(self, matrix: List[List[str]]) -> int: m = len(matrix) if m == 0: return 0 n = len(matrix[0]) heights = [0] * n ans = 0 for i in range(m): for j in range(n): if matrix[i][j] == \"0\": heights[j] = 0 else: heights[j] += 1 ans = max(ans, self.largestRectangleArea(heights)) return ans 复杂度分析 时间复杂度:$O(M * N)$ 空间复杂度:$O(N)$ 欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构,单调栈","slug":"数据结构,单调栈","permalink":"https://lucifer.ren/blog/categories/数据结构,单调栈/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/categories/Hard/"}],"tags":[{"name":"数据结构,算法,LeetCode 日记,Hard","slug":"数据结构,算法,LeetCode-日记,Hard","permalink":"https://lucifer.ren/blog/tags/数据结构,算法,LeetCode-日记,Hard/"}]},{"title":"每日一荐 2020-03 汇总","slug":"daily-featured-2020-03","date":"2020-03-01T16:00:00.000Z","updated":"2023-01-07T14:01:24.669Z","comments":true,"path":"2020/03/02/daily-featured-2020-03/","link":"","permalink":"https://lucifer.ren/blog/2020/03/02/daily-featured-2020-03/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2020-032020-03-31[好文]gRPC 使用 protobuf 进行数据封装(序列化和反序列化),同时使用 http2 进行数据传输,为什么不直接基于 TCP 传输呢?grpc 究竟和其他 rpc 框架,比如阿里的 dubbo,facebook 的 Thrift 有什么区别?这篇文章带你了解一下。 地址:https://mp.weixin.qq.com/s/GuMp4Z8oJv9K_MJxMptsSA 2020-03-30[好文]《吊打面试官》系列 Node.js 全栈秒杀系统。这篇文章非常详细地讲述了如何使用 nodejs 构建一个秒杀系统,文中提到的知识点,我也经常在面试中向候选人提问。 https://mp.weixin.qq.com/s/LoRr76smB-M8sNp-85wdqg 2020-03-27[库]今天给大家推荐的是一个图片上传组件 - uppload。支持: 20 多种选择文件的方式 10 种编辑文件的方式 支持自定义将文件发送到服务端 主题 插件 。。。 更重要的是其源码写的很赞,模块划分,代码解耦,以及单元测试都非常值得学习, 感兴趣的可以研究一下。 https://github.com/elninotech/uppload 2020-03-26[好文]一个外国游客来中国广州游玩,定了一家酒店,但是通过 Google 地图去找,离目的地相差几英里,原因在于 Google 使用的地图坐标系统是 WGS-84 ,而国内的比如 Baidu 地图可以很好的显示,因为其用的是 GCJ-02。一句话总结来说: 原文地址:https://abstractkitchen.com/blog/a-short-guide-to-chinese-coordinate-system/ 2020-03-25[工具]微软开源的 Puppeteer 的衍生项目 Playwirght,或许能够替代 Puppeteer。 和 Puppeteer 相比,其有以下特点: 弥补了 Puppeteer 的平台局限性,为所有热门渲染引擎提供类似的功能 和 Puppeteer 基本兼容,用户可以无痛(低痛)迁移 使用了隔离的 BrowserContext,而不是像 Puppeteer 一样共用一个 defaultBrowserContext。 项目地址: https://github.com/microsoft/playwright 2020-03-24[工具]如果你是学生党或者学术党,需要经常查找文献,那么一个文献管理工具就显得很有必要。这里推荐一个工具:Zotero。 地址: https://www.zotero.org/ 2020-03-23[好文]Elasticsearch 已经火了很多年了,现在依然可以见到他们活跃的身影。笔者公司就在用,我也参与了相关开发,其使用起来很简单,但是精通起来却不容易。而很多人正好对其不熟悉,这里正好有一个非常简单易懂的中文教程:《Elasticsearch 学习:入门篇》 地址: https://www.cyhone.com/articles/introduction-of-elasticsearch/index.html 2020-03-20[工具]这是一个在线服务,用来生成几何占位符,类似于 Github 的默认头像。 (Github 的默认头像) 使用方式也很简单,并支持多种参数: 123456789<img src=\"https://generative-placeholders.glitch.me/image?width=600&height=300&img=01\"/><img src=\"https://generative-placeholders.glitch.me/image?width=600&height=300&img=02\"/><img src=\"https://generative-placeholders.glitch.me/image?width=600&height=300&img=03\"/> 地址: https://generative-placeholders.glitch.me/ 2020-03-19[好文]政采云的前端 leader(花名堂主) 的一个关于前端基建的分享《堂主 - 如何推动前端团队的基础设施建设 | 7500 字》。如果你的团队也在做基础建设,那么或许可以帮到你,至少可以提供一些思路。 地址: https://mp.weixin.qq.com/s/2VSa3xBpy5St8G1v0RjW9g 2020-03-14[仓库]这里有一个很有意思的仓库,专门用来做远程面试,支持白板代码,视频通话,回放等功能。效果类似之前我在使用的 showmebug。 地址: https://github.com/AgoraIO-Community/2019-Hackathon-Works-CoderLane/blob/master/README.ZH.md 2020-03-13[仓库]一个可以在浏览器端压缩图片的库,从而减少网络传输,进而减小服务端的压力。 地址: https://github.com/Donaldcwl/browser-image-compression 2020-03-12[仓库]一个获取本机网卡信息的库,可以获取到 IPv4,IPv6 以及 MAC 地址。 地址: https://github.com/scravy/node-macaddress 2020-03-11[仓库]著名的知识管理平台《羽雀》就是从最开是的 CodeMirror 迁移到了 slate,slate 其实就是一个 Markdown 编辑器。 但是羽雀最终还是转向自研道路,基于浏览器的 contenteditable 实现富文本编辑器,通过 canvas 实现表格编辑器,通过 SVG 实现思维导图编辑器。 地址:https://github.com/slatedocs/slate 2020-03-10[仓库]一个可以制作类似“Github Project”效果的库。 地址: https://github.com/lourenci/react-kanban 2020-03-09[网站]之前给大家推荐了几个在线练习的网站,有算法的,有正则的,还有 git 的。今天介绍一个练习 SQL 语句的: SQLZOO 是一款很好用的 SQL 练习网站,这里都是比较常用的 SQL 命令。不仅对每个命令的用法有详细解释,每个专题后面还有题目。循序渐进,LeetCode 也有 SQL 相关的题目,不过难度一般比较大,建议大家 把 SQLZOO 刷完基础 SQL 命令再去 LeetCode 刷 SQL 题目。 网站地址:https://sqlzoo.net/ 2020-03-05[好文]Base64 编/解码器有不同实现,有的不相互兼容,如果使用了不兼容的实现,就会有 bug,比如典型的报错“Illegal base64 character a”。本文详细介绍了产生这个问题的原因,文章通俗易懂,适合新手阅读。 记一个 Base64 有关的 Bug 2020-03-03[好文]前端新建一个项目的时候,需要用到很多配置文件,通常是以。开头,因此我们也叫 dotfiles。这篇文章介绍了前端开发常见的 dotfiles,以及其简单用法,或许可以给你一点参考。而且我在我的 mac 装机教程 中也提到了 dotfiles,只不过那边的 dotfiles 更为广泛。 文章地址: https://lyreal666.com/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E9%85%8D%E7%BD%AE-react-typescript%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9Adotfiles/ 2020-03-02[好文]原文标题《使用 TypeScript 开发 Web 应用的最佳实践》。文中基本将 TS 在日常开发中的姿势都提到了,并且总结了很多坑点,并且给出了自己的探索和思考。 文章地址:https://yrq110.me/post/front-end/best-practice-of-typescript-in-webapp-developing/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2020-03","slug":"每日一荐/2020-03","permalink":"https://lucifer.ren/blog/categories/每日一荐/2020-03/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"使用web-component搭建企业级组件库","slug":"web-components-enterprize","date":"2020-02-21T16:00:00.000Z","updated":"2023-01-05T12:24:49.573Z","comments":true,"path":"2020/02/22/web-components-enterprize/","link":"","permalink":"https://lucifer.ren/blog/2020/02/22/web-components-enterprize/","excerpt":"前端目前比较主流的框架有 react,vuejs,angular 等。 我们通常去搭建组件库的时候都是基于某一种框架去搭建,比如 ant-design 是基于 react 搭建的 UI 组件库,而 elementUI 则是基于 vuejs 搭建的组件库。 虽然目前社区有相关工具,提供框架之间的转化服务,比如讲 vuejs 组件转化为 react 组件。但是毕竟是不同的框架,有不同的标准。因此框架 api 发生变动,那么你就需要重写转化逻辑,显然是不灵活的,因此我们暂不讨论这种情况。作为公司而言,就需要为不同的框架写不同的组件库,尽管逻辑都是一样的。 另外如果框架升级,比如从 1.x 升级到 2.x,那么对应组件库就需要升级,如果公司的组件库有很多(vuejs,react,angular 等),那么这种升级的概率就会更大。","text":"前端目前比较主流的框架有 react,vuejs,angular 等。 我们通常去搭建组件库的时候都是基于某一种框架去搭建,比如 ant-design 是基于 react 搭建的 UI 组件库,而 elementUI 则是基于 vuejs 搭建的组件库。 虽然目前社区有相关工具,提供框架之间的转化服务,比如讲 vuejs 组件转化为 react 组件。但是毕竟是不同的框架,有不同的标准。因此框架 api 发生变动,那么你就需要重写转化逻辑,显然是不灵活的,因此我们暂不讨论这种情况。作为公司而言,就需要为不同的框架写不同的组件库,尽管逻辑都是一样的。 另外如果框架升级,比如从 1.x 升级到 2.x,那么对应组件库就需要升级,如果公司的组件库有很多(vuejs,react,angular 等),那么这种升级的概率就会更大。 什么是 web-component?那么有没有更好的方案,一次编写,到处使用呢? 答案就是借助 web component。 Web Components 是一系列加入w3c的 HTML 和 DOM 的特性,使得开发者可以创建可复用的组件。 由于 web components 是由 w3c 组织去推动的,因此它很有可能在不久的将来成为浏览器的一个标配。 Web Components 主要由以下四个部分组成: Custom Elements – 定义新 html 元素的 api Shadow DOM – Encapsulated DOM and styling, with composition HTML Imports – Declarative methods of importing HTML documents into other documents HTML Templates – The <template> element, which allows documents to contain inert chunks of DOM web-component 有什么优点使用 web components 搭建组件库能够带来什么好处呢?前面说了,web components 是 w3c 推动的一系列的规范,它是一个标准。 如果我们使用 web components 的 api 开发一个组件,这个组件是脱离框架存在的,也就是说你可以在任何框架中使用它,当然也可以直接在原生 js 中使用。 我们无须为不同的框架编写不同的组件库。 使用 web components 编写的组件库的基本使用方法大概是这样的: 1234<script src=\"/build/duiba.js\"></script><!-- 运营位组件 --><operation-list></operation-list> 毫不夸张地说, web components 就是未来。 但是 web components 的 api 还是相对复杂的,因此用原生的 api 开发 web components 还是相对比较复杂的,就好像你直接用原生 canvas api 去开发游戏一样。 下面我们介绍下用于简化 web components 开发的库。 polymerpolymer 是我接触的第一个 web componment 开发库,那已经是很多年前的往事了。 Build modern apps using web components 更多介绍polymer stencilstencil 是在 polymer 之后出现的一个库。第一次接触时在 Polymer Summit 2017 的分享上,这里贴下地址Using Web Components in Ionic - Polymer Summit 2017。 Stencil is a tool developers use to create Web Components with some powerful features baked in, but it gets out of the way at runtime. 那么 powerful features 具体指的是什么? 12345Virtual DOMAsync rendering (inspired by React Fiber)Reactive data-bindingTypeScriptJSX 它也是一个用于生成 web compoennt 的 tool。 不同的是她提供了更多的特性(Reactive data-binding,TypeScript,JSX, virtual dom)以及更强的性能(virtual dom, Async rendering). 细心的人可能已经发现了,我将 Virtual DOM 既归为特性,又归为性能,没错! Virtual DOM 提供了一种到真实 dom 的映射,使得开发者不必关心真实 dom,从这个层面讲它是特性。 从虚拟 dom 之间的 diff,并将 diff info patch 到 real dom(调和)的过程来看,它是性能。 用 stencil 开发 web components 体验大概是这样的: 12345678910111213141516171819202122import { Component, Prop, State } from \"@stencil/core\";@Component({ tag: \"my-component\", styleUrl: \"my-component.scss\",})export class MyComponent { // Indicate that name should be a property on our new component @Prop() first: string; @Prop() last: string; @State() isVisible: boolean = true; render() { return ( <p> Hello, my name is {this.first} {this.last} </p> ); }} Demo这是我基于stenciljs + storybook写的一个小例子。大家可以 clone,并运行查看效果。 duiba-components 通过这样搭建的企业级组件库,就可以轻松地为不同业务线提供基础组件库,而不必担心使用者(各个业务方)的技术栈。 将来业务方的框架升级(比如 vue1 升级到 vue2),我们的组件库照样可以使用。 可以想象,如果 es 标准发展地够好,web components 等规范也足够普及,无框架时代将会到来。 无框架,不代表不使用库。 只需要借助工具库就可以开发足够通用的组件,也不需要 babel 这样的转换器,更不需要各种 polyfill。那么开发者大概会非常幸福吧,可惜这样的日子不可能存在,但是离这个目标足够近也是极好的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"组件化","slug":"前端/组件化","permalink":"https://lucifer.ren/blog/categories/前端/组件化/"},{"name":"web-component","slug":"前端/web-component","permalink":"https://lucifer.ren/blog/categories/前端/web-component/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"组件化","slug":"组件化","permalink":"https://lucifer.ren/blog/tags/组件化/"},{"name":"web-component","slug":"web-component","permalink":"https://lucifer.ren/blog/tags/web-component/"}]},{"title":"文科生都能看懂的循环移位算法","slug":"rotate-list","date":"2020-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:50.093Z","comments":true,"path":"2020/02/20/rotate-list/","link":"","permalink":"https://lucifer.ren/blog/2020/02/20/rotate-list/","excerpt":"循环移位问题真的是一个特别经典的问题了,今天我们就来攻克它。 循环移位的表现形式有很多种,就数据结构来说包括数组,字符串,链表等。就算法来说,有包含问题,直接移动问题,还有查找问题等。 虽然表现形式有很多,但是本质都是一样的,因为从逻辑上来讲其实他们都是线性数据结构,那么让我们来看一下吧。","text":"循环移位问题真的是一个特别经典的问题了,今天我们就来攻克它。 循环移位的表现形式有很多种,就数据结构来说包括数组,字符串,链表等。就算法来说,有包含问题,直接移动问题,还有查找问题等。 虽然表现形式有很多,但是本质都是一样的,因为从逻辑上来讲其实他们都是线性数据结构,那么让我们来看一下吧。 数组循环移位LeetCode 和 编程之美等都有这道题目,题目难度为 Easy。LeeCode 链接 题目描述123456789101112131415161718192021给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。示例 1:输入: [1,2,3,4,5,6,7] 和 k = 3输出: [5,6,7,1,2,3,4]解释:向右旋转 1 步: [7,1,2,3,4,5,6]向右旋转 2 步: [6,7,1,2,3,4,5]向右旋转 3 步: [5,6,7,1,2,3,4]示例 2:输入: [-1,-100,3,99] 和 k = 2输出: [3,99,-1,-100]解释:向右旋转 1 步: [99,-1,-100,3]向右旋转 2 步: [3,99,-1,-100]说明:尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。要求使用空间复杂度为 O(1) 的 原地 算法。 不符合题意的解法如果你拿到这道题没有思路,不要紧张,因为你不是一个人。 让我们先不要管题目的时间和空间复杂度的限制, 来用最最普通的方式实现它,看能不能得出一点思路。 最简单的做法就是新开辟一个完全一样的数组,然后每次移动的时候从 copy 的数组中取即可,由于新开辟的数组不会被改变,因此这种做法可行,我们直接看下代码: 1234567891011function RShift(list, k) { // 空间复杂度O(n) // 时间复杂度O(n) const copy = [...list]; const n = list.length; for (let i = 0; i < n; i++) { list[(k + i) % n] = copy[i]; } return list;} 实际上我们还可以优化一下,如果 k 是 N 的倍数,实际上是不需要做任何移动的,因此直接返回即可。 12345function RShift(list, k) { const n = list.length; if (k % n === 0) return; // 剩下代码} 由于我们需要覆盖原来的数组,那么原来的数组中的数据就会缺失,因此我们最简单的就是开辟一个完全一样的数组,这样就避免了问题,但是这样的空间复杂度是 N。我们有没有办法优化这个过程呢? 而且如果 k 是负数呢? 这其实在考察我们思考问题的严谨性。 除此之外,我们还应该思考: k 的范围是多少?如果很大,我的算法还有效么? n 的范围是多少?如果很大,我的算法还有效么? 上面两个问题的答案都是有效。 因为 k 就算再大,我们只需要求模,求模的值当成新的 k 即可。因此 k 最大不过就是 n。 如果 n 很大,由于我们的算法是 O(N)的复杂度,也就是线性,这个复杂度还是比较理想的。 时间换空间我们来试一下常数空间复杂度的解法,这种做法思路很简单,我们只需要每次移动一位,移动 k 次即可,移动一次的时间复杂度是 1,k 次共用一个变量即可,因此总的空间复杂度可以降低到 1。 我们来看下代码,这次我们把上面提到的 k 为负数的情况考虑进去。 123456789101112131415161718function RShift(list, k) { const n = list.length; if (k % n === 0) return; let cnt = Math.abs(k > 0 ? k % n : n + (k % n)); let t = null; while (cnt--) { t = list[n - 1]; // 右移一位 for (let i = n - 1; i > 0; i--) { list[i] = list[i - 1]; } list[0] = t; } return list;} 虽然上面的解法是常数空间复杂度,但是时间复杂度是 O(N * K),K 取值不限制的话,就是 O(N^2),还是不满足题意。不过没关系,我们继续思考。 我们再来看一种空间换时间的做法,这种做法的思路是拼接一个完全一样的数组到当前数组的尾部,然后问题就转化为截取数组使之满足右移的效果,这样的时间复杂度 O(N),空间复杂度是 O(N). 我们看下代码: 12345function RShift(list, k) { const n = list.length; let i = Math.abs(k > 0 ? k % n : n + (k % n)); return list.concat([...list]).slice(n - i, n * 2 - i);} 哈哈,虽然离题目越来越远了,但是扩展了思路,也不错,这就是刷题的乐趣。 三次翻转法我们来看下另外一种方法 - 经典的三次翻转法,我们可以这么做: 先把[0, n - k - 1]翻转 然后把[n - k, n - 1]翻转 最后把[0, n - 1]翻转 12345678910111213141516171819function reverse(list, start, end) { let t = null; while (start < end) { t = list[start]; list[start] = list[end]; list[end] = t; start++; end--; }}function RShift(list, k) { const n = list.length; if (k % n === 0) return; reverse(list, 0, n - k - 1); reverse(list, n - k, n - 1); reverse(list, 0, n - 1); return list;} 这里给一个简单的数学证明: 对于[0, n - k - 1] 部分,我们翻转一次后新的坐标 y 和之前的坐标 x 的关系可以表示为y = n - 1 - k - x 对于[n - k, n -1] 部分,我们翻转一次后新的坐标 y 和之前的坐标 x 的关系可以表示为y = 2 * n - 1 - k - x 最后我们整体进行翻转的时候,新的坐标 y 和之前的坐标 x 的关系可以表示为 y = n - 1 - (n - 1 - k - x) 即 y = k + x (0 <= x <= n - k - 1) y = n - 1 - (2 * n - 1 - k - x) 即 y = k + x - n (n - k <= x <= n - 1) 正好满足我们的位移条件。 这种做法时间复杂度是 O(N)空间复杂度 O(1),终于满足了我们的要求。 其他类似题目推荐: Sentence Reversal 字符串循环移位字符串和数组真的是一模一样,因为字符串也可以看成是字符序列,因此字符串就是数组。本质上来说,它和数组循环移位题目没有任何区别, 现在让我们来通过一道题来感受下。 题目描述给定两个字符串 s1 和 s2,要求判定 s2 是否能被 s1 循环移位得到的字符串包含。比如,给定 s1 = AABCD 和 s2 = CDAA,返回 true 。 给定 s1 = ABCD,s2 = ACBD, 则返回 false。 题目来自《编程之美》 暴力法s1 我们每次移动一位,然后判断逐一判断以每一个位置开头的字符串是否包含 s2,如果包含则返回 true,否则继续匹配。 这种做法很暴力,时间复杂度 O(n^2),在 n 特别大的时候不是很有效。 代码如下: 1234567891011121314151617181920212223242526function RIncludes(s1, s2) { const n = s1.length; const m = s2.length; let t = null; let p1; // s1的指针 let p2; // s2的指针 for (let i = 0; i < n; i++) { t = s1[0]; for (let j = 0; j < n - 1; j++) { s1[j] = s1[j + 1]; } s1[n - 1] = t; p1 = 0; p2 = 0; while (p1 < n && p2 < m) { if (s1[p1] === s2[p2]) { p1++; p2++; } else break; } if (p2 === m) return true; } return false;} 巧用模运算另外一种方法就是上面那种空间换时间的方式,我们将两个 s1 连接到一起,然后直接双指针即可,这里不再赘述。 这种方法虽然巧妙,但是我们花费了额外的 N 的空间,能否不借助额外的空间呢?答案是可以的,我们可以假想已经存在了另外一个相同的 s1,并且我们将它连接到 s1 的末尾。注意这里是假想,实际不存在,因此空间复杂度是 O(1)。那么如何实现呢? 答案还是利用求模。 1234567891011121314151617181920212223function RIncludes(s1, s2) { const n = s1.length; const m = s2.length; let t = null; let p1; // s1的指针 let p2; // s2的指针 for (let i = 0; i < n; i++) { p1 = i; // 这一行代码变了 p2 = 0; while (p1 < 2 * n && p2 < m) { // 不需要循环移动一位了,也就是说省了一个N的循环 if (s1[p1 % n] === s2[p2]) { // 这一行代码变了 p1++; p2++; } else break; } if (p2 === m) return true; } return false;} 至此,这道题就告一段落了,大家如果有别的方法,也欢迎在评论区留言。 链表循环移位链表不同于前面的数组和字符串,我们来个题目感受一下。 这里出一个 LeetCode 题目,官方难度为中等难度的一个题 - 61. 旋转链表 题目描述123456789101112131415161718给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。示例 1:输入: 1->2->3->4->5->NULL, k = 2输出: 4->5->1->2->3->NULL解释:向右旋转 1 步: 5->1->2->3->4->NULL向右旋转 2 步: 4->5->1->2->3->NULL示例 2:输入: 0->1->2->NULL, k = 4输出: 2->0->1->NULL解释:向右旋转 1 步: 2->0->1->NULL向右旋转 2 步: 1->2->0->NULL向右旋转 3 步: 0->1->2->NULL向右旋转 4 步: 2->0->1->NULL 思路其实这个思路简单,就是先找到断点,然后重新拼接链表即可。这个断点其实就是第n - k % n个节点, 其中 k 为右移的位数,n 为链表长度。这里取模的原因和上面一样,为了防止 k 过大做的无谓运算。但是这道题目限定了 k 是非负数,那么我们就不需要做这个判断了。 如图所示: 代码也很简单,我们来看下。 代码1234567891011121314151617181920212223var rotateRight = function (head, k) { if (head === null || head.next === null) return head; let p1 = head; let n = 1; let res = null; while (p1 && p1.next) { p1 = p1.next; n++; } let cur = 1; let p2 = head; while (cur < n - (k % n)) { p2 = p2.next; cur++; } p1.next = head; res = p2.next; p2.next = null; return res;}; 扩展 循环移位的有序数组,查找某一个值,要求时间复杂度为 O(logn) 这道题我在《每日一题》出过","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"字符串","slug":"数据结构/字符串","permalink":"https://lucifer.ren/blog/categories/数据结构/字符串/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"循环移位","slug":"算法/循环移位","permalink":"https://lucifer.ren/blog/categories/算法/循环移位/"},{"name":"链表","slug":"数据结构/链表","permalink":"https://lucifer.ren/blog/categories/数据结构/链表/"},{"name":"编程之美","slug":"编程之美","permalink":"https://lucifer.ren/blog/categories/编程之美/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"数组","slug":"数组","permalink":"https://lucifer.ren/blog/tags/数组/"},{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/tags/链表/"},{"name":"循环移位","slug":"循环移位","permalink":"https://lucifer.ren/blog/tags/循环移位/"},{"name":"字符串","slug":"字符串","permalink":"https://lucifer.ren/blog/tags/字符串/"},{"name":"编程之美","slug":"编程之美","permalink":"https://lucifer.ren/blog/tags/编程之美/"}]},{"title":"致作者","slug":"to-author","date":"2020-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.907Z","comments":true,"path":"2020/02/20/to-author/","link":"","permalink":"https://lucifer.ren/blog/2020/02/20/to-author/","excerpt":"写给我敬爱的作者们,关于“云写书”的初衷,愿景,规划以及具体细节。","text":"写给我敬爱的作者们,关于“云写书”的初衷,愿景,规划以及具体细节。 初衷建立作者群,让大家参与进来的目的有三个。 缩短写作的周期 我希望这本书尽快与大家见面,毕竟时间就是金钱,一个人的力量还是很有限的,目前计划作者控制在 2-10 个人,参与人数不限制。 结识志同道合的朋友,将来可以继续合作 本来计划写别的书的,只是突然觉得 LeetCode 题解这块受众更大,大家普遍希望有这么一本书,因此才决定先写这本。我也希望之后写别的书的时候大家也可以在一起合作。 更好地推广 作者们也可以起到很好地宣传作用,毕竟是自己深度参与的书,大家宣传的意愿很会有的。 愿景背靠着 Github LeetCode 排名第一的项目,再加上多个媒体平台的宣传推广,我个人觉得市场还是有的,另外市面上的多是以数据结构和算法为基础进行讲解,而不是 LeetCode 题解方面,这方面我认为是一个缺口。 另外我看了很多相关的资料,包括电子书,实体书以及博客,官方 articles 等,决定要不就是不够系统,要不就是不够通俗易懂。 我的受众群体是想找工作的LeetCode新手,帮助他们攻克一些高频题目,掌握解题技巧,更加有效率地刷题 规划 2019-10 建立初步的成员名单,制定写作规范,联系 LeetCode 官方授权 2019-11 分配章节给大家,大家分别书写 2019-12 汇总大家的文章,进行审阅 & 校验 Code StyleTODO 文章格式TODO 大纲这个是我之前写的大纲,需要微调, 大家可以先大概看一下 https://lucifer.ren/blog/2019/10/03/draft/ 样张 一文搞懂《链表反转》 文科生都能看懂的循环移位算法 一文看懂《最大子序列和问题》 如何参与要参加写作的, 给出你写过的文章,最好 LeetCode 或者算法相关,然后等待我审核,写文章的时候语言要求 python,不会的可以花几个小时学习一下。 另外需要提供三种语言(分别是 JS,Java 和 Python)的代码到我新建的一个仓库中,专门给这本书放源码,按照语言和章节划分一下。 作者们别忘记让我拉你进组织,我们的组织是https://github.com/leetcode-book 如果语言有什么困难,直接群里沟通,我相信语言不是问题。 另外大家写题解的时候,一定少用语言特有的东西。 有能力的欢迎提供其他语言的代码实现","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"LeetCode题解书","slug":"LeetCode/LeetCode题解书","permalink":"https://lucifer.ren/blog/categories/LeetCode/LeetCode题解书/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"}]},{"title":"【算法提高班】并查集","slug":"union-find","date":"2020-02-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.545Z","comments":true,"path":"2020/02/20/union-find/","link":"","permalink":"https://lucifer.ren/blog/2020/02/20/union-find/","excerpt":"关于并查集的题目不少,官方给的数据是 30 道(截止 2020-02-20),但是有一些题目虽然官方没有贴并查集标签,但是使用并查集来说确非常简单。这类题目如果掌握模板,那么刷这种题会非常快,并且犯错的概率会大大降低,这就是模板的好处。","text":"关于并查集的题目不少,官方给的数据是 30 道(截止 2020-02-20),但是有一些题目虽然官方没有贴并查集标签,但是使用并查集来说确非常简单。这类题目如果掌握模板,那么刷这种题会非常快,并且犯错的概率会大大降低,这就是模板的好处。 我这里总结了几道并查集的题目: 547.朋友圈 721. 账户合并 990. 等式方程的可满足性 大家可以学了模板之后去套用一下上面的三道题,做不出来的可以看看我的题解。 并查集概述并查集算法,主要是解决图论中「动态连通性」问题的 Union-Find 算法解决的是图的动态连通性问题,这个算法本身不难,能不能应用出来主要是看你抽象问题的能力,是否能够把原始问题抽象成一个有关图论的问题。 如果你对这个算法不是很明白,推荐看一下这篇文章Union-Find 算法详解,讲的非常详细。 你可以把并查集的元素看成部门的人,几个人可以组成一个部门个数。 并查集核心的三个方法分别是union, find, connected。 union: 将两个人所在的两个部门合并成一个部门(如果两个人是相同部门,实际山不需要合并) (图来自 labuladong) find: 查找某个人的部门 leader connnected: 判断两个人是否是一个部门的 (图来自 labuladong) 模板这是一个我经常使用的模板,我会根据具体题目做细小的变化,但是大体是不变的。 12345678910111213141516class UF: parent = {} cnt = 0 def __init__(self, M): # 初始化 parent 和 cnt def find(self, x): while x != self.parent[x]: x = self.parent[x] return x def union(self, p, q): if self.connected(p, q): return self.parent[self.find(p)] = self.find(q) self.cnt -= 1 def connected(self, p, q): return self.find(p) == self.find(q) 如果你想要更好的性能,这个模板更适合你,相应地代码稍微有一点复杂。 12345678910111213141516171819202122232425class UF: parent = {} size = {} cnt = 0 def __init__(self, M): # 初始化 parent,size 和 cnt def find(self, x): while x != self.parent[x]: x = self.parent[x] # 路径压缩 self.parent[x] = self.parent[self.parent[x]]; return x def union(self, p, q): if self.connected(p, q): return # 小的树挂到大的树上, 使树尽量平衡 leader_p = self.find(p) leader_q = self.find(q) if self.size[leader_p] < self.size[leader_q]: self.parent[leader_p] = leader_q else: self.parent[leader_q] = leader_p self.cnt -= 1 def connected(self, p, q): return self.find(p) == self.find(q) 大家可以根据情况使用不同的模板。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"}]},{"title":"【LeetCode日记】 1449. 数位成本和为目标值的最大数字","slug":"1449.form-largest-integer-with-digits-that-add-up-to-target","date":"2020-02-08T16:00:00.000Z","updated":"2023-01-07T12:20:39.935Z","comments":true,"path":"2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/","link":"","permalink":"https://lucifer.ren/blog/2020/02/09/1449.form-largest-integer-with-digits-that-add-up-to-target/","excerpt":"这是一道难度 Hard 的经典背包问题,属于完全背包问题,来看看背包问题的套路吧~ ​","text":"这是一道难度 Hard 的经典背包问题,属于完全背包问题,来看看背包问题的套路吧~ ​ 题目地址(1449. 数位成本和为目标值的最大数字)https://leetcode-cn.com/problems/form-largest-integer-with-digits-that-add-up-to-target/ 题目描述1234567891011121314151617181920212223242526272829303132333435363738394041424344454647给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数:给当前结果添加一个数位(i + 1)的成本为 cost[i] (cost 数组下标从 0 开始)。总成本必须恰好等于 target 。添加的数位中没有数字 0 。由于答案可能会很大,请你以字符串形式返回。如果按照上述要求无法得到任何整数,请你返回 "0" 。 示例 1:输入:cost = [4,3,2,5,6,7,2,5,5], target = 9输出:"7772"解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "997" 也是满足要求的数字,但 "7772" 是较大的数字。 数字 成本 1 -> 4 2 -> 3 3 -> 2 4 -> 5 5 -> 6 6 -> 7 7 -> 2 8 -> 5 9 -> 5示例 2:输入:cost = [7,6,5,5,5,6,8,7,8], target = 12输出:"85"解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12 。示例 3:输入:cost = [2,4,6,2,4,6,4,4,4], target = 5输出:"0"解释:总成本是 target 的条件下,无法生成任何整数。示例 4:输入:cost = [6,10,15,40,40,40,40,40,40], target = 47输出:"32211" 提示:cost.length == 91 <= cost[i] <= 50001 <= target <= 5000 思路由于数组可以重复选择,因此这是一个完全背包问题。 01 背包对于 01 背包问题,我们的套路是: 123for i in 0 to N: for j in 1 to V + 1: dp[j] = max(dp[j], dp[j - cost[i]) 而一般我们为了处理边界问题,我们一般会这么写代码: 1234for i in 1 to N + 1: # 这里是倒序的,原因在于这里是01背包。 for j in V to 0: dp[j] = max(dp[j], dp[j - cost[i - 1]) 其中 dp[j] 表示只能选择前 i 个物品,背包容量为 j 的情况下,能够获得的最大价值。 dp[j] 不是没 i 么? 其实我这里 i 指的是 dp[j]当前所处的循环中的 i 值 完全背包问题回到问题,我们这是完全背包问题: 1234for i in 1 to N + 1: # 这里不是倒序,原因是我们这里是完全背包问题 for j in 1 to V + 1: dp[j] = max(dp[j], dp[j - cost[i - 1]) 为什么 01 背包需要倒序,而完全背包则不可以实际上,这是一个骚操作,我来详细给你讲一下。 其实要回答这个问题,我要先将 01 背包和完全背包退化二维的情况。 对于 01 背包: 123for i in 1 to N + 1: for j in V to 0: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cost[i - 1]) 注意等号左边是 i,右边是 i - 1,这很好理解,因为 i 只能取一次嘛。 那么如果我们不降序遍历会怎么样呢? 如图橙色部分表示已经遍历的部分,而让我们去用[j - cost[i - 1]] 往前面回溯的时候,实际上回溯的是 dp[i]j - cost[i - 1]],而不是 dp[i - 1]j - cost[i - 1]]。 如果是降序就可以了,如图: 这个明白的话,我们继续思考为什么完全背包就要不降序了呢? 我们还是像上面一样写出二维的代码: 123for i in 1 to N + 1: for j in 1 to V + 1: dp[i][j] = max(dp[i - 1][j], dp[i][j - cost[i - 1]) 由于 i 可以取无数次,那么正序遍历正好可以满足,如上图。 恰好装满 VS 可以不装满题目有两种可能,一种是要求背包恰好装满,一种是可以不装满(只要不超过容量就行)。而本题是要求恰好装满的。而这两种情况仅仅影响我们dp数组初始化。 恰好装满。只需要初始化 dp[0] 为 0, 其他初始化为负数即可。 可以不装满。 只需要全部初始化为 0,即可, 原因很简单,我多次强调过 dp 数组本质上是记录了一个个自问题。 dp[0]是一个子问题,dp[1]是一个子问题。。。 有了上面的知识就不难理解了。 初始化的时候,我们还没有进行任何选择,那么也就是说 dp[0] = 0,因为我们可以通过什么都不选达到最大值 0。而 dp[1],dp[2]…则在当前什么都不选的情况下无法达成,也就是无解,因为为了区分,我们可以用负数来表示,当然你可以用任何可以区分的东西表示,比如 None。 回到本题而这道题和普通的完全背包不一样,这个是选择一个组成的最大数。由小学数学知识一个数字的全排列中,按照数字降序排列是最大的,我这里用了一个骚操作,那就是 cost 从后往前遍历,因为后面表示的数字大。 代码12345678class Solution: def largestNumber(self, cost: List[int], target: int) -> str: dp = [0] + [float('-inf')] * target for i in range(9, 0, -1): for j in range(1, target+1): if j >= cost[i - 1]: dp[j] = max(dp[j], (dp[j-cost[i - 1]] * 10) + i) return str(dp[target]) if dp[target] > 0 else '0' _复杂度分析_ 时间复杂度:$O(target))$ 空间复杂度:$O(target)$ 扩展最后贴几个我写过的背包问题,让大家看看历史是多么的相似。 (322. 硬币找零(完全背包问题)) 这里内外循环和本题正好是反的,我只是为了”秀技”(好玩),实际上在这里对答案并不影响。 (518. 零钱兑换 II) 这里内外循环和本题正好是反的,但是这里必须这么做,否则结果是不对的,具体可以点进去链接看我那个题解 所以这两层循环的位置起的实际作用是什么? 代表的含义有什么不同? 本质上: 123for i in 1 to N + 1: for j in V to 0: ... 这种情况选择物品 1 和物品 3(随便举的例子),是一种方式。选择物品 3 个物品 1(注意是有顺序的)是同一种方式。 原因在于你是固定物品,去扫描容量。 而: 123for j in V to 0: for i in 1 to N + 1: ... 这种情况选择物品 1 和物品 3(随便举的例子),是一种方式。选择物品 3 个物品 1(注意是有顺序的)也是一种方式。原因在于你是固定容量,去扫描物品。 因此总的来说,如果你认为[1,3]和[3,1]是一种,那么就用方法 1 的遍历,否则用方法 2。 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数组","slug":"数据结构/数组","permalink":"https://lucifer.ren/blog/categories/数据结构/数组/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"背包问题","slug":"算法/背包问题","permalink":"https://lucifer.ren/blog/categories/算法/背包问题/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"构造二叉树系列","slug":"构造二叉树专题","date":"2020-02-07T16:00:00.000Z","updated":"2023-01-05T12:24:50.080Z","comments":true,"path":"2020/02/08/构造二叉树专题/","link":"","permalink":"https://lucifer.ren/blog/2020/02/08/构造二叉树专题/","excerpt":"构造二叉树是一个常见的二叉树考点,相比于直接考察二叉树的遍历,这种题目的难度会更大。截止到目前(2020-02-08) LeetCode 关于构造二叉树一共有三道题目,分别是: 105. 从前序与中序遍历序列构造二叉树 106. 从中序与后序遍历序列构造二叉树 889. 根据前序和后序遍历构造二叉树 今天就让我们用一个套路一举攻破他们。 ​","text":"构造二叉树是一个常见的二叉树考点,相比于直接考察二叉树的遍历,这种题目的难度会更大。截止到目前(2020-02-08) LeetCode 关于构造二叉树一共有三道题目,分别是: 105. 从前序与中序遍历序列构造二叉树 106. 从中序与后序遍历序列构造二叉树 889. 根据前序和后序遍历构造二叉树 今天就让我们用一个套路一举攻破他们。 ​ 105. 从前序与中序遍历序列构造二叉树题目描述12345678910111213141516根据一棵树的前序遍历与中序遍历构造二叉树。注意:你可以假设树中没有重复的元素。例如,给出前序遍历 preorder = [3,9,20,15,7]中序遍历 inorder = [9,3,15,20,7]返回如下的二叉树: 3 / \\ 9 20 / \\ 15 7 思路我们以题目给出的测试用例来讲解: 前序遍历是根左右,因此 preorder 第一个元素一定整个树的根。由于题目说明了没有重复元素,因此我们可以通过 val 去 inorder 找到根在 inorder 中的索引 i。而由于中序遍历是左根右,我们容易找到 i 左边的都是左子树,i 右边都是右子树。 我使用红色表示根,蓝色表示左子树,绿色表示右子树。 根据此时的信息,我们能构造的树是这样的: 我们 preorder 继续向后移动一位,这个时候我们得到了第二个根节点”9“,实际上就是左子树的根节点。 我们 preorder 继续向后移动一位,这个时候我们得到了第二个根节点”20“,实际上就是右子树的根节点。其中右子树由于个数大于 1,我们无法确定,我们继续执行上述逻辑。 根据此时的信息,我们能构造的树是这样的: 我们不断执行上述逻辑即可。简单起见,递归的时候每次我都开辟了新的数组,这个其实是没有必要的,我们可以通过四个变量来记录 inorder 和 preorder 的起始位置即可。 代码代码支持:Python3 Python3 Code: 123456789101112class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 实际上inorder 和 postorder一定是同时为空的,因此你无论判断哪个都行 if not preorder: return None root = TreeNode(preorder[0]) i = inorder.index(root.val) root.left = self.buildTree(preorder[1:i + 1], inorder[:i]) root.right = self.buildTree(preorder[i + 1:], inorder[i+1:]) return root 复杂度分析 时间复杂度:由于每次递归我们的 inorder 和 preorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度忽略了开辟数组的内存消耗。 106. 从中序与后序遍历序列构造二叉树如果你会了上面的题目,那么这个题目对你来说也不是难事,我们来看下。 题目描述12345678910111213141516根据一棵树的中序遍历与后序遍历构造二叉树。注意:你可以假设树中没有重复的元素。例如,给出中序遍历 inorder = [9,3,15,20,7]后序遍历 postorder = [9,15,7,20,3]返回如下的二叉树: 3 / \\ 9 20 / \\ 15 7 思路我们以题目给出的测试用例来讲解: 后序遍历是左右根,因此 postorder 最后一个元素一定整个树的根。由于题目说明了没有重复元素,因此我们可以通过 val 去 inorder 找到根在 inorder 中的索引 i。而由于中序遍历是左根右,我们容易找到 i 左边的都是左子树,i 右边都是右子树。 我使用红色表示根,蓝色表示左子树,绿色表示右子树。 根据此时的信息,我们能构造的树是这样的: 其中右子树由于个数大于 1,我们无法确定,我们继续执行上述逻辑。我们 postorder 继续向前移动一位,这个时候我们得到了第二个根节点”20“,实际上就是右子树的根节点。 根据此时的信息,我们能构造的树是这样的: 我们不断执行上述逻辑即可。简单起见,递归的时候每次我都开辟了新的数组,这个其实是没有必要的,我们可以通过四个变量来记录 inorder 和 postorder 的起始位置即可。 代码代码支持:Python3 Python3 Code: 1234567891011class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: # 实际上inorder 和 postorder一定是同时为空的,因此你无论判断哪个都行 if not inorder: return None root = TreeNode(postorder[-1]) i = inorder.index(root.val) root.left = self.buildTree(inorder[:i], postorder[:i]) root.right = self.buildTree(inorder[i+1:], postorder[i:-1]) return root 复杂度分析 时间复杂度:由于每次递归我们的 inorder 和 postorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度忽略了开辟数组的内存消耗。 889. 根据前序和后序遍历构造二叉树题目描述1234567891011121314151617返回与给定的前序和后序遍历匹配的任何二叉树。 pre 和 post 遍历中的值是不同的正整数。 示例:输入:pre = [1,2,4,5,3,6,7], post = [4,5,2,6,7,3,1]输出:[1,2,3,4,5,6,7] 提示:1 <= pre.length == post.length <= 30pre[] 和 post[] 都是 1, 2, ..., pre.length 的排列每个输入保证至少有一个答案。如果有多个答案,可以返回其中一个。 思路我们以题目给出的测试用例来讲解: 前序遍历是根左右,因此 preorder 第一个元素一定整个树的根,preorder 第二个元素(如果存在的话)一定是左子树。由于题目说明了没有重复元素,因此我们可以通过 val 去 postorder 找到 pre[1]在 postorder 中的索引 i。而由于后序遍历是左右根,因此我们容易得出。 postorder 中的 0 到 i(包含)是左子树,preorder 的 1 到 i+1(包含)也是左子树。 其他部分可以参考上面两题。 代码代码支持:Python3 Python3 Code: 1234567891011121314class Solution: def constructFromPrePost(self, pre: List[int], post: List[int]) -> TreeNode: # 实际上pre 和 post一定是同时为空的,因此你无论判断哪个都行 if not pre: return None node = TreeNode(pre[0]) if len(pre) == 1: return node i = post.index(pre[1]) node.left = self.constructFromPrePost(pre[1:i + 2], post[:i + 1]) node.right = self.constructFromPrePost(pre[i + 2:], post[i + 1:-1]) return node 复杂度分析 时间复杂度:由于每次递归我们的 postorder 和 preorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度忽略了开辟数组的内存消耗。 总结如果你仔细对比一下的话,会发现我们的思路和代码几乎一模一样。注意到每次递归我们的两个数组个数都会减去 1,因此我们递归终止条件不难写出,并且递归问题规模如何缩小也很容易,那就是数组总长度减去 1。 我们拿最后一个题目来说: 12node.left = self.constructFromPrePost(pre[1:i + 2], post[:i + 1])node.right = self.constructFromPrePost(pre[i + 2:], post[i + 1:-1]) 我们发现 pre 被拆分为两份,pre[1:i + 2]和 pre[i + 2:]。很明显总数少了 1,那就是 pre 的第一个元素。 也就是说如果你写出一个,其他一个不用思考也能写出来。 而对于 post 也一样,post[:i + 1] 和 post[i + 1:-1],很明显总数少了 1,那就是 post 最后一个元素。 这个解题模板足够简洁,并且逻辑清晰,大家可以用我的模板试试~ 简单起见,递归的时候每次我都开辟了新的数组,这个其实是没有必要的,我们可以通过四个变量来记录 inorder 和 postorder 的起始位置即可, 具体见下方代码区。 代码: 1234567891011class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: def dfs(inorder, i_start, i_end, postorder, p_start, p_end): if i_start > i_end: return None if i_start == i_end: return TreeNode(inorder[i_start]) node = TreeNode(postorder[p_end]) i = inorder.index(postorder[p_end]) node.left = dfs(inorder, i_start, i - 1, postorder, p_start, p_start + (i - 1 - i_start)) node.right = dfs(inorder, i + 1, i_end, postorder, p_start + (i - 1 - i_start) + 1, p_end - 1) return node return dfs(inorder, 0, len(inorder) - 1, postorder, 0, len(postorder) - 1) updated:(有同学不懂为啥 post_end 是 post_start + i - 1 - in_start,我解释一下) 我上面提到了 实际上 inorder 的 长度和 postorder 长度是一样的。而: inorder 的长度是 i - 1 - in_start 因此 postorder 的长度也是 i - 1 - in_start postorder 的长度 = post_end - post_start 因此 post_end 就是 post_start + i - 1 - in_start 复杂度分析 时间复杂度:由于每次递归我们的 inorder 和 postorder 的总数都会减 1,因此我们要递归 N 次,故时间复杂度为 $O(N)$,其中 N 为节点个数。 空间复杂度:我们使用了递归,也就是借助了额外的栈空间来完成, 由于栈的深度为 N,因此总的空间复杂度为 $O(N)$,其中 N 为节点个数。 关注我更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/categories/二叉树/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"二叉树","slug":"二叉树","permalink":"https://lucifer.ren/blog/tags/二叉树/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"}]},{"title":"Floyd-Warshall 解题模板,助你快速AC","slug":"1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance","date":"2020-02-03T16:00:00.000Z","updated":"2023-01-07T12:20:39.932Z","comments":true,"path":"2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/","link":"","permalink":"https://lucifer.ren/blog/2020/02/04/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/","excerpt":"Floyd-Warshall 是解决任意两点间的最短路径的一种算法,LeetCode 有很多题目都用了,掌握这套解题模板帮你快速 AC。","text":"Floyd-Warshall 是解决任意两点间的最短路径的一种算法,LeetCode 有很多题目都用了,掌握这套解题模板帮你快速 AC。 题目地址(1334. 阈值距离内邻居最少的城市)https://leetcode-cn.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/ 题目描述123456789有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] = [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold。返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为 distanceThreshold 的城市。如果有多个这样的城市,则返回编号最大的城市。注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。 示例 1: 12345678910111213输入:n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4输出:3解释:城市分布图如上。每个城市阈值距离 distanceThreshold = 4 内的邻居城市分别是:城市 0 -> [城市 1, 城市 2] 城市 1 -> [城市 0, 城市 2, 城市 3] 城市 2 -> [城市 0, 城市 1, 城市 3] 城市 3 -> [城市 1, 城市 2] 城市 0 和 3 在阈值距离 4 以内都有 2 个邻居城市,但是我们必须返回城市 3,因为它的编号最大。示例 2: 123456789101112131415161718192021输入:n = 5, edges = [[0,1,2],[0,4,8],[1,2,3],[1,4,2],[2,3,1],[3,4,1]], distanceThreshold = 2输出:0解释:城市分布图如上。 每个城市阈值距离 distanceThreshold = 2 内的邻居城市分别是:城市 0 -> [城市 1] 城市 1 -> [城市 0, 城市 4] 城市 2 -> [城市 3, 城市 4] 城市 3 -> [城市 2, 城市 4]城市 4 -> [城市 1, 城市 2, 城市 3] 城市 0 在阈值距离 4 以内只有 1 个邻居城市。 提示:2 <= n <= 1001 <= edges.length <= n * (n - 1) / 2edges[i].length == 30 <= fromi < toi < n1 <= weighti, distanceThreshold <= 10^4所有 (fromi, toi) 都是不同的。 思路这道题的本质就是: 在一个无向图中寻找每两个城镇的最小距离,我们使用 Floyd-Warshall 算法(英语:Floyd-Warshall algorithm),中文亦称弗洛伊德算法,是解决任意两点间的最短路径的一种算法。 筛选最小距离不大于 distanceThreshold 的城镇。 统计每个城镇,其满足条件的城镇有多少个 我们找出最少的即可 Floyd-Warshall 算法的时间复杂度和空间复杂度都是$O(N^3)$, 而空间复杂度可以优化到$O(N^2)$。Floyd-Warshall 的基本思想是对于每两个点之间的最小距离,要么经过中间节点 k,要么不经过,我们取两者的最小值,这是一种动态规划思想,详细的解法可以参考Floyd-Warshall 算法(wikipedia) 代码代码支持:Python3 Python3 Code: 1234567891011121314151617181920212223242526class Solution: def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int: # 构建dist矩阵 dist = [[float('inf')] * n for _ in range(n)] for i, j, w in edges: dist[i][j] = w dist[j][i] = w for i in range(n): dist[i][i] = 0 for k in range(n): for i in range(n): for j in range(n): dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) # 过滤 res = 0 minCnt = float('inf') for i in range(n): cnt = 0 for d in dist[i]: if d <= distanceThreshold: cnt += 1 if cnt <= minCnt: minCnt = cnt res = i return res 关键点解析 Floyd-Warshall 算法 你可以将本文给的 Floyd-Warshall 算法当成一种解题模板使用","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"图","slug":"数据结构/图","permalink":"https://lucifer.ren/blog/categories/数据结构/图/"},{"name":"解题模板","slug":"解题模板","permalink":"https://lucifer.ren/blog/categories/解题模板/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"解题模板","slug":"解题模板","permalink":"https://lucifer.ren/blog/tags/解题模板/"},{"name":"图","slug":"图","permalink":"https://lucifer.ren/blog/tags/图/"},{"name":"Floyd-Warshall","slug":"Floyd-Warshall","permalink":"https://lucifer.ren/blog/tags/Floyd-Warshall/"}]},{"title":"我的日程安排表系列","slug":"leetcode-我的日程安排表系列","date":"2020-02-02T16:00:00.000Z","updated":"2023-01-07T12:34:34.361Z","comments":true,"path":"2020/02/03/leetcode-我的日程安排表系列/","link":"","permalink":"https://lucifer.ren/blog/2020/02/03/leetcode-我的日程安排表系列/","excerpt":"《我的日程安排表》截止目前(2020-02-03)在 LeetCode 上一共有三道题,其中两个中等难度,一个困难难度,分别是: 729. 我的日程安排表 I 731. 我的日程安排表 II 732. 我的日程安排表 III 另外 LeetCode 上有一个类似的系列《会议室》,截止目前(2020-02-03)有两道题目。其中一个简单一个中等,分别是: 252. 会议室 253. 会议室 II 今天我们就来攻克它们。","text":"《我的日程安排表》截止目前(2020-02-03)在 LeetCode 上一共有三道题,其中两个中等难度,一个困难难度,分别是: 729. 我的日程安排表 I 731. 我的日程安排表 II 732. 我的日程安排表 III 另外 LeetCode 上有一个类似的系列《会议室》,截止目前(2020-02-03)有两道题目。其中一个简单一个中等,分别是: 252. 会议室 253. 会议室 II 今天我们就来攻克它们。 729. 我的日程安排表 I题目地址https://leetcode-cn.com/problems/my-calendar-i 题目描述实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内没有其他安排,则可以存储这个新的日程安排。 MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。 当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订。 每次调用 MyCalendar.book 方法时,如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。 请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end) 示例 1: MyCalendar();MyCalendar.book(10, 20); // returns trueMyCalendar.book(15, 25); // returns falseMyCalendar.book(20, 30); // returns true解释:第一个日程安排可以添加到日历中. 第二个日程安排不能添加到日历中,因为时间 15 已经被第一个日程安排预定了。第三个日程安排可以添加到日历中,因为第一个日程安排并不包含时间 20 。说明: 每个测试用例,调用 MyCalendar.book 函数最多不超过 100 次。调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。 暴力法思路首先我们考虑暴力法。每插入一个元素我们都判断其是否和已有的所有课程重叠。 我们定一个函数intersected(calendar, calendars),其中 calendar 是即将要插入的课程,calendars 是已经插入的课程。 只要 calendar 和 calendars 中的任何一个课程有交叉,我们就返回 True,否则返回 False。 对于两个 calendar,我们的判断逻辑都是一样的。假设连个 calendar 分别是[s1, e1]和[s2, e2]。那么如果s1 >= e2 or s2 <= e1, 则两个课程没有交叉,可以预定,否则不可以。如图,1,2,3 可以预定,剩下的不可以。 代码是这样的: 12345678def intersected(calendar, calendars): for [start, end] in calendars: if calendar[0] >= end or calendar[1] <= start: continue else: return True return False 复杂度分析: 时间复杂度:$O(N^2)$。N 指的是日常安排的数量,对于每个新的日常安排,我们检查新的日常安排是否发生冲突来决定是否可以预订新的日常安排。 空间复杂度: $O(N)$。 这个代码写出来之后整体代码就呼之欲出了,全部代码见下方代码部分。 代码代码支持 Python3: Python3 Code: 1234567891011121314151617181920212223242526272829303132## @lc app=leetcode.cn id=729 lang=python3## [729] 我的日程安排表 I## @lc code=startclass MyCalendar: def __init__(self): self.calendars = [] def book(self, start: int, end: int) -> bool: def intersected(calendar, calendars): for [start, end] in calendars: if calendar[0] >= end or calendar[1] <= start: continue else: return True return False if intersected([start, end], self.calendars): return False self.calendars.append([start, end]) return True # Your MyCalendar object will be instantiated and called as such: # obj = MyCalendar() # param_1 = obj.book(start,end) # @lc code=end 实际上我们还可以换个角度,上面的思路判断交叉部分我们考虑的是“如何不交叉”,剩下的就是交叉。我们也可以直接考虑交叉。还是上面的例子,如果两个课程交叉,那么一定满足s1 < e2 and e1 > s2。基于此,我们写出下面的代码。 代码支持 Python3: Python3 Code: 12345678910111213141516171819202122232425## @lc app=leetcode.cn id=729 lang=python3## [729] 我的日程安排表 I## @lc code=startclass MyCalendar: def __init__(self): self.calendars = [] def book(self, start: int, end: int) -> bool: for s, e in self.calendars: if start < e and end > s: return False self.calendars.append([start, end]) return True # Your MyCalendar object will be instantiated and called as such: # obj = MyCalendar() # param_1 = obj.book(start,end) # @lc code=end 二叉查找树法思路和上面思路类似,只不过我们每次都对 calendars 进行排序,那么我们可以通过二分查找日程安排的情况来检查新日常安排是否可以预订。如果每次插入之前都进行一次排序,那么时间复杂度会很高。如图,我们的[s1,e1], [s2,e2], [s3,e3] 是按照时间顺序排好的日程安排。我们现在要插入[s,e],我们使用二分查找,找到要插入的位置,然后和插入位置的课程进行一次比对即可,这部分的时间复杂度是 O(logN)$。 我们考虑使用平衡二叉树来维护这种动态的变化,在最差的情况时间复杂度会退化到上述的$O(N^2)$,平均情况是$O(NlogN)$,其中 N 是已预订的日常安排数。 代码代码支持 Python3: Python3 Code: 1234567891011121314151617181920212223242526272829class Node: def __init__(self, start, end): self.start = start self.end = end self.left = self.right = None def insert(self, node): if node.start >= self.end: if not self.right: self.right = node return True return self.right.insert(node) elif node.end <= self.start: if not self.left: self.left = node return True return self.left.insert(node) else: return Falseclass MyCalendar(object): def __init__(self): self.root = None def book(self, start, end): if self.root is None: self.root = Node(start, end) return True return self.root.insert(Node(start, end)) 731. 我的日程安排表 II题目地址https://leetcode-cn.com/problems/my-calendar-ii 题目描述实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。 MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。 当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订。 每次调用 MyCalendar.book 方法时,如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。 请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end) 示例: MyCalendar();MyCalendar.book(10, 20); // returns trueMyCalendar.book(50, 60); // returns trueMyCalendar.book(10, 40); // returns trueMyCalendar.book(5, 15); // returns falseMyCalendar.book(5, 10); // returns trueMyCalendar.book(25, 55); // returns true解释:前两个日程安排可以添加至日历中。 第三个日程安排会导致双重预订,但可以添加至日历中。第四个日程安排活动(5,15)不能添加至日历中,因为它会导致三重预订。第五个日程安排(5,10)可以添加至日历中,因为它未使用已经双重预订的时间 10。第六个日程安排(25,55)可以添加至日历中,因为时间 [25,40] 将和第三个日程安排双重预订;时间 [40,50] 将单独预订,时间 [50,55)将和第二个日程安排双重预订。 提示: 每个测试用例,调用 MyCalendar.book 函数最多不超过 1000 次。调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。 暴力法思路暴力法和上述思路类似。但是我们多维护一个数组 intersectedCalendars 用来存储二次预定的日程安排。如果课程第一次冲突,我们将其加入 intersectedCalendars,如果和 intersectedCalendars 也冲突了,说明出现了三次预定,我们直接返回 False。 代码代码支持 Python3: Python3 Code: 123456789101112131415class MyCalendarTwo: def __init__(self): self.calendars = [] self.intersectedCalendars = [] def book(self, start: int, end: int) -> bool: for [s, e] in self.intersectedCalendars: if start < e and end > s: return False for [s, e] in self.calendars: if start < e and end > s: self.intersectedCalendars.append([max(start, s), min(end, e)]) self.calendars.append([start, end]) return True 二叉查找树法和上面的题目类似,我们仍然可以使用平衡二叉树来简化查找逻辑。具体可以参考这个 discussion>) 每次插入之前我们都需要进行一次判断,判断是否可以插入。如果不可以插入,直接返回 False,否则我们进行一次插入。 插入的时候,如果和已有的相交了,我们判断是否之前已经相交了一次,如果是返回 False,否则返回 True。关于如何判断是否和已有的相交,我们可以在 node 节点增加一个字段的方式来标记,在这里我们使用 single_overlap,True 表示产生了二次预定,False 则表示没有产生过两次及以上的预定。 代码代码支持 Python3: Python3 Code: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172class Node: def __init__(self, start, end): self.start = start self.end = end self.left = None self.right = None self.single_overlap = Falseclass MyCalendarTwo: def __init__(self): self.root = None def book(self, start, end): if not self.canInsert(start, end, self.root): return False self.root = self.insert(start, end, self.root) return True def canInsert(self, start, end, root): if not root: return True if start >= end: return True if end <= root.start: return self.canInsert(start, end, root.left) elif start >= root.end: return self.canInsert(start, end, root.right) else: if root.single_overlap: return False elif start >= root.start and end <= root.end: return True else: return self.canInsert(start, root.start, root.left) and self.canInsert(root.end, end, root.right) def insert(self, start, end, root): if not root: root = Node(start, end) return root if start >= end: return root if start >= root.end: root.right = self.insert(start, end, root.right) elif end <= root.start: root.left = self.insert(start, end, root.left) else: root.single_overlap = True a = min(root.start, start) b = max(root.start, start) c = min(root.end, end) d = max(root.end, end) root.start, root.end = b, c root.left, root.right = self.insert(a, b, root.left), self.insert(c, d, root.right) return root# Your MyCalendarTwo object will be instantiated and called as such:# obj = MyCalendarTwo()# param_1 = obj.book(start,end) 732. 我的日程安排表 III题目地址https://leetcode-cn.com/problems/my-calendar-iii/ 题目描述实现一个 MyCalendar 类来存放你的日程安排,你可以一直添加新的日程安排。 MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。 当 K 个日程安排有一些时间上的交叉时(例如 K 个日程安排都在同一时间内),就会产生 K 次预订。 每次调用 MyCalendar.book 方法时,返回一个整数 K ,表示最大的 K 次预订。 请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end) 示例 1: MyCalendarThree();MyCalendarThree.book(10, 20); // returns 1MyCalendarThree.book(50, 60); // returns 1MyCalendarThree.book(10, 40); // returns 2MyCalendarThree.book(5, 15); // returns 3MyCalendarThree.book(5, 10); // returns 3MyCalendarThree.book(25, 55); // returns 3解释:前两个日程安排可以预订并且不相交,所以最大的 K 次预订是 1。第三个日程安排[10,40]与第一个日程安排相交,最高的 K 次预订为 2。其余的日程安排的最高 K 次预订仅为 3。请注意,最后一次日程安排可能会导致局部最高 K 次预订为 2,但答案仍然是 3,原因是从开始到最后,时间[10,20],[10,40]和[5,15]仍然会导致 3 次预订。说明: 每个测试用例,调用 MyCalendar.book 函数最多不超过 400 次。调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。 二叉查找树法思路我们仍然可以使用上述的平衡二叉树的做法。只不过我们需要额外维护一个全局的最大值“k”,表示需要多少个预定。最终我们返回 k。 同时每一个 node 我们都增加一个属性 k,用来表示局部的最大值,对于每次插入,我们将 node 的 k 和全部的 k 进行比较,取出最大值即可。 代码代码支持 Python3: Python3 Code: 123456789101112131415161718192021222324252627282930313233343536373839404142434445class Node(object): def __init__(self, start, end, ktime=1): self.k = ktime self.s = start self.e = end self.right = None self.left = Noneclass MyCalendarThree(object): def __init__(self): self.root = None self.k = 0 def book(self, start, end): self.root = self.insert(self.root, start, end, 1) return self.k def insert(self, root, start, end, k): if start >= end: return root if not root: self.k = max(self.k, k) return Node(start, end, k) else: if start >= root.e: root.right = self.insert(root.right, start, end, k) return root elif end <= root.s: root.left = self.insert(root.left, start, end, k) return root else: a = min(root.s, start) b = max(root.s, start) c = min(root.e, end) d = max(root.e, end) root.left = self.insert(root.left, a, b, a == root.s and root.k or k) root.right = self.insert(root.right, c,d, d == root.e and root.k or k) root.k += k root.s = b root.e = c self.k = max(root.k, self.k) return root Count Map 法思路这个是我在看了 Discussion [C++] Map Solution, beats 95%+ 之后写的解法,解法非常巧妙。 我们使用一个 count map 来存储所有的预定,对于每次插入,我们执行count[start] += 1和count[end] -= 1。 count[t] 表示从 t 开始到下一个 t 我们有几个预定。因此我们需要对 count 进行排序才行。 我们维护一个最大值来 cnt 来表示需要的预定数。 比如预定[1,3]和[5,7],我们产生一个预定即可: 再比如预定[1,5]和[3,7],我们需要两个预定: 我们可以使用红黑树来简化时间复杂度,如果你使用的是 Java,可以直接使用现成的数据结构 TreeMap。我这里偷懒,每次都排序,时间复杂度会很高,但是可以 AC。 读到这里,你可能会发现: 这个解法似乎更具有通用型。对于第一题我们可以判断 cnt 是否小于等于 1,对于第二题我们可以判断 cnt 是否小于等于 2。 如果你不借助红黑树等数据结构直接使用 count-map 法,即每次都进行一次排序,第一题和第二题可能会直接超时。 代码代码支持 Python3: Python3 Code: 12345678910111213141516171819class MyCalendarThree: def __init__(self): self.count = dict() def book(self, start: int, end: int) -> int: self.count[start] = self.count.get(start, 0) + 1 self.count[end] = self.count.get(end, 0) - 1 cnt = 0 cur = 0 for k in sorted(self.count): cur += self.count[k] cnt = max(cnt, cur) return cnt # Your MyCalendarThree object will be instantiated and called as such: # obj = MyCalendarThree() # param_1 = obj.book(start,end) 相关题目LeetCode 上有一个类似的系列《会议室》,截止目前(2020-02-03)有两道题目。其中一个简单一个中等,解题思路非常类似,大家用这个解题思路尝试一下,检测一下自己是否已经掌握。两道题分别是: 252. 会议室 253. 会议室 II 总结我们对 LeetCode 上的专题《我的日程安排》的三道题进行了汇总。对于区间判断是否重叠,我们可以反向判断,也可以正向判断。 暴力的方法是每次对所有的课程进行判断是否重叠,这种解法可以 AC。我们也可以进一步优化,使用二叉查找树来简化时间复杂度。最后我们介绍了一种 Count-Map 方法来通用解决所有的问题,不仅可以完美解决这三道题,还可以扩展到《会议室》系列的两道题。","categories":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"}],"tags":[{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"算法系列","slug":"算法系列","permalink":"https://lucifer.ren/blog/tags/算法系列/"}]},{"title":"BigPipe和微前端","slug":"bigpipe-and-micro-fe","date":"2020-02-01T16:00:00.000Z","updated":"2023-01-07T12:20:39.942Z","comments":true,"path":"2020/02/02/bigpipe-and-micro-fe/","link":"","permalink":"https://lucifer.ren/blog/2020/02/02/bigpipe-and-micro-fe/","excerpt":"你可能听说过 BigPipe,这是一个十多年前的技术,而 BigPipe 通常都会跟“性能优化”同时被提起。微前端也是一个很早被提出的技术,但是最近几年才开始比较流行。而目前微前端能够解决的最大的问题恐怕就是遗留系统改造。我们可以将新技术构造的系统和旧技术构造的系统完美融合到一起,彼此构建,发布,运行等不受干扰。 那么 BigPipe 究竟和微前端有什么关系呢,我为什么要把这两个放到一起来看?","text":"你可能听说过 BigPipe,这是一个十多年前的技术,而 BigPipe 通常都会跟“性能优化”同时被提起。微前端也是一个很早被提出的技术,但是最近几年才开始比较流行。而目前微前端能够解决的最大的问题恐怕就是遗留系统改造。我们可以将新技术构造的系统和旧技术构造的系统完美融合到一起,彼此构建,发布,运行等不受干扰。 那么 BigPipe 究竟和微前端有什么关系呢,我为什么要把这两个放到一起来看? 回答这个问题之前,我们先来看下什么是 BigPipe,以及什么是微前端。 BigPipeBigPipe 最早上 FaceBook 用来提升自家网站性能的一个秘密武器。其核心思想在于将页面分成若干小的构件,我们称之为 pagelet。每一个构件之间并行执行。 那么 BigPipe 做了什么?和传统方式有什么不同呢?我们知道浏览器处理我们的 HTML 文档以及其中包含的 CSS,JS 等资源的时候是从上到下串行执行的。如果我们把浏览器处理的过程划分为若干阶段(stage),那么这些阶段之间有着明显的时间先后关系。那么我们能不能将其并行化,从而减少时间呢?这就是 BigPipe 的基本思想。 话不多说,我们通过一段代码来帮助大家理解,比如你的项目首页是 home.html,大概这样子: 1234567891011121314151617<!DOCTYPE html><html> <head> <script> window.BigPipe = { render(selector, content) { document.querySelector(selector).innerHTML = content; } }; </script> </head> <body> <div id=\"pagelet1\"></div> <div id=\"pagelet2\"></div> <div id=\"pagelet3\"></div> </body></html> 浏览器首先加载过来就是一个占位元素,这部分没有 JS 和 CSS,只有 HTML 部分,因此会很快。 之后我们慢慢填充pagelet1,pagelet2, pagelet3,在用户看来,就是一种“渐进式渲染”的效果。 服务端代码大概是: 12345678910111213141516171819202122232425const app = require('express')();const fs = require('fs');// 模拟真实场景function wirteChunk(content, delay, res) { return new Promise(r => { setTimeout(function() { res.write(content); delay); })}app.get('/', function (req, res) { // 为了简化代码,直接同步读。 强烈不建议生产环境这么做! res.write(fs.readFileSync(__dirname + \"/home.html\").toString()); const p1 = wirteChunk('<script>BigPipe.render(\"#pagelet1\",\"hello\");</script>', 1000) const p2 = wirteChunk('<script>BigPipe.render(\"#pagelet2\",\"word\");</script>', 2000) const p3 = wirteChunk('<script>BigPipe.render(\"#pagelet3\",\"!\");</script>', 3000) Promise.all([p1, p2, p3]).then(res.end)});app.listen(3000); 从这里我们可以看出,BigPipe 不是框架,不是新技术。我们只需要按照这样做就行了。 这对于页面可以细分为多个块,块之间关联不大的场景非常有用。如果还是不太明白,可以看下这篇文章 -bigpipe-pipelining-web-pages-for-high-performance 说完了 BigPipe,我们再来看一下微前端。 微前端和后端微服务类似,“微前端是一种架构风格,其中众多独立交付的前端应用组合成一个大型整体。” 如果你想做微前端,一定要能够回答出这 10 个问题。 微应用的注册、异步加载和生命周期管理; 微应用之间、主从之间的消息机制; 微应用之间的安全隔离措施; 微应用的框架无关、版本无关; 微应用之间、主从之间的公共依赖的库、业务逻辑(utils)以及版本怎么管理; 微应用独立调试、和主应用联调的方式,快速定位报错(发射问题); 微应用的发布流程; 微应用打包优化问题; 微应用专有云场景的出包方案; 渐进式升级:用微应用方案平滑重构老项目。 这里有一篇文档,区别与别的微前端文章的点在于其更加靠近规范层面,而不是结合自己的业务场景做的探索。这篇文章来自于阿里团队。 文章地址: https://mp.weixin.qq.com/s/rYNsKPhw2zR84-4K62gliw 还有一篇文章也不错,一并推荐给大家 - 大前端时代下的微前端架构:实现增量升级、代码解耦、独立部署 微前端中有一个重要的需要解决的问题是子系统之间的路由。而我们的 BigPipe 如果被当作一个个子应用的,那不就是微前端中的一个点么?BigPipe 也好,微前端也好,都是一种概念,一种指导思想。微前端是不限于技术栈的, 你可以使用传统的 ssr,也可以使用 csr,也可以使用现代 csr + ssr 等,框架也可以五花八门。 如何将这些系统组合起来,并且能够有条不紊地进行合作完成一个完整的应用?这是微前端所研究和要解决的问题。 对于微前端,我们隔离各个应用的方式有几种: iframe 类似 bigpipe 这种客户端异步加载技术 web-components 不管采用哪种方式,我们的大体逻辑都是: 先加载主框架 异步加载各个子应用 只不过加载子应用,我们可以通过 iframe 去加载,也可以使用 web-component 去加载,也可以使用类似 bigpipe 的方式分段并行加载。我们甚至可以将这几者进行结合使用。而 iframe 和 web-compoents 顺带解决了诸如 js 和 css 等隔离的作用,而 bigPipe 只是对资源加载的一个有效控制,其本身并没有什么特殊含义,更不要说诸如 js 和 css 等隔离作用了。 事物关联当前端有了 Nodejs 之后,我们发现可以做的事情变多了,除了 BigPipe,我们又去做 ssr,又要做 graphql,还要做微前端,海报服务,AI 等等。当你从大的视角看的时候,会发现这些技术或多或少都有交集,比如我刚才提到的 ssr。 我们知道 ssr 中有一点就是我们先返回给用户一个有内容的 html,这个 html 在服务端生成,由于在服务端生成,因此只有样式,没有绑定事件,所以后续我们需要在客户端合成事件。 如果将上面 BigPipe 的代码拿过来看的话,会发现我们的 html markup 可以看作服务端渲染内容(可以是直接写死的,也可以是服务端动态生成的)。之后我们输出后续 pagelet 的 JS 代码到前端,前端继续去执行。基于 BigPipe 我们甚至可以控制页面优先级显示。我们再继续去看的话, BFF 常见的一个功能“合并请求”在这里扮演了什么样的角色?大家可以自己想一下。当你不断从不同角度思考问题,会发现很多东西都有关联。每一个技术背后往往都会落到几个基本的原理上。了解技术初始产生背后解决的问题对于掌握一个技术来说非常重要。","categories":[],"tags":[{"name":"BigPipe","slug":"BigPipe","permalink":"https://lucifer.ren/blog/tags/BigPipe/"},{"name":"微前端","slug":"微前端","permalink":"https://lucifer.ren/blog/tags/微前端/"}]},{"title":"我是如何刷 LeetCode 的?","slug":"how-am-I-conque-leetcode","date":"2020-02-01T16:00:00.000Z","updated":"2023-01-07T12:34:34.207Z","comments":true,"path":"2020/02/02/how-am-I-conque-leetcode/","link":"","permalink":"https://lucifer.ren/blog/2020/02/02/how-am-I-conque-leetcode/","excerpt":"我就是那个 @量子位 答案里面提到的“lucifer 小哥哥”。 我本人从开始准备算法以来,大概经过了几个月的时间,这期间自己成长了很多,从刷题菜鸡,到现在对刷题套路,题型有了自己的理解,感受还是蛮多的。我本人不是算法高手,算是勤能补拙类型。不过经过几个月的学习和练习,不仅面试变得更加得心应手,而且在工作中写更容易写出干净优雅,性能好的代码。","text":"我就是那个 @量子位 答案里面提到的“lucifer 小哥哥”。 我本人从开始准备算法以来,大概经过了几个月的时间,这期间自己成长了很多,从刷题菜鸡,到现在对刷题套路,题型有了自己的理解,感受还是蛮多的。我本人不是算法高手,算是勤能补拙类型。不过经过几个月的学习和练习,不仅面试变得更加得心应手,而且在工作中写更容易写出干净优雅,性能好的代码。 我将自己这几个月的刷题经历都整理了下来,除了给出思路和关键点,还横向地对知识点进行整理,尽量做到一题多解,多题同解。 现在 GitHub 仓库有 18k+的 ✨ , 欢迎大家关注。仓库地址: azl397985856/leetcode 那么今天我就来回答一下这个问题,谈一下我是怎么刷leetcode的。 对于我来说,刷题的过程其实就是学习数据结构和算法的过程, 不仅仅是为了刷题而刷题,这样你才能感受到刷题的乐趣。 第一遍按 tag 刷,第二遍一题多解,多题同解个人建议,第一遍刷的时候可以先快速按照 tag 过一遍,快速感受一下常见数据结构和算法的套路,这样自己有一个感性的认识。 第二遍我们就不能像第一遍那样了,这个阶段我们需要多个角度思考问题,尽量做到一题多解,多题同解。我们需要对问题的本质做一些深度的理解,将来碰到类似的问题我们才能够触类旁通。 但是很多人做了几遍,碰到新题还是没有任何头绪,这是一个常见的问题,这怎么办呢? 我们继续往下看。 艾宾浩斯记忆曲线总结并记忆是学习以及刷题过程中非常重要的一环,不管哪个阶段,我们都需要做对应的总结,这样将来我们再回过头看的时候,才能够快读拾起来。 信息输入大脑后,遗忘也就随之开始了。遗忘率随时间的流逝而先快后慢,特别是在刚刚识记的短时间里,遗忘最快,这就是著名的艾宾浩斯遗忘曲线。 anki 就是根据艾宾浩斯记忆曲线开发的一个软件,它是一个使记忆变得容易的学习软件。因为它是一个自定义多功能的记忆方式,可以大大减少你的学习时间,也可以大大提高 你的学习容量。 对于我本人而言,我在 anki 里面写了很多 leetcode 题目和套路的 Card,然后 anki 会自动帮我安排复习时间,大大减少我的认知负担,提高了我的复习效率。 这个是我的anki card 大家可以直接导入使用,但是还是建议大家自己制作卡片,毕竟每个人情况不一样,并且制作卡片的过程也是记忆的过程。 使用方法: anki - 文件 - 导入 - 下拉格式选择“打包的 anki 集合”,然后选中你下载好的文件,确定即可。 更多关于 anki 使用方法的请查看anki 官网 目前已更新卡片一览(仅列举正面) 二分法解决问题的关键点是什么,相关问题有哪些? 如何用栈的特点来简化操作, 涉及到的题目有哪些? 双指针问题的思路以及相关题目有哪些? 滑动窗口问题的思路以及相关题目有哪些? 回溯法解题的思路以及相关题目有哪些? 数论解决问题的关键点是什么,相关问题有哪些? 位运算解决问题的关键点是什么,相关问题有哪些? 殊途同归大家刷了很多题之后,就会发现来来回回,题目就那么几种类型,因此掌握已有题目类型是多么重要。那样 leetcode 出题的老师,很多也是在原有的题目基础上做了适当扩展(比如 two-sum,two-sum2,three-sum, four-sum 等等)或者改造(使得不那么一下子看出问题的本质,比如猴子吃香蕉问题)。 其中算法,主要是以下几种: 基础技巧:分治、二分、贪心 排序算法:快速排序、归并排序、计数排序 搜索算法:回溯、递归、深度优先遍历,广度优先遍历,二叉搜索树等 图论:最短路径、最小生成树 动态规划:背包问题、最长子序列 数据结构,主要有如下几种: 数组与链表:单 / 双向链表 栈与队列 哈希表 堆:最大堆 / 最小堆 树与图:最近公共祖先、并查集 字符串:前缀树(字典树) / 后缀树 (图片来自 leetcode) 坚持做到了以上几点,我们还需要坚持。这个其实是最难的,不管做什么事情,坚持都是最重要也是最难的。 为了督促自己,同时帮助大家成长,我在群里举办《每日一题》活动,每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块。 大家如果发现自己很难坚持下去,也可以加入我的群聊,我们互相监督。 另外我还专门组建了 slack 群,有兴趣的可以加群后在群里喊即可。 关注我大家可以关注我的公众号《脑洞前端》,公众号后台回复“大前端”,拉你进《大前端面试宝典 - 图解前端群》。回复“leetcode”,拉你进《leetcode 题解交流群》 最后祝大家刷题愉快,拿到自己心仪的 offer。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/categories/学习方法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"学习方法","slug":"学习方法","permalink":"https://lucifer.ren/blog/tags/学习方法/"}]},{"title":"【LeetCode日记】 335. 路径交叉","slug":"335.self-crossing","date":"2020-01-30T16:00:00.000Z","updated":"2023-01-05T12:24:49.554Z","comments":true,"path":"2020/01/31/335.self-crossing/","link":"","permalink":"https://lucifer.ren/blog/2020/01/31/335.self-crossing/","excerpt":"这是一道 Hard 难度的题目,题目的难点在于我们不可以使用额外的空间完成。让我们来看下怎么做吧。 ​","text":"这是一道 Hard 难度的题目,题目的难点在于我们不可以使用额外的空间完成。让我们来看下怎么做吧。 ​ 原题地址:https://leetcode-cn.com/problems/self-crossing/ 题目描述123456789101112131415161718192021222324252627282930313233给定一个含有 n 个正数的数组 x。从点 (0,0) 开始,先向北移动 x[0] 米,然后向西移动 x[1] 米,向南移动 x[2] 米,向东移动 x[3] 米,持续移动。也就是说,每次移动后你的方位会发生逆时针变化。编写一个 O(1) 空间复杂度的一趟扫描算法,判断你所经过的路径是否相交。 示例 1:┌───┐│ │└───┼──> │输入: [2,1,1,2]输出: true示例 2:┌──────┐│ │││└────────────>输入: [1,2,3,4]输出: false示例 3:┌───┐│ │└───┼>输入: [1,1,1,1]输出: true 思路符合直觉的做法是$O(N)$时间和空间复杂度的算法。这种算法非常简单,但是题目要求我们使用空间复杂度为$O(1)$的做法。 关于空间复杂度为$O(N)$的算法可以参考我之前的874.walking-robot-simulation。 思路基本是类似,只不过 obstacles(障碍物)不是固定的,而是我们不断遍历的时候动态生成的,我们每遇到一个点,就将其标记为 obstacle。随着算法的进行,我们的 obstacles 逐渐增大,最终和 N 一个量级。 我们考虑进行优化。我们仔细观察发现,如果想让其不相交,从大的范围来看只有两种情况: 我们画的圈不断增大。 我们画的圈不断减少。 (有没有感觉像迷宫?) 这样我们会发现,其实我们画最新一笔的时候,并不是之前画的所有的都需要考虑,我们只需要最近的几个就可以了,实际上是最近的五个,不过不知道也没关系,我们稍后会讲解。 红色部分指的是我们需要考虑的,而剩余没有被红色标注的部分则无需考虑。不是因为我们无法与之相交,而是我们一旦与之相交,则必然我们也一定会与红色标记部分相交。 然而我们画的方向也是不用考虑的。比如我当前画的方向是从左到右,那和我画的方向是从上到下有区别么?在这里是没区别的,不信我帮你将上图顺时针旋转 90 度看一下: 方向对于我们考虑是否相交没有差别。 当我们仔细思考的时候,会发现其实相交的情况只有以下几种: 这个时候代码就呼之欲出了。 我们只需要遍历数组 x,假设当前是第 i 个元素。 如果 x[i] >= x[i - 2] and x[i - 1] <= x[i - 3],则相交(第一种情况) 如果 x[i - 1] <= x[i - 3] and x[i - 2] <= x[i],则相交(第二种情况) 如果 i > 3 and x[i - 1] == x[i - 3] and x[i] + x[i - 4] == x[i - 2],则相交(第三种情况) 如果 i > 4 and x[i] + x[i - 4] >= x[i - 2] and x[i - 1] >= x[i - 3] - x[i - 5] \\ and x[i - 1] <= x[i - 3] and x[i - 2] >= x[i - 4] and x[i - 3] >= x[i - 5] ,则相交(第四种情况) 否则不相交 关键点解析 一定要画图辅助 对于这种$O(1)$空间复杂度有固定的套路。常见的有: 直接修改原数组 滑动窗口(当前状态并不是和之前所有状态有关,而是仅和某几个有关)。 我们采用的是滑动窗口。但是难点就在于我们怎么知道当前状态和哪几个有关。对于这道题来说,画图或许可以帮助你打开思路。另外面试的时候说出$O(N)$的思路也不失为一个帮助你冷静分析问题的手段。 代码代码支持:Python3 Python3 Code: 12345678910111213141516class Solution: def isSelfCrossing(self, x: List[int]) -> bool: n = len(x) if n < 4: return False for i in range(3, n): if x[i] >= x[i - 2] and x[i - 1] <= x[i - 3]: return True if x[i - 1] <= x[i - 3] and x[i - 2] <= x[i]: return True if i > 3 and x[i - 1] == x[i - 3] and x[i] + x[i - 4] == x[i - 2]: return True if i > 4 and x[i] + x[i - 4] >= x[i - 2] and x[i - 1] >= x[i - 3] - x[i - 5] \\ and x[i - 1] <= x[i - 3] and x[i - 2] >= x[i - 4] and x[i - 3] >= x[i - 5]: return True return False","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"就地算法","slug":"算法/就地算法","permalink":"https://lucifer.ren/blog/categories/算法/就地算法/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/categories/Hard/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Hard","slug":"Hard","permalink":"https://lucifer.ren/blog/tags/Hard/"}]},{"title":"【LeetCode日记】 50. Pow(x, n)","slug":"50.powx-n","date":"2020-01-28T16:00:00.000Z","updated":"2023-01-05T12:24:49.767Z","comments":true,"path":"2020/01/29/50.powx-n/","link":"","permalink":"https://lucifer.ren/blog/2020/01/29/50.powx-n/","excerpt":"这是一道让我们实现系统函数的造轮子题目,我们来看下。 ​","text":"这是一道让我们实现系统函数的造轮子题目,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/powx-n/description/ 题目描述12345678910111213141516171819实现 pow(x, n) ,即计算 x 的 n 次幂函数。示例 1:输入: 2.00000, 10输出: 1024.00000示例 2:输入: 2.10000, 3输出: 9.26100示例 3:输入: 2.00000, -2输出: 0.25000解释: 2-2 = 1/22 = 1/4 = 0.25说明:-100.0 < x < 100.0n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。 解法零 - 遍历法思路这道题是让我们实现数学函数幂,因此直接调用系统内置函数是不被允许的。 符合直觉的做法是将x乘以n次,这种做法的时间复杂度是$O(N)$。 经实际测试,这种做法果然超时了。测试用例通过 291/304,在 0.00001\\n2147483647这个测试用例挂掉了。如果是面试,这个解法可以作为一种兜底解法。 代码语言支持: Python3 Python3 Code: 12345678910class Solution: def myPow(self, x: float, n: int) -> float: if n == 0: return 1 if n < 0: return 1 / self.myPow(x, -n) res = 1 for _ in range(n): res *= x return res 解法一 - 普通递归(超时法)思路首先我们要知道: 如果想要求 x ^ 4,那么我们可以求 (x^2)^2 如果是奇数,会有一点不同。 比如 x ^ 5 就等价于 x * (x^2)^2。 当然 x ^ 5 可以等价于 (x ^ 2) ^ 2.5, 但是这不相当于直接调用了幂函数了么。对于整数,我们可以很方便的模拟,但是小数就不方便了。 我们的思路就是: 将 n 地板除 2,我们不妨设结果为 a 那么 myPow(x, n) 就等价于 myPow(x, a) * myPow(x, n - a) 很可惜这种算法也会超时,原因在于重复计算会比较多,你可以试一下缓存一下计算看能不能通过。 如果你搞不清楚有哪些重复计算,建议画图理解一下。 代码语言支持: Python3 Python3 Code: 123456789class Solution: def myPow(self, x: float, n: int) -> float: if n == 0: return 1 if n == 1: return x if n < 0: return 1 / self.myPow(x, -n) return self.myPow(x, n // 2) * self.myPow(x, n - n // 2) 解法二 - 优化递归思路上面的解法每次直接 myPow 都会调用两次自己。我们不从缓存计算角度,而是从减少这种调用的角度来优化。 我们考虑 myPow 只调用一次自身可以么? 没错,是可以的。 我们的思路就是: 如果 n 是偶数,我们将 n 折半,底数变为 x^2 如果 n 是奇数, 我们将 n 减去 1 ,底数不变,得到的结果再乘上底数 x 这样终于可以 AC。 代码语言支持: Python3 Python3 Code: 123456789class Solution: def myPow(self, x: float, n: int) -> float: if n == 0: return 1 if n == 1: return x if n < 0: return 1 / self.myPow(x, -n) return self.myPow(x _ x, n // 2) if n % 2 == 0 else x _ self.myPow(x, n - 1) 解法三 - 位运算思路我们来从位(bit)的角度来看一下这道题。如果你经常看我的题解和文章的话,可能知道我之前写过几次相关的“从位的角度思考分治法”,比如 LeetCode 458.可怜的小猪。 以 x 的 10 次方举例。10 的 2 进制是 1010,然后用 2 进制转 10 进制的方法把它展成 2 的幂次的和。 因此我们的算法就是: 不断的求解 x 的 2^0 次方,x 的 2^1 次方,x 的 2^2 次方等等。 将 n 转化为二进制表示 将 n 的二进制表示中1的位置pick 出来。比如 n 的第 i 位为 1,那么就将 x^i pick 出来。 将 pick 出来的结果相乘 这里有两个问题: 第一个问题是似乎我们需要存储 x^i 以便后续相乘的时候用到。实际上,我们并不需要这么做。我们可以采取一次遍历的方式来完成,具体看代码。 第二个问题是,如果我们从低位到高位计算的时候,我们如何判断最高位置是否为 1?我们需要一个 bitmask 来完成,这种算法我们甚至需要借助一个额外的变量。 然而我们可以 hack 一下,直接从高位到低位进行计算,这个时候我们只需要判断最后一位是否为 1 就可以了,这个就简单了,我们直接和 1 进行一次与运算即可。 代码语言支持: Python3 Python3 Code: 1234567891011class Solution: def myPow(self, x: float, n: int) -> float: if n < 0: return 1 / self.myPow(x, -n) res = 1 while n: if n & 1 == 1: res *= x x *= x n >>= 1 return res 关键点解析 超时分析 hashtable 数学分析 位运算 二进制转十进制 相关题目 458.可怜的小猪","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"hashtable","slug":"数据结构/hashtable","permalink":"https://lucifer.ren/blog/categories/数据结构/hashtable/"},{"name":"数学","slug":"算法/数学","permalink":"https://lucifer.ren/blog/categories/算法/数学/"},{"name":"位运算","slug":"算法/位运算","permalink":"https://lucifer.ren/blog/categories/算法/位运算/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"}]},{"title":"累死累活干不过一个写PPT的","slug":"ppt-data","date":"2020-01-19T16:00:00.000Z","updated":"2023-01-07T12:34:34.404Z","comments":true,"path":"2020/01/20/ppt-data/","link":"","permalink":"https://lucifer.ren/blog/2020/01/20/ppt-data/","excerpt":"无论是身处什么行业什么领域,数据分析越来越成为一向必不可少的技能,而运用数据思维进行决策更能产生形成高质量的决策结果。 随着互联网的不断发展和物联网设备的不断普及,我们日常生活中的各种数据被存储下来,让我们可以通过定量分析数据,利用数据实现更好的决策制定。","text":"无论是身处什么行业什么领域,数据分析越来越成为一向必不可少的技能,而运用数据思维进行决策更能产生形成高质量的决策结果。 随着互联网的不断发展和物联网设备的不断普及,我们日常生活中的各种数据被存储下来,让我们可以通过定量分析数据,利用数据实现更好的决策制定。 现在越来越多的公司开始注重这一块,一方面自建数据体系,一方面去买一些数据。而对于我们个人似乎还没有意识到或者开始挖掘数据对我们的价值。 笔者最近的工作大都是做一些基础设施搭建和流程优化相关的工作。这部分工作对很多人来说都是“隐形”的,对上层使用者来说很难有很大的感知。对于领导来说,如果你只是闷头去做事情,他们也是很难知道你干的怎么样,如果这之间再加上你没有什么反馈,就会给同事和领导一种“不靠谱”的感觉。 因此给予反馈和直观展示自己劳动成果的能力就显得非常重要。然而如果你能很好展示自己的劳动成果,那么只需要将这个给老板看就是一种很好很直观的反馈。 这篇文章,我们来谈一下,如何量化我们的工作,如何将我们的工作成果展示出来。如何让同事,让领导体会到我们工作的成果。我会通过几个例子来帮助大家快速理解,以及掌握这门“技术”。 《让数据开口说话》是我给这篇文章的标题,让数据开口说话,你就可以少说一点,并且摆数据就是摆事实,数据带来的说服力要比你说的话强很多。 关于我我是一个对技术充满兴趣的程序员, 擅长前端工程化,前端性能优化,前端标准化等。 做过.net, 搞过 Java,现在是一名前端工程师。 除了我的本职工作外,我会在开源社区进行一些输出和分享,GitHub 共计获得 1.5W star。比较受欢迎的项目有leetcode 题解 , 宇宙最强的前端面试指南 和我的第一本小书 收集数据如果让数据开头说话,那么首先第一步你要有数据。 因此我们的第一步就是收集数据,那么在这之前你需要知道你需要什么数据。这部分的内容随着每个人任务不同肯定是不一样的。因此有着很大的灵活性, 有一个指导思想就是对关键指标分解。比如我现在要做打包时间进行优化,那么打包时间由哪些时间决定。 1打包时间 = 阶段1 时间 + 阶段2 时间 + 阶段3 时间 我们减少打包时间肯定要减少其中一个或多个。 有时候我们无法找到这种简单的分解,那就教大家另外一个技巧:运用对比。 一方面可以基于时间进行对比,比如环比增长,同比增长等数据都是这么来的。 另一方面我们可以基于用户属性进行对比,比如用户年龄,性别,偏好,操作系统类型,地域属性等。 下面我举几个例子。 打包优化假如你被分配了一个任务。让你对项目的打包过程进行优化。 你需要对打包时间进行优化,减少打包的时间 你需要对打包的最终产物进行优化,减少打出的包的包体大小。 将打包变得尽可能的简单,也就说尽量减少人为的操作过程。 你接到了这样一个任务,你会如何去做? 这里我们不考虑具体的具体思路和细节。 假设你的架构思路,方案规划,各种 fallback 已经想好了。我们如何通过上面提到的让数据说话的角度来收集数据呢? 换句话说,我们需要收集哪些数据? 打包时间对于打包时间的数据,最简单的我们计算一下总体的打包时间。 最后我们只需要对比优化前后的总体打包时间差异即可。 这对于老板来说可能已经够了,但是这缺乏一些精确性,我们无法知道通过优化了哪个环节进行减少了打包时间。 因此一种简单的改进是将打包划分为多个阶段,每个阶段分别进行统计计时 ⌛️ 。 包的大小包的大小的数据其实和上面讲的打包时间思路类似。 我们当然可以只统计总体包大小。 但是为了获得更加灵活的定制和更加精确的范围我们可以对包进行一定的划分。这个划分可以是业务纬度,也可以是纯技术纬度。 打包命令这部分比较简单,我们只需要简单地统计手动操作的次数即可。 通过收集以上的数据,我们就可以用数据来表示我们的成果,让数据说话,关于如何使用这些数据,我们稍后讨论。 页面加载性能优化假如你被分配了一个任务。让你对项目的页面加载速度进行优化。你会怎么做? 这个任务有点太宽泛了,更多的时候会有一些更精确的指标,比如将网络状态为fast 3G的中端机型的白屏时间降低到3s以内。 timing性能优化的第一步就是测量,没有测量就没有优化。我们不能为了优化而优化, 而是看到了某些点需要优化才去优化的。 而发现这个点一个重要的方式就是要靠测量。 说到测量,普遍接受的方式是,在浏览器中进行打点,将浏览器打开网页的过程看作是一个旅行。那么我们每到一个地方就拍张带有时间的照片(事件),最后我们对这些照片按照时间进行排列, 然后分析哪部分是我们的瓶颈点等。 有了这些 timing 我们可以很方便的计算各项性能指标。我们还可以自定义一些我们关心的指标,比如请求时间(成功和失败分开统计),较长 js 操作时间,或者比较重要的功能等。 总之收集到这些数据之后,我们只需要根据我们的需求去定制一些指标即可。 这样我们就很容易展示出这样的画面: 人效提升假如你是一个项目的管理者,上级分配给你一个任务,要在未来几个季度去做“研发效率提升”,也就是提高“交付速度”。 你会怎么做这件事? 任务这个事情是比较主观的了,因此我们切实需要一些可以量化的东西来辅助我们。 我们考虑将需求进行拆分,变成一个个任务。一个需求可能有多个任务。 我们考虑对每一个任务进行计时,而不是需求,因为需求有太大的差异。我们可以针对任务进行分类,然后我们的目标就可以变成“减少同类任务的交付时长”。 但是这种粒度似乎还是有点大。我们可以采取标签的形式,对任务进行交叉分类。 任务纬度可能还是有点太大,我们可以采取更小的粒度划分,比如模块和组件。 这样我们的统计纬度就丰富起来了,我们不仅可以总体进行统计分析,我们还可以根据 tag 和 tag 的组合进行汇总。 比如一个典型的统计结果大概是: 12345678- task1 (tagA) - module1 (tagA) - component1 (tagB) - component2 (tagA) - module2 (tagB) - module3 (tagB)- task2 (tagA)- task3 (tagC) 比如这里有一种 tag 叫“是否复用了以前的代码”,那么我们就很容易统计出组件复用率,也就很容易很直观地知道前后的差距了。 用户拉新和留存再比如我们需要做“用户拉新和留存”,我们应该怎么做? 这个留做思考题,大家可以思考一下。 我这里抛砖引玉一下,比如我们的统计纬度可以是: 12345- 用户访问时长 (tagA)- 跳出率 (tagB)- 新用户 (tagA)- 流失的老用户 (tagB)- 地址位置 (tagA) 假如我的 tag 有两个分别是 用户 id 和时间, 我们就可以方便地统计每个用户的活动数据趋势。 让数据说话有了数据,我们如何通过数据来增强表现力呢? 一种非常有效的措施是可视化。现在的可视化引擎和工具有很多,功能也非常复杂。 但是我发现我个人需要的就那么几个,可能大家每个人需要的种类不大一样,但是我相信作为个人,你需要的种类不会很多。因此自己根据自身的实际情况,挑选适合自己的几种类型,做到迎刃有余就足够了。 对于我而言,我常用的是饼图,用来表示分布关系。 曲线图用来表示趋势。用柱状图表示对比+趋势。用热度图表示离散的数据分布等等。 我们可以使用一些现有的成熟的产品来帮助我们将刚才我们收集到的数据转化为各种图表,比如 画布 这个网站能做的图表种类比较少。 当然作为一名前端我们也可以自己写代码去更灵活地展示我们的数据,比如D3或者百度的echarts 任何类型的图表都可以做,只有你想不到,没有它做不到。 相对折中一点,我们可以选择支持代码定制的一些产品,在特殊情况我们可以自定义。 累死累活干不过做 PPT 的有了这些数据图表,是时候写一份 PPT 来秀一下了。 一种方式是使用你电脑的办公软件或者一些在线的幻灯片制作工具做,比如slides 。 另一种方式通过写代码的方式实现,作为程序员我推荐使用第二种。这里推荐一款nodejs cli 工具 nodeppt,还有另外一个JS 框架 reveal.js 。上面提到的 slides 背后的原理就是它。 总结这篇文章我主要讲述了如何量化我们的工作,并将我们的工作成果展示出来。从而摆脱“干了很多事情,却说不出来,甚至功劳被人无情拿走的尴尬局面”。 首先我们将了如何收集数据,收集数据的一些技巧,这里通过几个实际工作的例子,分别是“打包优化”,“性能优化”,“人效提升”,“用户留存” ,来帮助大家理解这个过程,掌握这个技巧。 有了数据之后,我们需要通过一些手段将其数据展示出来,给人直观的感受,最好有视觉冲击感。这里我推荐了几个工具和平台,大家可以根据自己的情况选择。 最后结合我们实际情况,PPT 是一个很好的展示自己的东西,不管是晋升还是宣传都是很好的方式,这里我也推荐了几个产品,帮助大家更快更好地将图表展示出来。 让数据开口说话,你就可以少说一点,并且摆数据就是摆事实,数据带来的说服力要比你说的话强很多。 关注我最近我重新整理了下自己的公众号,并且我还给他换了一个名字《脑洞前端》,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解前端是我的目标。 之后我的文章同步到微信公众号 脑洞前端 ,您可以关注获取最新的文章,或者和我进行交流。 交流群现在还是初级阶段,需要大家的意见和反馈,为了减少沟通成本,我组建了交流群。大家可以扫码进入 QQ 群 微信群 (由于微信的限制,100 个人以上只能邀请加入, 你可以添加我的机器人回复“大前端”拉你进群)","categories":[],"tags":[{"name":"技能","slug":"技能","permalink":"https://lucifer.ren/blog/tags/技能/"},{"name":"PPT","slug":"PPT","permalink":"https://lucifer.ren/blog/tags/PPT/"}]},{"title":"一文搞懂《链表反转》","slug":"reverseList","date":"2020-01-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.728Z","comments":true,"path":"2020/01/20/reverseList/","link":"","permalink":"https://lucifer.ren/blog/2020/01/20/reverseList/","excerpt":"翻转链表一直都是热门题目,笔者就在某大型互联网公司的面试题中碰到过这种题目,这种题目很常常见,相对应的变形和扩展也很多,今天我们就来攻克它吧。","text":"翻转链表一直都是热门题目,笔者就在某大型互联网公司的面试题中碰到过这种题目,这种题目很常常见,相对应的变形和扩展也很多,今天我们就来攻克它吧。 反转链表反转链表是这个系列中最简单的了,没有别的要求,就是将一个链表从头到尾进行反转,最后返回反转后的链表即可。 我们来看一个 LeetCode 题目, 206. 反转链表, 官方难度为 Easy。 题目描述12345678反转一个单链表。示例:输入: 1->2->3->4->5->NULL输出: 5->4->3->2->1->NULL进阶:你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 思路链表的翻转过程,初始化一个为 null 的 previous node(prev),然后遍历链表的同时,当前 node (curr)的下一个(next)指向前一个 node(prev), 在改变当前 node 的指向之前,用一个临时变量记录当前 node 的下一个 node(curr.next). 即 1234ListNode temp = curr.next;curr.next = prev;prev = curr;curr = temp; 举例如图:翻转整个链表 1->2->3->4->null -> 4->3->2->1->null 代码我们直接来看下代码: 12345678910111213141516171819202122232425/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } *//** * @param {ListNode} head * @return {ListNode} */function reverseList(head) { if (!head || !head.next) return head; let cur = head; let pre = null; while (cur) { const next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre;} 这里不再赘述,如果不理解,想看更多更详细内容,请参考我的LeetCode 题解 - 206.reverse-linked-list 分组反转这个题目和上面的有点类似,只不过我们并不是从头到尾反转,而是每 k 个为一组进行反转。LeetCode 同样有原题25. K 个一组翻转链表官方难度为 Hard。 题目描述123456789101112131415161718给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。示例 :给定这个链表:1->2->3->4->5当 k = 2 时,应当返回: 2->1->4->3->5当 k = 3 时,应当返回: 3->2->1->4->5说明 :你的算法只能使用常数的额外空间。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 思路我们的思路还是一样的,我们把每 k 位的位置当成是尾节点即可。 我们的任务就是每次反转头尾之间的所有节点,然后将链表重新拼起来即可。 我们先来写一下反转头尾之间的所有节点这个方法。 1234567891011121314151617181920// 翻转head到tail之间的部分,不包括head和tail// 返回原链表的第一个元素,也就是翻转后的最后一个元素function reverseList(head, tail) { if (head === null || head.next === null) return head; let cur = head.next; first = cur; let pre = head; // 这里就是翻转不包括head的原因,否则就是head.pre了(当然我们没有pre指针) // 这里就是翻转不包括tail的原因,否则就是tail.next了。 while (cur !== tail) { const next = cur.next; cur.next = pre; pre = cur; cur = next; } // 拼接 head.next = pre; first.next = cur; return first;} 这里的反转不包括 head 和 tail,并不是我一开始的思路,但是在慢慢想的过程,发现这样写代码会更优雅。 上面的代码如果是 head 是我们的头节点,tail 是 null,那么就等效于上面的那道题。也就是说我们的这个 k 分组是上面题目的一般形式,当 k 为链表长度的时候,就会变成上面那道题了。 还有一点不同的是,我们每次反转之后都要对链表进行拼接,这是上面那个反转所没有的,这里要注意一下。 12head.next = pre;first.next = cur; 这里是对每一组(k个nodes)进行翻转, 先分组,用一个count变量记录当前节点的个数 用一个start 变量记录当前分组的起始节点位置的前一个节点 用一个end变量记录要翻转的最后一个节点位置 翻转一组(k个nodes)即(start, end) - start and end exclusively。 翻转后,start指向翻转后链表, 区间(start,end)中的最后一个节点, 返回start 节点。 如果不需要翻转,end 就往后移动一个(end=end.next),每一次移动,都要count+1. 如图所示 步骤 4 和 5: 翻转区间链表区间(start, end) 举例如图,head=[1,2,3,4,5,6,7,8], k = 3 NOTE: 一般情况下对链表的操作,都有可能会引入一个新的dummy node,因为head有可能会改变。这里head 从1->3,dummy (List(0))保持不变。 这种做法的时间复杂度为 O(n),空间复杂度为 O(1)。 代码Java 代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455class ReverseKGroupsLinkedList { public ListNode reverseKGroup(ListNode head, int k) { if (head == null || k == 1) { return head; } ListNode dummy = new ListNode(0); dummy.next = head; ListNode start = dummy; ListNode end = head; int count = 0; while (end != null) { count++; // group if (count % k == 0) { // reverse linked list (start, end] start = reverse(start, end.next); end = start.next; } else { end = end.next; } } return dummy.next; } /** * reverse linked list from range (start, end), return last node. * for example: * 0->1->2->3->4->5->6->7->8 * | | * start end * * After call start = reverse(start, end) * * 0->3->2->1->4->5->6->7->8 * | | * start end * first * */ private ListNode reverse(ListNode start, ListNode end) { ListNode curr = start.next; ListNode prev = start; ListNode first = curr; while (curr != end){ ListNode temp = curr.next; curr.next = prev; prev = curr; curr = temp; } start.next = prev; first.next = curr; return first; }} Python3 代码: 1234567891011121314151617181920212223242526272829class Solution: def reverseKGroup(self, head: ListNode, k: int) -> ListNode: if head is None or k < 2: return head dummy = ListNode(0) dummy.next = head start = dummy end = head count = 0 while end: count += 1 if count % k == 0: start = self.reverse(start, end.next) end = start.next else: end = end.next return dummy.next def reverse(self, start, end): prev, curr = start, start.next first = curr while curr != end: temp = curr.next curr.next = prev prev = curr curr = temp start.next = prev first.next = curr return first JavaScript 代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */// 翻转head到tail之间的部分,不包括head和tail// 返回原链表的第一个元素,也就是翻转后的最后一个元素function reverseList(head, tail) { if (head === null || head.next === null) return head; let cur = head.next; first = cur; let pre = head; // 这里就是翻转不包括head的原因 while (cur !== tail) { // 这里就是翻转不包括tail的原因 const next = cur.next; cur.next = pre; pre = cur; cur = next; } // 拼接 head.next = pre; first.next = cur; return first;}/** * @param {ListNode} head * @param {number} k * @return {ListNode} */var reverseKGroup = function (head, k) { if (head === null || k === 1) { return head; } let cnt = 0; const dummy = { next: head, }; let start = dummy; let end = head; while (end !== null) { cnt++; if (cnt % k !== 0) { end = end.next; } else { start = reverseList(start, end.next); end = start.next; } } return dummy.next;}; 这里不再赘述,如果不理解,想看更多更详细内容,请参考我的LeetCode 题解 - 25.reverse-nodes-in-k-groups-cn 分组反转 - 增强版这道题目来自字节跳动面试题。 题目描述要求从后往前以 k 个为一组进行翻转。 例子,1->2->3->4->5->6->7->8, k = 3, 从后往前以 k=3 为一组, 6->7->8 为一组翻转为 8->7->6,3->4->5 为一组翻转为 5->4->3.1->2 只有 2 个 nodes 少于 k=3 个,不翻转。最后返回: 1->2->5->4->3->8->7->6 思路这里的思路跟从前往后以k个为一组进行翻转类似,可以进行预处理: 翻转链表 对翻转后的链表进行从前往后以 k 为一组翻转。 翻转步骤 2 中得到的链表。 例子:1->2->3->4->5->6->7->8, k = 3 翻转链表得到:8->7->6->5->4->3->2->1 以 k 为一组翻转: 6->7->8->3->4->5->2->1 翻转步骤#2 链表: 1->2->5->4->3->8->7->6 类似题目 Swap Nodes in Pairs","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/categories/LeetCode/"},{"name":"链表反转","slug":"算法/链表反转","permalink":"https://lucifer.ren/blog/categories/算法/链表反转/"},{"name":"链表","slug":"数据结构/链表","permalink":"https://lucifer.ren/blog/categories/数据结构/链表/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"字节跳动","slug":"字节跳动","permalink":"https://lucifer.ren/blog/tags/字节跳动/"},{"name":"链表","slug":"链表","permalink":"https://lucifer.ren/blog/tags/链表/"}],"author":{"name":"snowan","avatar":"https://avatars1.githubusercontent.com/u/6018815?s=40&v=4","url":"https://github.com/snowan"}},{"title":"【LeetCode 日记】面试题46. 把数字翻译成字符串","slug":"面试题46. 把数字翻译成字符串","date":"2020-01-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.695Z","comments":true,"path":"2020/01/20/面试题46. 把数字翻译成字符串/","link":"","permalink":"https://lucifer.ren/blog/2020/01/20/面试题46. 把数字翻译成字符串/","excerpt":"​","text":"​ 原题地址: https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof 题目描述给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。 示例 1: 输入: 12258输出: 5解释: 12258 有 5 种不同的翻译,分别是”bccfi”, “bwfi”, “bczi”, “mcfi”和”mzi” 提示: 0 <= num < 231 思路我们另 f(n)表示给定数字 num 的情况下,从 num 的第 1 位(包含)到第 n 位(包含)有多少种不同的翻译方法。 我们从几个简单的例子入手,尝试打开思路。 对于数字 12258 来说: | (挡板)表示从这里分开翻译, ,(逗号)表示分割多个翻译方式。 f(1) = 1,分别为 1。 f(2) = 2,分别为 1|2, 12。 f(3) = 3,分别为 1|2|2,1|22,12|2 … 其实对于 f(3) 来说, 我手动的情况下,是这么想的: 先把 f(2) 结果搬过来,即 1|2,12 在 f(2)的基础上分割,我要添加第三位,也就是一个 2 到末尾。 1|2|2 这样是行的, 12|2 同样是可以的。 继续在 f(1) 的基础上分割,我要添加第三位,也就是一个 2 到末尾。 1|22 那么总的情况就是三种。OK,总结下我的逻辑: 如果我不可以和前面的数字组成 10 - 25 之间的数,那么在 f(n - 1) 的末尾添加挡板 如果可以,同时在 f(n - 1)和 f(n -2) 的末尾添加挡板 用图来表示: 因此,实际上这道题就是爬楼梯的换皮题。 代码12345678910class Solution: def translateNum(self, num: int) -> int: @lru_cache def helper(s: str) -> int: if not s: return 1 pre = helper(s[:-1]) if 10 <= int(s[-2:]) <= 25: return pre + helper(s[:-2]) return pre return helper(str(num)) 复杂度分析 时间复杂度:最坏的情况,每一个数组都可以和前面的组成新的数组, 有大约 $2^N$ 种组合,因此时间复杂度为 $O(2^N)$,而我这里使用了 @lru_cache 因此不会有重复计算,时间复杂度为 $(N)$,其中 N 为 数字长度。 空间复杂度:由于空间复杂的受递归调用栈的影响,因此空间复杂度为 $O(2^N)$,而我这里使用了 @lru_cache 因此不会有重复计算,空间复杂度为 $(N)$,其中 N 为 数字长度。 如果你愿意的话,其实优化起来也比较简单,我们只需要 bottom-up 即可。 12345678910class Solution: def translateNum(self, num: int) -> int: s = str(num) n = len(s) dp = [1] * n for i in range(1, n): dp[i] = dp[i - 1] if 10 <= int(s[i - 1:i + 1]) <= 25: dp[i] += dp[i - 2] return dp[-1] 进而可以优化到空间 $O(1)$ 12345678910111213class Solution: def translateNum(self, num: int) -> int: s = str(num) n = len(s) a = b = 1 for i in range(1, n): if 10 <= int(s[i - 1:i + 1]) <= 25: temp = a a = b b = temp + b else: a = b return b 更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解","categories":[{"name":"算法,动态规划","slug":"算法,动态规划","permalink":"https://lucifer.ren/blog/categories/算法,动态规划/"},{"name":"中等","slug":"中等","permalink":"https://lucifer.ren/blog/categories/中等/"}],"tags":[{"name":"数据结构,算法,LeetCode 日记,中等","slug":"数据结构,算法,LeetCode-日记,中等","permalink":"https://lucifer.ren/blog/tags/数据结构,算法,LeetCode-日记,中等/"}]},{"title":"【LeetCode日记】 1332. 删除回文子序列","slug":"1332.remove-palindromic-subsequences","date":"2020-01-13T16:00:00.000Z","updated":"2023-01-05T12:24:49.528Z","comments":true,"path":"2020/01/14/1332.remove-palindromic-subsequences/","link":"","permalink":"https://lucifer.ren/blog/2020/01/14/1332.remove-palindromic-subsequences/","excerpt":"LeetCode 上有很多抖机灵的题目,需要你仔细审题,否则很容易被套路。这里就有一道,我们来看下。 ​","text":"LeetCode 上有很多抖机灵的题目,需要你仔细审题,否则很容易被套路。这里就有一道,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/remove-palindromic-subsequences/ 题目描述s,它仅由字母 'a' 和 'b' 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。12345678910111213141516171819202122232425262728293031323334353637返回删除给定字符串中所有字符(字符串为空)的最小删除次数。「子序列」定义:如果一个字符串可以通过删除原字符串某些字符而不改变原字符顺序得到,那么这个字符串就是原字符串的一个子序列。「回文」定义:如果一个字符串向后和向前读是一致的,那么这个字符串就是一个回文。 示例 1:输入:s = "ababa"输出:1解释:字符串本身就是回文序列,只需要删除一次。示例 2:输入:s = "abb"输出:2解释:"abb" -> "bb" -> "".先删除回文子序列 "a",然后再删除 "bb"。示例 3:输入:s = "baabb"输出:2解释:"baabb" -> "b" -> "".先删除回文子序列 "baab",然后再删除 "b"。示例 4:输入:s = ""输出:0 提示:0 <= s.length <= 1000s 仅包含字母 'a' 和 'b'在真实的面试中遇到过这道题? 思路这又是一道“抖机灵”的题目,类似的题目有1297.maximum-number-of-occurrences-of-a-substring 由于只有 a 和 b 两个字符。其实最多的消除次数就是 2。因为我们无论如何都可以先消除全部的 1 再消除全部的 2(先消除 2 也一样),这样只需要两次即可完成。 我们再看一下题目给的一次消除的情况,题目给的例子是“ababa”,我们发现其实它本身就是一个回文串,所以才可以一次全部消除。那么思路就有了: 如果 s 是回文,则我们需要一次消除 否则需要两次 一定要注意特殊情况, 对于空字符串,我们需要 0 次 代码代码支持:Python3 Python3 Code: 123456789101112131415class Solution: def removePalindromeSub(self, s: str) -> int: if s == '': return 0 def isPalindrome(s): l = 0 r = len(s) - 1 while l < r: if s[l] != s[r]: return False l += 1 r -= 1 return True return 1 if isPalindrome(s) else 2 如果你觉得判断回文不是本题重点,也可以简单实现: Python3 Code: 12345class Solution: def removePalindromeSub(self, s: str) -> int: if s == '': return 0 return 1 if s == s[::-1] else 2 关键点解析 注意审题目,一定要利用题目条件“只含有 a 和 b 两个字符”否则容易做的很麻烦","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"字符串","slug":"数据结构/字符串","permalink":"https://lucifer.ren/blog/categories/数据结构/字符串/"},{"name":"回文","slug":"算法/回文","permalink":"https://lucifer.ren/blog/categories/算法/回文/"},{"name":"Easy","slug":"Easy","permalink":"https://lucifer.ren/blog/categories/Easy/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode日记","slug":"LeetCode日记","permalink":"https://lucifer.ren/blog/tags/LeetCode日记/"},{"name":"Easy","slug":"Easy","permalink":"https://lucifer.ren/blog/tags/Easy/"}]},{"title":"LeetCode一道令人发指的陷阱题","slug":"1297.maximum-number-of-occurrences-of-a-substring","date":"2020-01-09T16:00:00.000Z","updated":"2023-01-05T12:24:49.846Z","comments":true,"path":"2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/","link":"","permalink":"https://lucifer.ren/blog/2020/01/10/1297.maximum-number-of-occurrences-of-a-substring/","excerpt":"LeetCode 有一些题目会给你设置陷阱,给你一些干扰信息。这个时候你需要小心,不要被他们带跑偏了。那么是什么样的陷阱呢?让我们来看一下!","text":"LeetCode 有一些题目会给你设置陷阱,给你一些干扰信息。这个时候你需要小心,不要被他们带跑偏了。那么是什么样的陷阱呢?让我们来看一下! 题目地址(1297. 子串的最大出现次数)https://leetcode-cn.com/problems/maximum-number-of-occurrences-of-a-substring 题目描述123456789101112131415161718192021222324252627282930313233给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数:子串中不同字母的数目必须小于等于 maxLetters 。子串的长度必须大于等于 minSize 且小于等于 maxSize 。示例 1:输入:s = "aababcaab", maxLetters = 2, minSize = 3, maxSize = 4输出:2解释:子串 "aab" 在原字符串中出现了 2 次。它满足所有的要求:2 个不同的字母,长度为 3 (在 minSize 和 maxSize 范围内)。示例 2:输入:s = "aaaa", maxLetters = 1, minSize = 3, maxSize = 3输出:2解释:子串 "aaa" 在原字符串中出现了 2 次,且它们有重叠部分。示例 3:输入:s = "aabcabcab", maxLetters = 2, minSize = 2, maxSize = 3输出:3示例 4:输入:s = "abcde", maxLetters = 2, minSize = 3, maxSize = 3输出:0提示:1 <= s.length <= 10^51 <= maxLetters <= 261 <= minSize <= maxSize <= min(26, s.length)s 只包含小写英文字母。 暴力法题目给的数据量不是很大,为 1 <= maxLetters <= 26,我们试一下暴力法。 思路暴力法如下: 先找出所有满足长度大于等于 minSize 且小于等于 maxSize 的所有子串。(平方的复杂度) 对于 maxLetter 满足题意的子串,我们统计其出现次数。时间复杂度为 O(k),其中 k 为子串长度 返回最大的出现次数 代码Pythpn Code: 1234567891011121314151617181920class Solution: def maxFreq(self, s: str, maxLetters: int, minSize: int, maxSize: int) -> int: n = len(s) letters = set() cnts = dict() res = 0 for i in range(n - minSize + 1): length = minSize while i + length <= n and length <= maxSize: t = s[i:i + length] for c in t: if len(letters) > maxLetters: break letters.add(c) if len(letters) <= maxLetters: cnts[t] = cnts.get(t, 0) + 1 res = max(res, cnts[t]) letters.clear() length += 1 return res 上述代码会超时。我们来利用剪枝来优化。 剪枝思路还是暴力法的思路,不过我们在此基础上进行一些优化。首先我们需要仔细阅读题目,如果你足够细心或者足够有经验,可能会发现其实题目中 maxSize 没有任何用处,属于干扰信息。 也就是说我们没有必要统计长度大于等于 minSize 且小于等于 maxSize 的所有子串,而是统计长度为 minSize 的所有字串即可。原因是,如果一个大于 minSize 长度的字串若是满足条件,那么该子串其中必定有至少一个长度为 minSize 的字串满足条件。因此一个大于 minSize 长度的字串出现了 n 次,那么该子串其中必定有一个长度为 minSize 的子串出现了 n 次。 代码代码支持 Python3,Java: Python Code: 12345678910 def maxFreq(self, s: str, maxLetters: int, minSize: int, maxSize: int) -> int: counter, res = {}, 0 for i in range(0, len(s) - minSize + 1): sub = s[i : i + minSize] if len(set(sub)) <= maxLetters: counter[sub] = counter.get(sub, 0) + 1 res = max(res, counter[sub]) return res;# @lc code=end Java Code: 12345678910111213141516171819 public int maxFreq(String s, int maxLetters, int minSize, int maxSize) { Map<String, Integer> counter = new HashMap<>(); int res = 0; for (int i = 0; i < s.length() - minSize + 1; i++) { String substr = s.substring(i, i + minSize); if (checkNum(substr, maxLetters)) { int newVal = counter.getOrDefault(substr, 0) + 1; counter.put(substr, newVal); res = Math.max(res, newVal); } } return res;}public boolean checkNum(String substr, int maxLetters) { Set<Character> set = new HashSet<>(); for (int i = 0; i < substr.length(); i++) set.add(substr.charAt(i)); return set.size() <= maxLetters;} 关键点解析 滑动窗口 识别题目干扰信息 看题目限制条件,对于本题有用的信息是1 <= maxLetters <= 26 扩展我们也可以使用滑动窗口来解决,感兴趣的可以试试看。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"陷阱题","slug":"陷阱题","permalink":"https://lucifer.ren/blog/tags/陷阱题/"},{"name":"滑动窗口","slug":"滑动窗口","permalink":"https://lucifer.ren/blog/tags/滑动窗口/"}]},{"title":"掌握前缀表达式真的可以为所欲为!","slug":"1310.xor-queries-of-a-subarray","date":"2020-01-08T16:00:00.000Z","updated":"2023-01-05T12:24:49.558Z","comments":true,"path":"2020/01/09/1310.xor-queries-of-a-subarray/","link":"","permalink":"https://lucifer.ren/blog/2020/01/09/1310.xor-queries-of-a-subarray/","excerpt":"前缀表达式是一种非常常见和重要的知识点,如果你还不知道,那就赶紧点进来看看吧!","text":"前缀表达式是一种非常常见和重要的知识点,如果你还不知道,那就赶紧点进来看看吧! 题目地址(1310. 子数组异或查询)https://leetcode-cn.com/problems/xor-queries-of-a-subarray 题目描述123456789101112131415161718192021222324252627282930313233343536有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]。对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor ... xor arr[Ri])作为本次查询的结果。并返回一个包含给定查询 queries 所有结果的数组。示例 1:输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]输出:[2,7,14,8]解释:数组中元素的二进制表示形式是:1 = 00013 = 00114 = 01008 = 1000查询的 XOR 值为:[0,1] = 1 xor 3 = 2[1,2] = 3 xor 4 = 7[0,3] = 1 xor 3 xor 4 xor 8 = 14[3,3] = 8示例 2:输入:arr = [4,8,2,10], queries = [[2,3],[1,3],[0,0],[0,3]]输出:[8,0,4,4]提示:1 <= arr.length <= 3 * 10^41 <= arr[i] <= 10^91 <= queries.length <= 3 * 10^4queries[i].length == 20 <= queries[i][0] <= queries[i][1] < arr.length 暴力法思路最直观的思路是双层循环即可,果不其然超时了。 代码123456789101112class Solution: def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: res = [] for (L, R) in queries: i = L xor = 0 while i <= R: xor ^= arr[i] i += 1 res.append(xor) return res 前缀表达式思路比较常见的是前缀和,这个概念其实很容易理解,即一个数组中,第 n 位存储的是数组前 n 个数字的和。 对 [1,2,3,4,5,6] 来说,其前缀和可以是 pre=[1,3,6,10,15,21]。我们可以使用公式 pre[𝑖]=pre[𝑖−1]+nums[𝑖]得到每一位前缀和的值,从而通过前缀和进行相应的计算和解题。其实前缀和的概念很简单,但困难的是如何在题目中使用前缀和以及如何使用前缀和的关系来进行解题。 这道题是前缀对前缀异或,我们利用了异或的性质 x ^ y ^ x = y。 代码代码支持 Python3,Java,C++: Python Code: 1234567891011121314151617181920## @lc app=leetcode.cn id=1218 lang=python3## [1218] 最长定差子序列## @lc code=startclass Solution: def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: pre = [0] res = [] for i in range(len(arr)): pre.append(pre[i] ^ arr[i]) for (L, R) in queries: res.append(pre[L] ^ pre[R + 1]) return res# @lc code=end Java Code: 123456789101112131415161718public int[] xorQueries(int[] arr, int[][] queries) { int[] preXor = new int[arr.length]; preXor[0] = 0; for (int i = 1; i < arr.length; i++) preXor[i] = preXor[i - 1] ^ arr[i - 1]; int[] res = new int[queries.length]; for (int i = 0; i < queries.length; i++) { int left = queries[i][0], right = queries[i][1]; res[i] = arr[right] ^ preXor[right] ^ preXor[left]; } return res; } C++ Code: 123456789101112131415161718class Solution {public: vector<int> xorQueries(vector<int>& arr, vector<vector<int>>& queries) { vector<int>res; for(int i=1; i<arr.size(); ++i){ arr[i]^=arr[i-1]; } for(vector<int>temp :queries){ if(temp[0]==0){ res.push_back(arr[temp[1]]); } else{ res.push_back(arr[temp[0]-1]^arr[temp[1]]); } } return res; }}; 关键点解析 异或的性质 x ^ y ^ x = y 前缀表达式 相关题目 303. 区域和检索 - 数组不可变 1186.删除一次得到子数组最大和","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前缀和","slug":"前缀和","permalink":"https://lucifer.ren/blog/tags/前缀和/"},{"name":"前缀表达式","slug":"前缀表达式","permalink":"https://lucifer.ren/blog/tags/前缀表达式/"}]},{"title":"原来状态机也可以用来刷 LeetCode?","slug":"1262.greatest-sum-divisible-by-three","date":"2020-01-04T16:00:00.000Z","updated":"2023-01-05T12:24:49.817Z","comments":true,"path":"2020/01/05/1262.greatest-sum-divisible-by-three/","link":"","permalink":"https://lucifer.ren/blog/2020/01/05/1262.greatest-sum-divisible-by-three/","excerpt":"什么? 状态机还可以用来刷 LeetCode? 如果你还不知道,那么就快进来看看吧!","text":"什么? 状态机还可以用来刷 LeetCode? 如果你还不知道,那么就快进来看看吧! 题目地址: https://leetcode-cn.com/problems/greatest-sum-divisible-by-three/description/ 题目描述12345678910111213141516171819202122232425给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和。 示例 1:输入:nums = [3,6,5,1,8]输出:18解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。示例 2:输入:nums = [4]输出:0解释:4 不能被 3 整除,所以无法选出数字,返回 0。示例 3:输入:nums = [1,2,3,4,4]输出:12解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。 提示:1 <= nums.length <= 4 * 10^41 <= nums[i] <= 10^4 暴力法思路一种方式是找出所有的能够被 3 整除的子集,然后挑选出和最大的。由于我们选出了所有的子集,那么时间复杂度就是 $O(2^N)$ , 毫无疑问会超时。这里我们使用回溯法找子集,如果不清楚回溯法,可以参考我之前的题解,很多题目都用到了,比如78.subsets。 更多回溯题目,可以访问上方链接查看(可以使用一套模板搞定): 代码12345678910111213141516class Solution: def maxSumDivThree(self, nums: List[int]) -> int: self.res = 0 def backtrack(temp, start): total = sum(temp) if total % 3 == 0: self.res = max(self.res, total) for i in range(start, len(nums)): temp.append(nums[i]) backtrack(temp, i + 1) temp.pop(-1) backtrack([], 0) return self.res 减法 + 排序减法的核心思想是,我们求出总和。如果总和不满足题意,我们尝试减去最小的数,使之满足题意。 思路这种算法的思想,具体来说就是: 我们将所有的数字加起来,我们不妨设为 total total 除以 3,得到一个余数 mod, mod 可能值有 0,1,2. 同时我们建立两个数组,一个是余数为 1 的数组 one,一个是余数为 2 的数组 two 如果 mod 为 0,我们直接返回即可。 如果 mod 为 1,我们可以减去 one 数组中最小的一个(如果有的话),或者减去两个 two 数组中最小的(如果有的话),究竟减去谁取决谁更小。 如果 mod 为 2,我们可以减去 two 数组中最小的一个(如果有的话),或者减去两个 one 数组中最小的(如果有的话),究竟减去谁取决谁更小。 由于我们需要取 one 和 two 中最小的一个或者两个,因此对数组 one 和 two 进行排序是可行的,如果基于排序的话,时间复杂度大致为 $O(NlogN)$,这种算法可以通过。 以题目中的例 1 为例: 以题目中的例 2 为例: 代码12345678910111213141516171819202122232425class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [] two = [] total = 0 for num in nums: total += num if num % 3 == 1: one.append(num) if num % 3 == 2: two.append(num) one.sort() two.sort() if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 减法 + 非排序思路上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。 代码123456789101112131415161718192021222324252627282930313233class Solution: def maxSumDivThree(self, nums: List[int]) -> int: one = [float('inf')] * 2 two = [float('inf')] * 2 total = 0 for num in nums: total += num if num % 3 == 1: if num < one[0]: t = one[0] one[0] = num one[1] = t elif num < one[1]: one[1] = num if num % 3 == 2: if num < two[0]: t = two[0] two[0] = num two[1] = t elif num < two[1]: two[1] = num if total % 3 == 0: return total elif total % 3 == 1 and one: if len(two) >= 2 and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2 and two: if len(one) >= 2 and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return 0 有限状态机思路我在数据结构与算法在前端领域的应用 - 第二篇 中讲到了有限状态机。 状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。 状态机使用非常广泛,比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。 拿本题中来说,我们从左到右扫描数组的过程,将会不断改变状态机的状态。 我们使用 state 数组来表示本题的状态: state[0] 表示 mod 为 0 的 最大和 state[1] 表示 mod 为 1 的 最大和 state[2] 表示 mod 为 1 的 最大和 我们的状态转移方程就会很容易。说到状态转移方程,你可能会想到动态规划。没错!这种思路可以直接翻译成动态规划,算法完全一样。如果你看过我上面提到的文章,那么状态转移方程对你来说就会很容易。如果你不清楚,那么请往下看: 我们从左往右不断读取数字,我们不妨设这个数字为 num。 如果 num % 3 为 0。 那么我们的 state[0], state[1], state[2] 可以直接加上 num(题目限定了 num 为非负), 因为任何数字加上 3 的倍数之后,mod 3 的值是不变的。 如果 num % 3 为 1。 我们知道 state[2] + num 会变成一个能被三整除的数,但是这个数字不一定比当前的 state[0]大。 代码表示就是max(state[2] + num, state[0])。同理 state[1] 和 state[2] 的转移逻辑类似。 同理 num % 3 为 2 也是类似的逻辑。 最后我们返回 state[0]即可。 代码123456789101112131415161718class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: if num % 3 == 0: state = [state[0] + num, state[1] + num, state[2] + num] if num % 3 == 1: a = max(state[2] + num, state[0]) b = max(state[0] + num, state[1]) c = max(state[1] + num, state[2]) state = [a, b, c] if num % 3 == 2: a = max(state[1] + num, state[0]) b = max(state[2] + num, state[1]) c = max(state[0] + num, state[2]) state = [a, b, c] return state[0] 当然这个代码还可以简化: 1234567891011class Solution: def maxSumDivThree(self, nums: List[int]) -> int: state = [0, float('-inf'), float('-inf')] for num in nums: temp = [0] * 3 for i in range(3): temp[(i + num) % 3] = max(state[(i + num) % 3], state[i] + num) state = temp return state[0] 关键点解析 贪婪法 状态机 数学分析 扩展实际上,我们可以采取加法(贪婪策略),感兴趣的可以试一下。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"},{"name":"状态机","slug":"状态机","permalink":"https://lucifer.ren/blog/tags/状态机/"},{"name":"贪心","slug":"贪心","permalink":"https://lucifer.ren/blog/tags/贪心/"}]},{"title":"一行代码就可以通过 LeetCode?来看下我是怎么做到的!","slug":"1227.airplane-seat-assignment-probability","date":"2020-01-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.544Z","comments":true,"path":"2020/01/04/1227.airplane-seat-assignment-probability/","link":"","permalink":"https://lucifer.ren/blog/2020/01/04/1227.airplane-seat-assignment-probability/","excerpt":"这是一道 LeetCode 为数不多的概率题,我们来看下。 ​","text":"这是一道 LeetCode 为数不多的概率题,我们来看下。 ​ 原题地址:https://leetcode-cn.com/problems/airplane-seat-assignment-probability/description/ 题目描述123456789101112131415161718192021222324252627有 n 位乘客即将登机,飞机正好有 n 个座位。第一位乘客的票丢了,他随便选了一个座位坐下。剩下的乘客将会:如果他们自己的座位还空着,就坐到自己的座位上,当他们自己的座位被占用时,随机选择其他座位第 n 位乘客坐在自己的座位上的概率是多少? 示例 1:输入:n = 1输出:1.00000解释:第一个人只会坐在自己的位置上。示例 2:输入: n = 2输出: 0.50000解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。 提示:1 <= n <= 10^5 暴力递归这是一道 LeetCode 为数不多的概率题,我们来看下。 思路我们定义原问题为 f(n)。对于第一个人来说,他有 n 中选择,就是分别选择 n 个座位中的一个。由于选择每个位置的概率是相同的,那么选择每个位置的概率应该都是 1 / n。 我们分三种情况来讨论: 如果第一个人选择了第一个人的位置(也就是选择了自己的位置),那么剩下的人按照票上的座位做就好了,这种情况第 n 个人一定能做到自己的位置 如果第一个人选择了第 n 个人的位置,那么第 n 个人肯定坐不到自己的位置。 如果第一个人选择了第 i (1 < i < n)个人的位置,那么第 i 个人就相当于变成了“票丢的人”,此时问题转化为 f(n - i + 1)。 此时的问题转化关系如图: (红色表示票丢的人) 整个过程分析: 代码代码支持 Python3: Python3 Code: 12345678910class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 res = 1 / n for i in range(2, n): res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n return res 上述代码会栈溢出。 暴力递归 + hashtable思路我们考虑使用记忆化递归来减少重复计算,虽然这种做法可以减少运行时间,但是对减少递归深度没有帮助。还是会栈溢出。 代码代码支持 Python3: Python3 Code: 123456789101112131415class Solution: seen = {} def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 if n in self.seen: return self.seen[n] res = 1 / n for i in range(2, n): res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n self.seen[n] = res return res 动态规划思路上面做法会栈溢出。其实我们根本不需要运行就应该能判断出栈溢出,题目已经给了数据规模是 1 <= n <= 10 ** 5。 这个量级不管什么语言,除非使用尾递归,不然一般都会栈溢出,具体栈深度大家可以查阅相关资料。 既然是栈溢出,那么我们考虑使用迭代来完成。 很容易想到使用动态规划来完成。其实递归都写出来,写一个朴素版的动态规划也难不到哪去,毕竟动态规划就是记录子问题,并建立子问题之间映射而已,这和递归并无本质区别。 代码代码支持 Python3: Python3 Code: 1234567891011121314class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 dp = [1, .5] * n for i in range(2, n): dp[i] = 1 / n for j in range(2, i): dp[i] += dp[i - j + 1] * 1 / n return dp[-1] 这种思路的代码超时了,并且仅仅执行了 35/100 testcase 就超时了。 数学分析思路我们还需要进一步优化时间复杂度,我们需要思考是否可以在线性的时间内完成。 我们继续前面的思路进行分析, 不难得出,我们不妨称其为等式 1: 1234f(n)= 1/n + 0 + 1/n * (f(n-1) + f(n-2) + ... + f(2))= 1/n * (f(n-1) + f(n-2) + ... + f(2) + 1)= 1/n * (f(n-1) + f(n-2) + ... + f(2) + f(1)) 似乎更复杂了?没关系,我们继续往下看,我们看下 f(n - 1),我们不妨称其为等式 2。 1f(n-1) = 1/(n-1) * (f(n-2) + f(n-3) + ... + f(1)) 我们将等式 1 和等式 2 两边分别同时乘以 n 和 n - 1 12n * f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(1)(n-1) * f(n-1) = f(n-2) + f(n-3) + ... + f(1) 我们将两者相减: 1n * f(n) - (n-1)*f(n-1) = f(n-1) 我们继续将 (n-1)*f(n-1) 移到等式右边,得到: 1n * f(n) = n * f(n-1) 也就是说: 1f(n) = f(n - 1) 当然前提是 n 大于 2。 既然如此,我们就可以减少一层循环, 我们用这个思路来优化一下上面的 dp 解法。这种解法终于可以 AC 了。 代码代码支持 Python3: Python3 Code: 123456789101112class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: if n == 1: return 1 if n == 2: return 0.5 dp = [1, .5] * n for i in range(2, n): dp[i] = 1/n+(n-2)/n * dp[n-1] return dp[-1] 优化数学分析思路上面我们通过数学分析,得出了当 n 大于 2 时: 1f(n) = f(n - 1) 那么是不是意味着我们随便求出一个 n 就好了? 比如我们求出 n = 2 的时候的值,是不是就知道 n 为任意数的值了。 我们不难想出 n = 2 时候,概率是 0.5,因此只要 n 大于 1 就是 0.5 概率,否则就是 1 概率。 代码代码支持 Python3: Python3 Code: 123class Solution: def nthPersonGetsNthSeat(self, n: int) -> float: return 1 if n == 1 else .5 关键点 概率分析 数学推导 动态规划 递归 + mapper 栈限制大小 尾递归","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"数学","slug":"算法/数学","permalink":"https://lucifer.ren/blog/categories/算法/数学/"},{"name":"动态规划","slug":"算法/动态规划","permalink":"https://lucifer.ren/blog/categories/算法/动态规划/"},{"name":"概率","slug":"算法/概率","permalink":"https://lucifer.ren/blog/categories/算法/概率/"},{"name":"递归","slug":"算法/递归","permalink":"https://lucifer.ren/blog/categories/算法/递归/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"数学","slug":"数学","permalink":"https://lucifer.ren/blog/tags/数学/"},{"name":"概率","slug":"概率","permalink":"https://lucifer.ren/blog/tags/概率/"},{"name":"递归","slug":"递归","permalink":"https://lucifer.ren/blog/tags/递归/"},{"name":"动态规划","slug":"动态规划","permalink":"https://lucifer.ren/blog/tags/动态规划/"}]},{"title":"数据结构快速盘点","slug":"basic-data-structure","date":"2020-01-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.811Z","comments":true,"path":"2020/01/03/basic-data-structure/","link":"","permalink":"https://lucifer.ren/blog/2020/01/03/basic-data-structure/","excerpt":"这篇文章不是讲解数据结构的文章,而是结合现实的场景帮助大家理解和复习数据结构与算法,如果你的数据结构基础很差,建议先去看一些基础教程,再转过来看。 本篇文章的定位是侧重于前端的,通过学习前端中实际场景的数据结构,从而加深大家对数据结构的理解和认识。","text":"这篇文章不是讲解数据结构的文章,而是结合现实的场景帮助大家理解和复习数据结构与算法,如果你的数据结构基础很差,建议先去看一些基础教程,再转过来看。 本篇文章的定位是侧重于前端的,通过学习前端中实际场景的数据结构,从而加深大家对数据结构的理解和认识。 线性结构数据结构我们可以从逻辑上分为线性结构和非线性结构。线性结构有数组,栈,链表等, 非线性结构有树,图等。 其实我们可以称树为一种半线性结构。 需要注意的是,线性和非线性不代表存储结构是线性的还是非线性的,这两者没有任何关系,它只是一种逻辑上的划分。比如我们可以用数组去存储二叉树。 一般而言,有前驱和后继的就是线性数据结构。比如数组和链表。其实一叉树就是链表。 数组数组是最简单的数据结构了,很多地方都用到它。 比如有一个数据列表等,用它是再合适不过了。其实后面的数据结构很多都有数组的影子。 我们之后要讲的栈和队列其实都可以看成是一种受限的数组, 怎么个受限法呢?我们后面讨论。 我们来讲几个有趣的例子来加深大家对数组这种数据结构的理解。 React HooksHooks 的本质就是一个数组, 伪代码: 那么为什么 hooks 要用数组? 我们可以换个角度来解释,如果不用数组会怎么样? 12345678910111213141516171819function Form() { // 1. Use the name state variable const [name, setName] = useState(\"Mary\"); // 2. Use an effect for persisting the form useEffect(function persistForm() { localStorage.setItem(\"formData\", name); }); // 3. Use the surname state variable const [surname, setSurname] = useState(\"Poppins\"); // 4. Use an effect for updating the title useEffect(function updateTitle() { document.title = name + \" \" + surname; }); // ...} 基于数组的方式,Form 的 hooks 就是 [hook1, hook2, hook3, hook4],我们可以得出这样的关系, hook1 就是[name, setName] 这一对,hook2 就是 persistForm 这个。 如果不用数组实现,比如对象,Form 的 hooks 就是 123456{ 'key1': hook1, 'key2': hook2, 'key3': hook3, 'key4': hook4,} 那么问题是 key1,key2,key3,key4 怎么取呢? 关于 React hooks 的本质研究,更多请查看React hooks: not magic, just arrays React 将如何确保组件内部hooks保存的状态之间的对应关系这个工作交给了开发人员去保证,即你必须保证 HOOKS 的顺序严格一致,具体可以看 React 官网关于 Hooks Rule 部分。 队列队列是一种受限的序列,它只能够操作队尾和队首,并且只能只能在队尾添加元素,在队首删除元素。 队列作为一种最常见的数据结构同样有着非常广泛的应用, 比如消息队列 “队列”这个名称,可类比为现实生活中排队(不插队的那种) 在计算机科学中, 一个 队列(queue) 是一种特殊类型的抽象数据类型或集合。集合中的实体按顺序保存。 队列基本操作有两种: 向队列的后端位置添加实体,称为入队 从队列的前端位置移除实体,称为出队。 队列中元素先进先出 FIFO (first in, first out)的示意: (图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/queue/README.zh-CN.md) 我们前端在做性能优化的时候,很多时候会提到的一点就是“HTTP 1.1 的队头阻塞问题”,具体来说就是 HTTP2 解决了 HTTP1.1 中的队头阻塞问题,但是为什么 HTTP1.1 有队头阻塞问题,HTTP2 究竟怎么解决的很多人都不清楚。 其实“队头阻塞”是一个专有名词,不仅仅这里有,交换器等其他都有这个问题,引起这个问题的根本原因是使用了队列这种数据结构。 对于同一个 tcp 连接,所有的 http1.0 请求放入队列中,只有前一个请求的响应收到了,然后才能发送下一个请求,这个阻塞主要发生在客户端。 这就好像我们在等红绿灯,即使旁边绿灯亮了,你的这个车道是红灯,你还是不能走,还是要等着。 HTTP/1.0 和 HTTP/1.1:在HTTP/1.0 中每一次请求都需要建立一个 TCP 连接,请求结束后立即断开连接。在HTTP/1.1 中,每一个连接都默认是长连接(persistent connection)。对于同一个 tcp 连接,允许一次发送多个 http1.1 请求,也就是说,不必等前一个响应收到,就可以发送下一个请求。这样就解决了 http1.0 的客户端的队头阻塞,而这也就是HTTP/1.1中管道(Pipeline)的概念了。但是,http1.1规定,服务器端的响应的发送要根据请求被接收的顺序排队,也就是说,先接收到的请求的响应也要先发送。这样造成的问题是,如果最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队头阻塞。可见,http1.1 的队首阻塞发生在服务器端。 如果用图来表示的话,过程大概是: HTTP/2 和 HTTP/1.1: 为了解决HTTP/1.1中的服务端队首阻塞,HTTP/2采用了二进制分帧 和 多路复用 等方法。二进制分帧中,帧是HTTP/2数据通信的最小单位。在HTTP/1.1数据包是文本格式,而HTTP/2的数据包是二进制格式的,也就是二进制帧。采用帧可以将请求和响应的数据分割得更小,且二进制协议可以更高效解析。HTTP/2中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。多路复用用以替代原来的序列和拥塞机制。在HTTP/1.1中,并发多个请求需要多个 TCP 链接,且单个域名有 6-8 个 TCP 链接请求限制。在HHTP/2中,同一域名下的所有通信在单个链接完成,仅占用一个 TCP 链接,且在这一个链接上可以并行请求和响应,互不干扰。 此网站可以直观感受HTTP/1.1和HTTP/2的性能对比。 栈栈也是一种受限的序列,它只能够操作栈顶,不管入栈还是出栈,都是在栈顶操作。 在计算机科学中, 一个 栈(stack) 是一种抽象数据类型,用作表示元素的集合,具有两种主要操作: push, 添加元素到栈的顶端(末尾);pop, 移除栈最顶端(末尾)的元素.以上两种操作可以简单概括为“后进先出(LIFO = last in, first out)”。 此外,应有一个 peek 操作用于访问栈当前顶端(末尾)的元素。(只返回不弹出) “栈”这个名称,可类比于一组物体的堆叠(一摞书,一摞盘子之类的)。 栈的 push 和 pop 操作的示意: (图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/stack/README.zh-CN.md) 栈在很多地方都有着应用,比如大家熟悉的浏览器就有很多栈,其实浏览器的执行栈就是一个基本的栈结构,从数据结构上说,它就是一个栈。这也就解释了,我们用递归的解法和用循环+栈的解法本质上是差不多。 比如如下 JS 代码: 1234567891011function bar() { const a = 1; const b = 2; console.log(a, b);}function foo() { const a = 1; bar();}foo(); 真正执行的时候,内部大概是这样的: 我画的图没有画出执行上下文中其他部分(this 和 scope 等), 这部分是闭包的关键,而我这里不是将闭包的,是为了讲解栈的。 社区中有很多“执行上下文中的 scope 指的是执行栈中父级声明的变量”说法,这是完全错误的, JS 是词法作用域,scope 指的是函数定义时候的父级,和执行没关系 栈常见的应用有进制转换,括号匹配,栈混洗,中缀表达式(用的很少),后缀表达式(逆波兰表达式)等。 合法的栈混洗操作,其实和合法的括号匹配表达式之间存在着一一对应的关系,也就是说 n 个元素的栈混洗有多少种,n 对括号的合法表达式就有多少种。感兴趣的可以查找相关资料 链表链表是一种最基本数据结构,熟练掌握链表的结构和常见操作是基础中的基础。 (图片来自: https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/linked-list/traversal) React Fiber很多人都说 fiber 是基于链表实现的,但是为什么要基于链表呢,可能很多人并没有答案,那么我觉得可以把这两个点(fiber 和链表)放到一起来讲下。 fiber 出现的目的其实是为了解决 react 在执行的时候是无法停下来的,需要一口气执行完的问题的。 图片来自 Lin Clark 在 ReactConf 2017 分享 上面已经指出了引入 fiber 之前的问题,就是 react 会阻止优先级高的代码(比如用户输入)执行。因此 fiber打算自己自建一个虚拟执行栈来解决这个问题,这个虚拟执行栈的实现是链表。 Fiber 的基本原理是将协调过程分成小块,一次执行一块,然乎将运算结果保存起来,并判断是否有时间(react 自己实现了一个类似 requestIdleCallback 的功能)继续执行下一块。如果有时间,则继续。 否则跳出,让浏览器主线程歇一会,执行别的优先级高的代码。 当协调过程完成(所有的小块都运算完毕), 那么就会进入提交阶段, 真正的进行副作用(side effect)操作,比如更新 DOM,这个过程是没有办法取消的,原因就是这部分有副作用。 问题的关键就是将协调的过程划分为一块块的,最后还可以合并到一起,有点像 Map/Reduce。 React 必须重新实现遍历树的算法,从依赖于内置堆栈的同步递归模型,变为具有链表和指针的异步模型。 Andrew 是这么说的: 如果你只依赖于[内置]调用堆栈,它将继续工作直到堆栈为空。。。 如果我们可以随意中断调用堆栈并手动操作堆栈帧,那不是很好吗?这就是 React Fiber 的目的。 Fiber 是堆栈的重新实现,专门用于 React 组件。 你可以将单个 Fiber 视为一个虚拟堆栈帧。 react fiber 大概是这样的: 12345678910111213let fiber = { tag: HOST_COMPONENT, type: \"div\", return: parentFiber, children: childFiber, sibling: childFiber, alternate: currentFiber, stateNode: document.createElement(\"div\"), props: { children: [], className: \"foo\" }, partialState: null, effectTag: PLACEMENT, effects: []}; 从这里可以看出 fiber 本质上是个对象,使用 parent,child,sibling 属性去构建 fiber 树来表示组件的结构树,return, children, sibling 也都是一个 fiber,因此 fiber 看起来就是一个链表。 细心的朋友可能已经发现了, alternate 也是一个 fiber, 那么它是用来做什么的呢?它其实原理有点像 git, 可以用来执行 git revert ,git commit 等操作,这部分挺有意思,我会在我的《从零开发 git》中讲解 想要了解更多的朋友可以看这个文章 如果可以翻墙, 可以看英文原文 这篇文章也是早期讲述 fiber 架构的优秀文章 我目前也在写关于《从零开发 react 系列教程》中关于 fiber 架构的部分,如果你对具体实现感兴趣,欢迎关注。 非线性结构那么有了线性结构,我们为什么还需要非线性结构呢? 答案是为了高效地兼顾静态操作和动态操作。大家可以对照各种数据结构的各种操作的复杂度来直观感受一下。 树树的应用同样非常广泛,小到文件系统,大到因特网,组织架构等都可以表示为树结构,而在我们前端眼中比较熟悉的 DOM 树也是一种树结构,而 HTML 作为一种 DSL 去描述这种树结构的具体表现形式。如果你接触过 AST,那么 AST 也是一种树,XML 也是树结构。。。树的应用远比大多数人想象的要得多。 树其实是一种特殊的图,是一种无环连通图,是一种极大无环图,也是一种极小连通图。 从另一个角度看,树是一种递归的数据结构。而且树的不同表示方法,比如不常用的长子 + 兄弟法,对于你理解树这种数据结构有着很大用处, 说是一种对树的本质的更深刻的理解也不为过。 树的基本算法有前中后序遍历和层次遍历,有的同学对前中后这三个分别具体表现的访问顺序比较模糊,其实当初我也是一样的,后面我学到了一点,你只需要记住:所谓的前中后指的是根节点的位置,其他位置按照先左后右排列即可。比如前序遍历就是根左右, 中序就是左根右,后序就是左右根, 很简单吧? 我刚才提到了树是一种递归的数据结构,因此树的遍历算法使用递归去完成非常简单,幸运的是树的算法基本上都要依赖于树的遍历。 但是递归在计算机中的性能一直都有问题,因此掌握不那么容易理解的”命令式地迭代”遍历算法在某些情况下是有用的。如果你使用迭代式方式去遍历的话,可以借助上面提到的栈来进行,可以极大减少代码量。 如果使用栈来简化运算,由于栈是 FILO 的,因此一定要注意左右子树的推入顺序。 树的重要性质: 如果树有 n 个顶点,那么其就有 n - 1 条边,这说明了树的顶点数和边数是同阶的。 任何一个节点到根节点存在唯一路径, 路径的长度为节点所处的深度 实际使用的树有可能会更复杂,比如使用在游戏中的碰撞检测可能会用到四叉树或者八叉树。以及 k 维的树结构 k-d 树等。 (图片来自 https://zh.wikipedia.org/wiki/K-d%E6%A0%91) 二叉树二叉树是节点度数不超过二的树,是树的一种特殊子集,有趣的是二叉树这种被限制的树结构却能够表示和实现所有的树,它背后的原理正是长子 + 兄弟法,用邓老师的话说就是二叉树是多叉树的特例,但在有根且有序时,其描述能力却足以覆盖后者。 实际上, 在你使用长子 + 兄弟法表示树的同时,进行 45 度角旋转即可。 一个典型的二叉树: 标记为 7 的节点具有两个子节点, 标记为 2 和 6; 一个父节点,标记为 2,作为根节点, 在顶部,没有父节点。 (图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/tree/README.zh-CN.md) 对于一般的树,我们通常会去遍历,这里又会有很多变种。 下面我列举一些二叉树遍历的相关算法: 94.binary-tree-inorder-traversal 102.binary-tree-level-order-traversal 103.binary-tree-zigzag-level-order-traversal 144.binary-tree-preorder-traversal 145.binary-tree-postorder-traversal 199.binary-tree-right-side-view 相关概念: 真二叉树 (所有节点的度数只能是偶数,即只能为 0 或者 2) 另外我也专门开设了二叉树的遍历章节, 具体细节和算法可以去那里查看。 堆堆其实是一种优先级队列,在很多语言都有对应的内置数据结构,很遗憾 javascript 没有这种原生的数据结构。不过这对我们理解和运用不会有影响。 堆的特点: 在一个 最小堆(min heap) 中, 如果 P 是 C 的一个父级节点, 那么 P 的 key(或 value)应小于或等于 C 的对应值.正因为此,堆顶元素一定是最小的,我们会利用这个特点求最小值或者第 k 小的值。 在一个 最大堆(max heap) 中, P 的 key(或 value)大于 C 的对应值。 需要注意的是优先队列不仅有堆一种,还有更复杂的,但是通常来说,我们会把两者做等价。 相关算法: 295.find-median-from-data-stream 二叉查找树二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。 二叉查找树具有下列性质的二叉树: 若左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若右子树不空,则右子树上所有节点的值均大于它的根节点的值; 左、右子树也分别为二叉排序树; 没有键值相等的节点。 对于一个二叉查找树,常规操作有插入,查找,删除,找父节点,求最大值,求最小值。 二叉查找树,之所以叫查找树就是因为其非常适合查找,举个例子,如下一颗二叉查找树,我们想找节点值小于且最接近 58 的节点,搜索的流程如图所示: (图片来自 https://www.geeksforgeeks.org/floor-in-binary-search-tree-bst/) 另外我们二叉查找树有一个性质是: 其中序遍历的结果是一个有序数组。有时候我们可以利用到这个性质。 相关题目: 98.validate-binary-search-tree 二叉平衡树平衡树是计算机科学中的一类数据结构,为改进的二叉查找树。一般的二叉查找树的查询复杂度取决于目标结点到树根的距离(即深度),因此当结点的深度普遍较大时,查询的均摊复杂度会上升。为了实现更高效的查询,产生了平衡树。 在这里,平衡指所有叶子的深度趋于平衡,更广义的是指在树上所有可能查找的均摊复杂度偏低。 一些数据库引擎内部就是用的这种数据结构,其目标也是将查询的操作降低到 logn(树的深度),可以简单理解为树在数据结构层面构造了二分查找算法。 基本操作: 旋转 插入 删除 查询前驱 查询后继 AVL是最早被发明的自平衡二叉查找树。在 AVL 树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 {\\displaystyle O(\\log {n})} O(\\log{n})。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在 1962 年的论文 An algorithm for the organization of information 中公开了这一数据结构。 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。 红黑树在 1972 年由鲁道夫·贝尔发明,被称为”对称二叉 B 树”,它现代的名字源于 Leo J. Guibas 和 Robert Sedgewick 于 1978 年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 {\\displaystyle O(\\log {n})} O(\\log{n})时间内完成查找,插入和删除,这里的 n 是树中元素的数目 字典树(前缀树)又称 Trie 树,是一种树形结构。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 (图来自 https://baike.baidu.com/item/%E5%AD%97%E5%85%B8%E6%A0%91/9825209?fr=aladdin)它有 3 个基本性质: 根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。 immutable 与 字典树immutableJS的底层就是share + tree. 这样看的话,其实和字典树是一致的。 相关算法: 208.implement-trie-prefix-tree 图前面讲的数据结构都可以看成是图的特例。 前面提到了二叉树完全可以实现其他树结构,其实有向图也完全可以实现无向图和混合图,因此有向图的研究一直是重点考察对象。 图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。 图的表示方法 邻接矩阵(常见) 空间复杂度 O(n^2),n 为顶点个数。 优点: 直观,简单。 适用于稠密图 判断两个顶点是否连接,获取入度和出度以及更新度数,时间复杂度都是 O(1) 关联矩阵 邻接表 对于每个点,存储着一个链表,用来指向所有与该点直接相连的点对于有权图来说,链表中元素值对应着权重 例如在无向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 可以看出在无向图中,邻接矩阵关于对角线对称,而邻接链表总有两条对称的边而在有向无权图中: (图片来自 https://zhuanlan.zhihu.com/p/25498681) 图的遍历图的遍历就是要找出图中所有的点,一般有以下两种方法: 深度优先遍历:(Depth First Search, DFS) 深度优先遍历图的方法是,从图中某顶点 v 出发, 不断访问邻居, 邻居的邻居直到访问完毕。 广度优先搜索:(Breadth First Search, BFS) 广度优先搜索,可以被形象地描述为 “浅尝辄止”,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"}]},{"title":"每日一荐 2020-01 汇总","slug":"daily-featured-2020-01","date":"2020-01-01T16:00:00.000Z","updated":"2023-01-07T14:01:24.667Z","comments":true,"path":"2020/01/02/daily-featured-2020-01/","link":"","permalink":"https://lucifer.ren/blog/2020/01/02/daily-featured-2020-01/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2020-012020-01-23[资料]尤雨溪在多伦多的演讲《Vue 3.0 设计原则》对于想要学习 Vue3.0 或者想要直接从零开发 Vue3.0 的人来说,绝对是必读的。PPT 内容不多,几分钟就可以读完,不建议看视频,视频大概有 50 多分钟。 PPT 地址: https://docs.google.com/presentation/d/1r0HcS4baHy2c106DsZ4jA7Zt0R9u2MnRmmKIvAVuf1o/edit#slide=id.p 2020-01-22[软件]安卓软件的开屏广告很烦人,严重影响效率。这里推荐一个安卓 APP 可以改善这种情况, 并且不需要 root,只需要开启权限即可。注意它不是万能的,不过实际使用情况来看,还是有用的。 地址: https://www.coolapk.com/apk/me.angeldevil.autoskip 2020-01-21[好文]可访问性真的是一个非常重要的东西,尤其是对于有障碍人士。但是整个行业在这方面做的还完全不够,不管是技术能力,还是重视程度上。 比如我在使用钉钉软件的时候,他们的快捷键很少,这一点非常困扰我,当然类似的应用还有很多。我觉得整个行业应该注重起来这一块,将可访问性做好,要知道这也是用户体验中很重要的组成。这里有一篇文章 《Debugging your application for accessibility》, 从浅层次上讲解了可访问性的内容,以及基本实践,同时还推荐了一个叫 axe 的 chrome 扩展工具帮助你分析网页存在的可访问性问题,类似于 网页性能分析之于 lighthouse。 文章地址: https://blog.logrocket.com/debugging-application-accessibility/ 2020-01-20[网站]我平时有 RSS 阅读的习惯,我使用的 Feedly 管理订阅内容。但是有的网站本身并不支持 RSS 订阅。那么一种黑科技,就是使用第三方服务帮我们转换一下,生成订阅。原理很简单,就是轮训内容变化,如果变化就通知你。当然前提你要知道“如何判断发布了新内容”,这部分 feed43 做的不错。我们利用 Feed43,将任意网页制作成 RSS 订阅源。 这里有一篇少数派的文章,大家可以参考一下: https://sspai.com/post/34320 你也可以使用 rsshub 来做同样的事情,rsshub 支持私有化部署,地址: https://docs.rsshub.app/。 2020-01-19[网站]给大家介绍一个 mac 软件下载网站,效果你懂的。类似的网站还有 xclient.info。 地址:https://www.macappdownload.com/ 2020-01-17[工具]如果你想开发一个 VSCode 插件,那么一个脚手架是有用的。我推荐使用官方的脚手架工具。顺便再推荐一个 vscode 插件开发指南,来自 sorrycc,地址 https://www.yuque.com/docs/share/cf6d9191-be02-4644-aef5-afc2f2f38297 地址: https://github.com/Microsoft/vscode-generator-code 2020-01-16[工具]不改变任何功能的情况下给你的 docker image 瘦身。 Github 地址: https://github.com/docker-slim/docker-slim 2020-01-13[插件]今天推荐两个关于 Github 的 chrome 插件。 一个是用来查看 Github 提交历史的,名字是Git History Browser Extension,安装之后 git 文件右上角信息会多一个按钮。 点开之后是这种画风: 另外一个插件是OctoLinker。这个插件你可以用来方便地进行文件跳转。 2020-01-09[好文]如果你关注 Node.js 社区,那么你一定记得 Node.js v12 一个非常重磅的功能就是,内核的 HTTP Parser 默认使用 llhttp,取代了老旧的 http-parser,性能提升了 156%。 但知其然也要知其所以然,llhttp 是如何做到这一点的呢?《llhttp 是如何使 Node.js 性能翻倍的?》进行了详细的阐述。 地址: https://zhuanlan.zhihu.com/p/100660049 2020-01-08[好文]昨天介绍了《当你在浏览器中输入 google.com 并且按下回车之后发生了什么?》,今天推荐一篇《图解浏览器的基本工作原理》。 讲的内容主要是浏览器渲染相关的,让你在更大的视角,更细的粒度了解浏览器原理,最可贵的是文章通俗易懂,图文并茂,对于想了解浏览器原理而又找不到好的入门资料的同学来说很有用。 其中还提到了很多延伸知识,比如事件冒泡更微观角度是什么?事件的 passive:true 做了什么?为什么很多时候我们绘图不流畅以及如何实现平滑绘图? 12345678window.addEventListener(\"pointermove\", (event) => { const events = event.getCoalescedEvents(); for (let event of events) { const x = event.pageX; const y = event.pageY; // draw a line using x and y coordinates. }}); (使用 getCoalescedEvents API 来获取组合的事件,从而绘制一条平滑的曲线) 文章地址: https://zhuanlan.zhihu.com/p/47407398 2020-01-07[好文]或许目前实际上最全的《当你在浏览器中输入 google.com 并且按下回车之后发生了什么?》。文档内容不仅局限于 DNS,TCP,HTTP,CDN。发送 HTML,解析 DOM 等过程,甚至包括了物理键盘和系统中断的工作原理,系统中断,ARP 等等更为详细的内容。 地址: https://github.com/skyline75489/what-happens-when-zh_CN 2020-01-06[框架]前端测试正在变得越来越重要,之前也写了一篇文章前端测试,那么拥有一个顺手的测试框架显得越来越重要。 我个人目前在使用的测试框架是 Jest,除了 Jest 还有很多优秀的测试框架,知己知彼,百战不殆。我们看看下: Mocha:非常老牌的测试框架,使用 Jest 之前我在用 Enzyme:一个 React 测试框架,后期我不再使用了,而是转向 Jest + react-dom/test-utils Ava Jasmine Cypress 另外你做自动化测试的话,推荐使用 Puppeteer,如果你做组件测试的话可以考虑 Jest 的快照或者 StoryBook(一个 2015 年以来一直关注并且看好的一个框架)。 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2020-01","slug":"每日一荐/2020-01","permalink":"https://lucifer.ren/blog/categories/每日一荐/2020-01/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"《一文看懂浏览器事件循环》","slug":"event-loop","date":"2019-12-10T16:00:00.000Z","updated":"2023-01-05T12:24:49.636Z","comments":true,"path":"2019/12/11/event-loop/","link":"","permalink":"https://lucifer.ren/blog/2019/12/11/event-loop/","excerpt":"实际上浏览器的事件循环标准是由 HTML 标准规定的,具体来说就是由 whatwg 规定的,具体内容可以参考event-loops in browser。而 NodeJS 中事件循环其实也略有不同,具体可以参考event-loops in nodejs 我们在讲解事件模型的时候,多次提到了事件循环。 事件指的是其所处理的对象就是事件本身,每一个浏览器都至少有一个事件循环,一个事件循环至少有一个任务队列。循环指的是其永远处于一个“无限循环”中。不断将注册的回调函数推入到执行栈。 那么事件循环究竟是用来做什么的?浏览器的事件循环和 NodeJS 的事件循环有什么不同?让我们从零开始,一步一步探究背后的原因。","text":"实际上浏览器的事件循环标准是由 HTML 标准规定的,具体来说就是由 whatwg 规定的,具体内容可以参考event-loops in browser。而 NodeJS 中事件循环其实也略有不同,具体可以参考event-loops in nodejs 我们在讲解事件模型的时候,多次提到了事件循环。 事件指的是其所处理的对象就是事件本身,每一个浏览器都至少有一个事件循环,一个事件循环至少有一个任务队列。循环指的是其永远处于一个“无限循环”中。不断将注册的回调函数推入到执行栈。 那么事件循环究竟是用来做什么的?浏览器的事件循环和 NodeJS 的事件循环有什么不同?让我们从零开始,一步一步探究背后的原因。 为什么要有事件循环JS 引擎要回答这个问题,我们先来看一个简单的例子: 12345678function c() {}function b() { c();}function a() { b();}a(); 以上一段简单的 JS 代码,究竟是怎么被浏览器执行的? 首先,浏览器想要执行 JS 脚本,需要一个“东西”,将 JS 脚本(本质上是一个纯文本),变成一段机器可以理解并执行的计算机指令。这个“东西”就是 JS 引擎,它实际上会将 JS 脚本进行编译和执行,整个过程非常复杂,这里不再过多介绍,感兴趣可以期待下我的 V8 章节,如无特殊说明,以下都拿 V8 来举例子。 有两个非常核心的构成,执行栈和堆。执行栈中存放正在执行的代码,堆中存放变量的值,通常是不规则的。 当 V8 执行到a()这一行代码的时候,a 会被压入栈顶。 在 a 的内部,我们碰到了b(),这个时候 b 被压入栈顶。 在 b 的内部,我们又碰到了c(),这个时候 c 被压入栈顶。 c 执行完毕之后,会从栈顶移除。 函数返回到 b,b 也执行完了,b 也从栈顶移除。 同样 a 也会被移除。 整个过程用动画来表示就是这样的: (在线观看) 这个时候我们还没有涉及到堆内存和执行上下文栈,一切还比较简单,这些内容我们放到后面来讲。 DOM 和 WEB API现在我们有了可以执行 JS 的引擎,但是我们的目标是构建用户界面,而传统的前端用户界面是基于 DOM 构建的,因此我们需要引入 DOM。DOM 是文档对象模型,其提供了一系列 JS 可以直接调用的接口,理论上其可以提供其他语言的接口,而不仅仅是 JS。 而且除了 DOM 接口可以给 JS 调用,浏览器还提供了一些 WEB API。 DOM 也好,WEB API 也好,本质上和 JS 没有什么关系,完全不一回事。JS 对应的 ECMA 规范,V8 用来实现 ECMA 规范,其他的它不管。 这也是 JS 引擎和 JS 执行环境的区别,V8 是 JS 引擎,用来执行 JS 代码,浏览器和 Node 是 JS 执行环境,其提供一些 JS 可以调用的 API 即JS bindings。 由于浏览器的存在,现在 JS 可以操作 DOM 和 WEB API 了,看起来是可以构建用户界面啦。 有一点需要提前讲清楚,V8 只有栈和堆,其他诸如事件循环,DOM,WEB API 它一概不知。原因前面其实已经讲过了,因为 V8 只负责 JS 代码的编译执行,你给 V8 一段 JS 代码,它就从头到尾一口气执行下去,中间不会停止。 另外这里我还要继续提一下,JS 执行栈和渲染线程是相互阻塞的。为什么呢? 本质上因为 JS 太灵活了,它可以去获取 DOM 中的诸如坐标等信息。 如果两者同时执行,就有可能发生冲突,比如我先获取了某一个 DOM 节点的 x 坐标,下一时刻坐标变了。 JS 又用这个“旧的”坐标进行计算然后赋值给 DOM,冲突便发生了。 解决冲突的方式有两种: 限制 JS 的能力,你只能在某些时候使用某些 API。 这种做法极其复杂,还会带来很多使用不便。 JS 和渲染线程不同时执行就好了,一种方法就是现在广泛采用的相互阻塞。 实际上这也是目前浏览器广泛采用的方式。 单线程 or 多线程 or 异步前面提到了你给V8一段JS代码,它就从头到尾一口气执行下去,中间不会停止。 为什么不停止,可以设计成可停止么,就好像 C 语言一样? 假设我们需要获取用户信息,获取用户的文章,获取用的朋友。 单线程无异步由于是单线程无异步,因此我们三个接口需要采用同步方式。 123fetchUserInfoSync().then(doSomethingA); // 1sfetchMyArcticlesSync().then(doSomethingB); // 3sfetchMyFriendsSync().then(doSomethingC); // 2s 由于上面三个请求都是同步执行的,因此上面的代码会先执行fetchUserInfoSync,一秒之后执行fetchMyArcticlesSync,再过三秒执行fetchMyFriendsSync。 最可怕的是我们刚才说了JS执行栈和渲染线程是相互阻塞的。 因此用户就在这期间根本无法操作,界面无法响应,这显然是无法接受的。 多线程无异步由于是多线程无异步,虽然我们三个接口仍然需要采用同步方式,但是我们可以将代码分别在多个线程执行,比如我们将这段代码放在三个线程中执行。 线程一: 1fetchUserInfoSync().then(doSomethingA); // 1s 线程二: 1fetchMyArcticlesSync().then(doSomethingB); // 3s 线程三: 1fetchMyFriendsSync().then(doSomethingC); // 2s 由于三块代码同时执行,因此总的时间最理想的情况下取决与最慢的时间,也就是 3s,这一点和使用异步的方式是一样的(当然前提是请求之间无依赖)。为什么要说最理想呢?由于三个线程都可以对 DOM 和堆内存进行访问,因此很有可能会冲突,冲突的原因和我上面提到的 JS 线程和渲染线程的冲突的原因没有什么本质不同。因此最理想情况没有任何冲突的话是 3s,但是如果有冲突,我们就需要借助于诸如锁来解决,这样时间就有可能高于 3s 了。 相应地编程模型也会更复杂,处理过锁的程序员应该会感同身受。 单线程 + 异步如果还是使用单线程,改成异步是不是会好点?问题的是关键是如何实现异步呢?这就是我们要讲的主题 - 事件循环。 事件循环究竟是怎么实现异步的?我们知道浏览器中 JS 线程只有一个,如果没有事件循环,就会造成一个问题。 即如果 JS 发起了一个异步 IO 请求,在等待结果返回的这个时间段,后面的代码都会被阻塞。 我们知道 JS 主线程和渲染进程是相互阻塞的,因此这就会造成浏览器假死。 如何解决这个问题? 一个有效的办法就是我们这节要讲的事件循环。 其实事件循环就是用来做调度的,浏览器和NodeJS中的事件循坏就好像操作系统的调度器一样。操作系统的调度器决定何时将什么资源分配给谁。对于有线程模型的计算机,那么操作系统执行代码的最小单位就是线程,资源分配的最小单位就是进程,代码执行的过程由操作系统进行调度,整个调度过程非常复杂。 我们知道现在很多电脑都是多核的,为了让多个 core 同时发挥作用,即没有一个 core 是特别闲置的,也没有一个 core 是特别累的。操作系统的调度器会进行某一种神秘算法,从而保证每一个 core 都可以分配到任务。 这也就是我们使用 NodeJS 做集群的时候,Worker 节点数量通常设置为 core 的数量的原因,调度器会尽量将每一个 Worker 平均分配到每一个 core,当然这个过程并不是确定的,即不一定调度器是这么分配的,但是很多时候都会这样。 了解了操作系统调度器的原理,我们不妨继续回头看一下事件循环。 事件循环本质上也是做调度的,只不过调度的对象变成了 JS 的执行。事件循环决定了 V8 什么时候执行什么代码。V8只是负责JS代码的解析和执行,其他它一概不知。浏览器或者 NodeJS 中触发事件之后,到事件的监听函数被 V8 执行这个时间段的所有工作都是事件循环在起作用。 我们来小结一下: 对于 V8 来说,它有: 调用栈(call stack) 这里的单线程指的是只有一个 call stack。只有一个 call stack 意味着同一时间只能执行一段代码。 堆(heap) 对于浏览器运行环境来说: WEB API DOM API 任务队列 事件来触发事件循环进行流动 以如下代码为例: 12345678function c() {}function b() { c();}function a() { setTimeout(b, 2000);}a(); 执行过程是这样的: (在线观看) 因此事件循环之所以可以实现异步,是因为碰到异步执行的代码“比如 fetch,setTimeout”,浏览器会将用户注册的回调函数存起来,然后继续执行后面的代码。等到未来某一个时刻,“异步任务”完成了,会触发一个事件,浏览器会将“任务的详细信息”作为参数传递给之前用户绑定的回调函数。具体来说,就是将用户绑定的回调函数推入浏览器的执行栈。 但并不是说随便推入的,只有浏览器将当然要执行的 JS 脚本“一口气”执行完,要”换气“的时候才会去检查有没有要被处理的“消息”。如果于则将对应消息绑定的回调函数推入栈。当然如果没有绑定事件,这个事件消息实际上会被丢弃,不被处理。比如用户触发了一个 click 事件,但是用户没有绑定 click 事件的监听函数,那么实际上这个事件会被丢弃掉。 我们来看一下加入用户交互之后是什么样的,拿点击事件来说: 12345678910111213$.on(\"button\", \"click\", function onClick() { setTimeout(function timer() { console.log(\"You clicked the button!\"); }, 2000);});console.log(\"Hi!\");setTimeout(function timeout() { console.log(\"Click the button!\");}, 5000);console.log(\"Welcome to loupe.\"); 上述代码每次点击按钮,都会发送一个事件,由于我们绑定了一个监听函数。因此每次点击,都会有一个点击事件的消息产生,浏览器会在“空闲的时候”对应将用户绑定的事件处理函数推入栈中执行。 伪代码: 12345while (true) { if (queue.length > 0) { queue.processNextMessage(); }} 动画演示: (在线观看) 加入宏任务&微任务我们来看一个更复制的例子感受一下。 123456789101112131415console.log(1);setTimeout(() => { console.log(2);}, 0);Promise.resolve() .then(() => { return console.log(3); }) .then(() => { console.log(4); });console.log(5); 上面的代码会输出:1、5、3、4、2。 如果你想要非常严谨的解释可以参考 whatwg 对其进行的描述 -event-loop-processing-model。 下面我会对其进行一个简单的解释。 浏览器首先执行宏任务,也就是我们 script(仅仅执行一次) 完成之后检查是否存在微任务,然后不停执行,直到清空队列 执行宏任务 其中: 宏任务主要包含:setTimeout、setInterval、setImmediate、I/O、UI 交互事件 微任务主要包含:Promise、process.nextTick、MutaionObserver 等 有了这个知识,我们不难得出上面代码的输出结果。 由此我们可以看出,宏任务&微任务只是实现异步过程中,我们对于信号的处理顺序不同而已。如果我们不加区分,全部放到一个队列,就不会有宏任务&微任务。这种人为划分优先级的过程,在某些时候非常有用。 加入执行上下文栈说到执行上下文,就不得不提到浏览器执行JS函数其实是分两个过程的。一个是创建阶段Creation Phase,一个是执行阶段Execution Phase。 同执行栈一样,浏览器每遇到一个函数,也会将当前函数的执行上下文栈推入栈顶。 举个例子: 1234567891011function a(num) { function b(num) { function c(num) { const n = 3; console.log(num + n); } c(num); } b(num);}a(1); 遇到上面的代码。 首先会将 a 的压入执行栈,我们开始进行创建阶段Creation Phase, 将 a 的执行上下文压入栈。然后初始化 a 的执行上下文,分别是 VO,ScopeChain(VO chain)和 This。 从这里我们也可以看出,this 其实是动态决定的。VO 指的是variables, functions 和 arguments。 并且执行上下文栈也会同步随着执行栈的销毁而销毁。 伪代码表示: 12345const EC = { scopeChain: {}, variableObject: {}, this: {},}; 我们来重点看一下 ScopeChain(VO chain)。如上图的执行上下文大概长这个样子,伪代码: 12345678910111213141516171819202122232425262728global.VO = { a: pointer to a(), scopeChain: [global.VO]}a.VO = { b: pointer to b(), arguments: { 0: 1 }, scopeChain: [a.VO, global.VO]}b.VO = { c: pointer to c(), arguments: { 0: 1 }, scopeChain: [b.VO, a.VO, global.VO]}c.VO = { arguments: { 0: 1 }, n: 3 scopeChain: [c.VO, b.VO, a.VO, global.VO]} 引擎查找变量的时候,会先从 VOC 开始找,找不到会继续去 VOB…,直到 GlobalVO,如果 GlobalVO 也找不到会返回Referrence Error,整个过程类似原型链的查找。 值得一提的是,JS 是词法作用域,也就是静态作用域。换句话说就是作用域取决于代码定义的位置,而不是执行的位置,这也就是闭包产生的本质原因。 如果上面的代码改造成下面的: 123456function c() {}function b() {}function a() {}a();b();c(); 或者这种: 12345678function c() {}function b() { c();}function a() { b();}a(); 其执行上下文栈虽然都是一样的,但是其对应的 scopeChain 则完全不同,因为函数定义的位置发生了变化。拿上面的代码片段来说,c.VO 会变成这样: 123c.VO = { scopeChain: [c.VO, global.VO],}; 也就是说其再也无法获取到 a 和 b 中的 VO 了。 总结通过这篇文章,希望你对单线程,多线程,异步,事件循环,事件驱动等知识点有了更深的理解和感悟。除了这些大的层面,我们还从执行栈,执行上下文栈角度讲解了我们代码是如何被浏览器运行的,我们顺便还解释了作用域和闭包产生的本质原因。 最后我总结了一个浏览器运行代码的整体原理图,希望对你有帮助: 下一节浏览器的事件循环和NodeJS的事件循环有什么不同, 敬请期待~ 参考 Node.js event loop - logrocket event-loop - nodejs.org what-is-the-execution-context-in-javascript Event Loop in JS - youtube","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"浏览器","slug":"前端/浏览器","permalink":"https://lucifer.ren/blog/categories/前端/浏览器/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"},{"name":"浏览器","slug":"浏览器","permalink":"https://lucifer.ren/blog/tags/浏览器/"},{"name":"事件循环","slug":"事件循环","permalink":"https://lucifer.ren/blog/tags/事件循环/"}]},{"title":"每日一荐 2019-12 汇总","slug":"daily-featured-2019-12","date":"2019-12-01T16:00:00.000Z","updated":"2023-01-07T14:01:24.665Z","comments":true,"path":"2019/12/02/daily-featured-2019-12/","link":"","permalink":"https://lucifer.ren/blog/2019/12/02/daily-featured-2019-12/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-122019-12-31[见闻]今天是我的生日,祝我生日快乐 🎂 ~~~~ 一般公司的卫生间贴的都是公司信息或心灵鸡汤,但是谷歌卫生间贴的是如何找到代码 Bug,不得不感叹“这实在是太硬核了”! 2019-12-24[技巧]今天是平安夜,苹果 🍎 和圣诞礼物 🎁 都收到了么? 今天给大家推荐的是一个 linux 中非常常见的命令 grep 的常用用法。 图版本: 文字版本: Matches patterns in input text.Supports simple patterns and regular expressions. Search for an exact string:grep search_string path/to/file Search in case-insensitive mode:grep -i search_string path/to/file Search recursively (ignoring non-text files) in current directory for an exact string:grep -RI search_string . Use extended regular expressions (supporting ?, +, {}, () and |):grep -E ^regex$ path/to/file Print 3 lines of [C]ontext around, [B]efore, or [A]fter each match:grep -C|B|A 3 search_string path/to/file Print file name with the corresponding line number for each match:grep -Hn search_string path/to/file Use the standard input instead of a file:cat path/to/file | grep search_string Invert match for excluding specific strings:grep -v search_string 2019-12-23[学习方法]很多人问我如何保持高效率。 首先要说的是我的效率并不是很高,这也就是我为什么还在不断学习提高效率的原因之一。那么既然有人问了我就班门弄斧回答一下,大家有什么好的方法和技巧欢迎提出来交流。 为了让自己保持高效率,我自己开了一个仓库记录了自己保持“高效率”的方式。希望可以给大家启发,本仓库内容持续更新~ 仓库大纲: 仓库截图: 仓库地址:https://github.com/azl397985856/To-Be-Productive 2019-12-20[新闻]Facebook 发布 Hermes, 一个新的专门用于 React Native 的 JS 引擎。 文章地址:https://facebook.github.io/react-native/blog/2019/07/17/hermes 2019-12-19[好文]很多高级语言有自动的垃圾回收器,比如 JS,JAVA,Go 等。其会自动地进行垃圾回收工作,而不必像诸如 C 和 C++那样手动分配和清除内存。 对于 old space 的垃圾回收算法有一个是标记清除,从一个根对象开始对于所有可达的对象进行标记,剩下的就是不可达的,我们将其进行清除,本文讲解了三色标记法(黑色,白色和灰色),三色标记法本质上进行一次 DFS,并将内存对象分到三个部分,DFS 完成之后清除不可达的内存(白色)。这篇文章以动画形式讲解了三色标记法的具体过程。 文章(《一张图了解三色标记法》)地址:http://idiotsky.top/2017/08/16/gc-three-color/ 2019-12-18[教程]哈弗大学 CS50 系列,内容持续更新,现在最新的是 2019 年。 你可以跟着教程来重新学习 CS 基础。 地址:https://cs50.harvard.edu/college/ 2019-12-17[网站]Learn Git Branching 是一个交互式学习 Git 的网站。沙盒里你能执行相应的命令,还能看到每个命令的执行情况; 通过一系列刺激的关卡挑战,逐步深入的学习 Git 的强大功能,在这个过程中你可能还会发现一些有意思的事情。 地址: https://learngitbranching.js.org/ 2019-12-16[新闻]最新版本的 Chrome 和 Firefo 浏览器取消 EV 证书的显示。 只有用户点击了锁 🔒,才会显示出 EV 证书的信息。 为什么会这样?想要知道答案的可以点击原文阅读。 原文地址:Chrome and Firefox Changes Spark the End of EV Certificates 2019-12-13[类库]loki 是一个 React Storybook 组件回归测试工具。React Storybook 是一个我 15 年就开始关注的一个工具,本身的设计思想我比较喜欢。现在除了支持 React,也支持 React Native,Vue,Angular 等,甚至最新的 Svelte 也支持。 loki Github 地址: https://github.com/oblador/loki 2019-12-12[技巧]Angular 的 Commit Message Conventions 是一套很流行的 Commit Message 规约。简单方便,一目了然,更重要的是这种约定化如果形成一种默契,不管对于之后查看,还是生成各种外部资料(比如 CHNAGELOG)都是非常方便的。 详细信息: https://gist.github.com/stephenparish/9941e89d80e2bc58a153 相关工具也有很多,我个人使用的是Commitizen 2019-12-11[好文]文章标题 《花椒前端基于 WebAssembly 的 H.265 播放器研发》,本文从背景介绍,技术调研,实际方案到最后的实践效果,完整地讲述了通过 wasm 将 H.265 应用到不支持其的浏览器的过程。干货满满,其架构图画的也是我比较喜欢的风格。 文章地址: https://zhuanlan.zhihu.com/p/73772711 2019-12-10[技巧]我们有时候需要在终端访问一些国外的资源。我目前采取的措施主要是给终端设置 proxy。 12alias proxy='export all_proxy=socks5://127.0.0.1:1086'alias unproxy='unset all_proxy' 其中socks5://127.0.0.1:1086是我的本机的正向代理地址。 如下是使用效果: 如图显示我们代理成功了,而且我们可以方便的在不想要代理的时候去掉代理。 2019-12-09[类库]对于前端,我们经常需要将组件进行可视化的展示。在 Vue 中,我们通常会用 docsify 或者 vuepress 等。而对于 react 比较有名的有 storybook 和 docz。 当然这并不是绝对的,比如 storybook 也在支持 vue 和 webcomponents。 2019-12-06[技能]在分析 CPU、内存、磁盘等的性能指标时,有几种工具是高频出现的,如 top、vmstat、pidstat,这里稍微总结一下: CPU:top、vmstat、pidstat、sar、perf、jstack、jstat;内存:top、free、vmstat、cachetop、cachestat、sar、jmap;磁盘:top、iostat、vmstat、pidstat、du/df;网络:netstat、sar、dstat、tcpdump;应用:profiler、dump 分析。排查 Java 应用的线上异常或者分析应用代码瓶颈,可以使用阿里开源的 Arthas ,nodejs 应用可以使用 alinode 2019-12-05[好文]如果你想做微前端,一定要能够回答出这 10 个问题。 微应用的注册、异步加载和生命周期管理; 微应用之间、主从之间的消息机制; 微应用之间的安全隔离措施; 微应用的框架无关、版本无关; 微应用之间、主从之间的公共依赖的库、业务逻辑(utils)以及版本怎么管理; 微应用独立调试、和主应用联调的方式,快速定位报错(发射问题); 微应用的发布流程; 微应用打包优化问题; 微应用专有云场景的出包方案; 渐进式升级:用微应用方案平滑重构老项目。 今天推荐的这个文档,区别与别的微前端文章的点在于其更加靠近规范层面,而不是结合自己的业务场景做的探索。这篇文章来自于阿里团队。 文章地址: https://mp.weixin.qq.com/s/rYNsKPhw2zR84-4K62gliw 2019-12-04[工具]相信大家使用 shell 的时候,会经常碰到忘记的 option,或者某一个用法记不清楚。遇到这种问题通常我们会用 man 或者命令提供的—help 查看用法。 这里给大家介绍另外一种工具tldr, 它是一个将 man page 进行简化,将大家常用的用法总结出来的工具。 安全也非常简单,只需要 npm install -g(前提是你必须安装 node), 如果你不想安装也没有关系,它还提供了web 版。另外你也可以参考这里定制你的主题 仓库地址: https://github.com/tldr-pages/tldr 2019-12-03[技巧]今天给大家介绍的是Google高级搜索技巧。我们经常使用搜索引擎搜索一些东西,不管是遇到问题想寻求解决方案也好,想学习一些新东西也好,掌握一定的搜索技巧是可以让你搜索的过程事半功倍,尤其是常用的技巧一定要记住。 2019-12-02[软件]我们公司在使用的一个完全开源的堡垒机,是符合 4A 的专业运维审计系统。 地址: https://github.com/jumpserver/jumpserver 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-12","slug":"每日一荐/2019-12","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-12/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"每日一荐 2019-11 汇总","slug":"daily-featured-2019-11","date":"2019-11-03T16:00:00.000Z","updated":"2023-01-07T14:01:24.660Z","comments":true,"path":"2019/11/04/daily-featured-2019-11/","link":"","permalink":"https://lucifer.ren/blog/2019/11/04/daily-featured-2019-11/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-112019-11-29[网站]之前我在每日一题出了一道题 2019-08-16 - 怎么在浏览器中操作本地的文件。 一个可行的思路就是在本地创建一个服务器,比如 node 服务器,浏览器操作发送请求到服务器,然后 node 去操作本地文件。 现在 Chrome 支持 native fs api,使得这个过程原生支持,想要体验的可以访问这里 https://labs.vaadin.com/native-fs/ , 如果你愿意,你可以将它添加为 Chrome APP。 2019-11-28[工具]今天给大家推荐的工具是效率类软件 Alfred 的灵魂 workflows(工作流)。 以下是我使用频率比较高的 workflow,使用频率低的会被我定期清理掉。 我自己制作的工作流不在这里展示和推荐 下载地址: Chrome Bookmarks Colors Dash Kill Process RamdaDocs TerminalFinder Yosemite iOS Simulator Youdao 2019-11-27[软件]mac 端不能右键新建文件,这在某些时候不是很方便。 没有这个软件的时候,我是在编辑器中新建或者是使用 touch 命令。 使用了这个New File Menu软件之后多了一种更方便的选择,你可以直接右键新建,Just Like Windows Users。 地址: https://apps.apple.com/cn/app/new-file-menu/id1064959555?l=en&mt=12 2019-11-26[网站]一个网站,可以分析的 Github 仓库,采用手绘风格,对于厌倦了标准图形的我来说简直是耳目一新的感觉。 项目地址: https://repo-analytics.github.io/ 2019-11-25[技巧]Chrome 开发者工具有非常好用但是却鲜为人知的功能。今天给大家介绍一个功能 - 代码覆盖率。 指的是你下载的代码有多少是被执行了的,这在做性能优化的时候很有用。如果一些代码根本没执行,就可以延迟加载。 使用方法: Ctrl+Shift+P for windows CMD+Shift+P for mac 输入 coverage, 选择如图的选项,并确定。 然后你就能看到检测结果了: 2019-11-22[工具]Github Large File Storage (简称 git-lfs), 可以用来存储大文件,比如上 GB 的大文件,相比于传统的 Github 存储,这种方式空间更大,速度更快,并且工作流程和传统 Git flow 并无二致。 地址:https://git-lfs.github.com/ 2019-11-21[娱乐]文章标题 【The skeptic’s guide to interpreting developer marketing speak 🗺️】 - 暂翻译为【如何翻译开发人员的营销术语】 里面讲了各种开发人员常用的营销术语,以及对应我们应该怎么去解读 ta。比如: 现代化 就是说刚刚git init AI加持 就是说无数的if else switch case语句 最小化 就是说没有测试用例,没有例子 跨平台 就是说我听说Electron可以跨平台,于是我就用它写了 … 文章地址: https://changelog.com/posts/the-skeptics-guide-to-interpreting-developer-marketing-speak 2019-11-20[工具]微信的一个插件,功能有很多。 不过目前已经不再维护了。 消息自动回复 消息防撤回 远程控制(已支持语音) 微信多开 第二次登录免认证 聊天置底功能(类似置顶) 微信窗口置顶 会话多选删除 自动登录开关 通知中心快捷回复 聊天窗口表情包复制 & 存储 … 我用的比较多的功能恐怕就是双开和防撤回了。 消息防撤回 微信多开 仓库地址:https://github.com/TKkk-iOSer/WeChatPlugin-MacOS/tree/master 2019-11-19[工具]JS 依赖检测工具,可以用来生成图片,可视化程度很高,还可以做成自动化,集成到 CI CD ,支持 CommonJS,AMD 和 ES Module。 项目地址: https://github.com/pahen/madge 2019-11-18[娱乐]今天给大家推荐一个在线 nokia 短信图片生成器,可以自己输入短语,一键生成。 网站地址: https://zzkia.noddl.me:8020/ 2019-11-15[网站]有的什么我们需要在 Google Play 上下载软件,但是苦于没有通畅的网络(关于如何获取畅通的网络我在 2019-11-01 讲到,感兴趣可以翻过去看看)。因此一个 Google Play 镜像就很重要了。 这就如同我们 npm 和 cnpm 的关系。我们可以在这里直接下载 apkx。 apkx 需要特殊的安装工具,或者一些小技巧才能安装。 网站地址: https://apkpure.com/ 2019-11-14[技巧]很多时候我们会看到一些英文的简写。比如邮件,IM 等,这些简称能够帮我们提高沟通效率,如果你不知道一些常见的简写,沟通的时候就难免有障碍,以下是一些常见的简写,欢迎补充。 2019-11-13[技巧]今天要分享的是关于 Bash 中历史记录那些事。 第一个要介绍的是history, history is an alias for fc -l 1,你可以通过这个命令来查看最近你使用的命令。 然后你可以用!n(n 指的是 history 命令返回的命令编号)再次执行。其中有许多缩写,最有用的就是 !$, 用于指代上次键入的参数,!! 可以指代上次键入的命令。 第二个要介绍的是历史搜索ctrl + r, 然后输入你想搜索的关键字即可 第三个要介绍的是上下方向键,你可以通过他在历史记录中上下移动。即按下上返回当前上一个命名,按下下返回当前下一个命令。 还有一个小插曲,似乎和历史有那么一点点关系。 就是cd -,切换到上一次的工作路径 如果你还知道什么和历史记录相关的命令,欢迎大家补充。 2019-11-12[技巧]dig 命令是常用的域名查询工具,可以用来测试域名系统工作是否正常。 如下dig lucifer.ren, 可以发现很多信息,包括域名最终解析到了到了另外一个域名azl397985856.github.io, IP 是185.199.108.153. 这个工具在很多情况下非常有用,尤其是对于喜欢命令行的你来说。 其实 dig 是usr/bin下的一个可执行文件,更多用法请man dig查看。 12~ type dig# dig is /usr/bin/dig 2019-11-11[分享]今天是双十一,大家剁手快乐。 今天给大家分享一下前一段时间刚刚举行的大会React Conf 2019,这个是 React 最高规模的技术会议。喜欢 React 的小伙伴千万不要错过了,这里有全套视频。 地址:https://www.youtube.com/playlist?list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh 2019-11-08[好文]前几天读了一篇文章《Scaling webapps for newbs & non-techies》,文章从最简单的单体应用,逐步讲到大型应用架构,不仅讲的通俗易懂,并且图画的也非常好,是我喜欢的风格。 很期待他的第二篇《the cloud for newbs and non-techies》。 (A single server + database) (Adding a Reverse Proxy) 文章地址: https://arcentry.com/blog/scaling-webapps-for-newbs-and-non-techies/ 2019-11-07[学习方法]前一段时间看了一篇文章 -《如何构建自己的产品知识库》。这篇文章的亮点在于其所提到的技巧能够横向类比到任何领域。换句话说你可以按照它将的方法构建你自己的知识库。 里面有一句话产品知识体系是对产品知识搜集、筛选、整理后形成的知识组合,并且这些知识能够用于解决实际遇到的问题。 学习任何知识又何尝不是呢?很多人问我学习方法,其实这个东西非常地系统,很难通过几个技巧完成,也很难在短期内看到很明显的效果。大家可以看一下,说不定对你的学习和生活所有启发,即便你不是一个产品经理。 文章地址: https://www.toutiao.com/a6738596936057618951/ 后期如果有机会的话,我也会分享一下自己的学习方法 2019-11-06[工具]像 PS 和 Sketch 一样,figma 也是一个设计工具,和其他相比团队显得更简单,这点有点像蓝湖。做设计的同学要了解起来了。 地址: https://www.figma.com/ 2019-11-05[观点]VSCode 和 MDN 进行了官方联动,详情. 再也不用跳出 IDE 用 Dash 查了。 用 Alfred + Dash 虽然方便,但是不免有一种应用跳出的感觉。现在就很方便了,如果之后有更多的联动支持,相信体验会越来越好。 2019-11-04[好文]最近几年啊,我本人也看了很多关于微服务的介绍,理念,落地等技术文章,今天给大家推荐一篇阿里飞冰团队发布的技术文,这或许是最简单的微服务落地技术文章。这篇文章详细讲述了业务场景,并详细记录了解决问题的过程以及对比了业界的一些解决方案,管中窥豹,让读者慢慢走进微服务,从这篇文章可以学习到icestark这个微服务的解决方案是怎么从从到有再到落地产生实际业务价值的。 文章地址: https://zhuanlan.zhihu.com/p/88449415#h5o-9 2019-11-01[工具]身为一个程序员,科学上网是标配。市面上免费的软件大多不稳定,出了问题很难及时解决。 自建服务器虽然好,但是还是有一点繁琐的,尤其是碰到了“开会”,IP 端口就会被封锁,自己处理就比较麻烦了。 今天给大家推荐一下 SSNG 的订阅功能,有了这个订阅地址就相当于有了无数的自建服务器,然后你可以在不同的节点之间进行切换。一般而言,我会对服务器进行测速,然后选择速度最快,如果某一个服务器挂了,我只需要一键切换到另外一个即可,无需额外操作。 市面上有很多这种订阅服务,这里推荐一个付费的服务 KyCloud,挺便宜的,我订阅的是 45/季度,平均一个月 15,50G 流量,基本对于我来说非常够用了。 使用方式也非常简单,只需要以下三步即可。 下载对应客户端 点击复制订阅地址 将地址粘贴到客户端 提示: 你也可以像我一样测速,然后根据速度选择节点。 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-11","slug":"每日一荐/2019-11","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-11/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"每日一荐 2019-10 汇总","slug":"daily-featured-2019-10","date":"2019-10-09T16:00:00.000Z","updated":"2023-01-07T13:58:09.045Z","comments":true,"path":"2019/10/10/daily-featured-2019-10/","link":"","permalink":"https://lucifer.ren/blog/2019/10/10/daily-featured-2019-10/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-102019-10-31[技能]虽然我们不是专门的网络工程师,但是不免在实际工作以及面试中会需要这方面的知识,当然这是浅层的即可。如果完全不知道,那么对于一些网络性能优化问题肯定是没有概念,从而无从下手的。 网上关于 HTTP 协议的文章很多,面试的时候关于 HTTP 三大版本的差异也是热门考点。这篇文章就通俗易懂地解释了HTTP/2与HTTP/3 的新特性, 相比于之前,为什么要推出新的协议,核心解决了什么问题?有什么不足? 这些东西网上资料要么就是太专业,要么就是太笼统和人云亦云,这篇文章是相对比较适合新手读的一个文章。 文章地址: https://segmentfault.com/a/1190000020714686#articleHeader16 2019-10-30[类库]很多年前我自己写过一个简陋的pub/sub库, 这个仓库甚至被一些人在用。很多时候我们需要用到这种灵活的pub/sub功能,这个时候我们就会自己实现,或者用一些开源的。 今天给大家介绍的就是鼎鼎大名的 Jason Miller 写的一个tiny event pub sub implement,代码就几十行,不仅注释详实,而且给出了丰富的类型定义,代码简单易懂,非常适合学习。 代码地址: https://github.com/developit/mitt/blob/master/src/index.js 2019-10-29[网站]上一次给大家分享了一个常用正则的 VSCode 插件(2019-10-11 期),另外的《大前端面试宝典》也讲到了正则的学习,并给出了我认为非常不错的学习资料。 今天我继续给大家介绍几个正则学习&练习的网站。 Regex Golf 是一个有名的正则练习网站,会根据你的正则打分,难度偏高。 regexone 是一个交互友好,面向新手的一个正则练习网站,可以交互式地学习正则,右边还贴心地给出了 Notes,另外语言不同其实正则规范也不太一样,这个网站给出了不同语言的正则讲解,很用心。值得一题的是,里面的资料非常新,最新的/u- interpret the regular expression as unicode codepoints 都有。 regexr这个不是练习的, 是用来可视化的, 和之前的regexper有点像,就连域名都差不多,不过这个用户体验是真的棒。 The regular expression game 是一个过关类型的正则练习网站,有意思的是它可以根据你写的正则匹配程度进行打分,即使你没有全部匹配也是可以得分的。 2019-10-28[工具]这个是人称贺老的百姓网贺师俊Hax整理的一份中文技术活动日程, 这些活动有几个特点: 技术活动的主要语言是中文 技术活动的主要参与者是程序员 技术活动的主要日程接受公开报名 技术活动具有一定规模 目前这个仓库仅有个简陋的 yaml 数据文件,记录技术活动的时间和一些信息。后续希望能加入一个更好的查询界面。活动组织者可直接修改数据文件并提交 PR,或提交 issue 描述一下活动情况。活动组织者也可以 watch 本仓库,这样当有变动时(通常是会议信息更新),可以收到通知。 仓库地址:https://github.com/hax/chinese-tech-conf-schedule 2019-10-25[工具]今天给大家推荐的是一个我个人非常喜欢的一个好用且免费图床工具 - IPic。 这个工具不仅内置免费的微博图床,还可以自己定义,我本人还添加了腾讯云的 COS 使用起来也非常简单,直接复制图片,然后点击对应的图片即可,另外值得一提的是它本身还支持直接生成 MardDown 链接,这对经常用 MarkDown 写作的我来说绝对是一个非常实用的功能。 另外它还搭配了一个软件IPic Mover用来迁移图床,比如你的文章里面都是新浪图床,只需要一键就可以瞬间迁移到别的平台,比如腾讯云 COS。 不过这个搭配的工具是收费的,但是有免费体验时间。 2019-10-24[技能]今天是 1024 程序员节,祝广大的程序员们节日快乐。🎩🎩🎩🎩🎩🎩 之前给大家介绍了一款跨平台的 Web 平台技术栈检测工具 Wappalyzer。这几天我看了下他的源码,觉得很不错,于是就想着推荐给大家。 Wappalyzer 的整体架构非常有意思,这里讲一下我发现的主要特点,更多细节等待着你的探索。 平台相关的代码放在 drivers 文件夹下,公共的代码在 src/wappalyzer.js 特地写了 validate 脚本来检测代码。 将检测逻辑抽离到了 src/app.json 中, 以配置文件的形式存放(这个 json 文件结构设计地很精巧,应该是花了心思的) 主要采用正则来检测应用 考虑到间接引用,比如框架 A 引用了 B,那么检测到了 A 也会把 B 带上 如果想快速上手可以看下 ta 提供的测试用例,非常简洁。 Github 地址: https://github.com/AliasIO/Wappalyzer 2019-10-23[网站]今天给广大的前端朋友们介绍一个在线做题的网站,可以瞬间在线知道答案,而且不需要登陆,一共 58 道题目,不知道后续会不会更新。 这个网站的题目我看了其实没有什么新意,但是不需要登陆而是直接使用 LocalStorage 来存储你的答题情况对用户来说很轻量,给我的感觉很好,感兴趣的可以试一下。 (题目列表) (题目详情) 网址: https://quiz.typeofnan.dev/ 2019-10-22[观点]前一段时间王思聪的股权遭到了冻结,据中新经纬记者计算,王思聪名下冻结股权价值合计已经超过 8445 万元。 “这种情况一般是王思聪欠别人钱,别人追讨,作为诉讼保全措施冻结的。”金杜律师事务所律师李量接受虎嗅采访时说,“王思聪欠钱可以是直接欠,或和贾跃亭一样,给别人提供担保,承担了连带责任。” 但是实际上这种冻结对于王思聪来说根本起不到作用,他会有很多方法来免除所谓的强制执行,他只要将自己的股权先一步将股权质押给万达集团,这样质押权人就对被保全的股份有优先权, 换句话说这对王思聪来说这种冻结根本无效。 现实中有很多这样的事情,这些规则似乎是在限制那些“能力不足”的人,而对社会上这些“制造规则的人”无能为力。更可悲的是,很多人对这些不知道,不关注。其实越是贫穷的家庭,越是生活在社会底层的人,他们的后代,大概率还会是穷人。其实,我们奋斗的目标无非就是:让子女一出生就站在了别人的肩膀上! 2019-10-21[效率]我是一个有着轻微强迫症的人,社交软件的小红点有时候会打乱我的节奏,将我从专注模式(Focus Mode)强制转化为发散模式(Diffuse Mode)。 这两种模式适合我们做不同类型的工作,因此掌控小红点,避免这种不希望的模式切换是提高效率的一个有效途径。 我的做法是: 手机静音 电脑关闭红点提醒 mac 电脑可以在系统偏好设置 - 通知 - 对应 APP - 将标记应用程序的勾去掉 一般而言,你也不用担心会错过什么东西,因为右侧通知还是会有的,比如钉钉的会议提醒等。 经过这样的设置,你就可以自由切换两种模式,而不会被频繁打断,当然还是会有人来打断你,这个问题我们以后再讲。 2019-10-18[类库]UMI 的官方定位是可插拔的企业级 react 应用框架。其作者云谦也是 Ant-Design,dvajs 的核心贡献者,同时也是我早期关注的人之一。这个项目的价值绝对不亚于更受大家欢迎的 dvajs,是一个值得学习的项目。 说白了,Umi 和 create-react-app(cra)类似,就是现有技术栈的组合,封装细节,让开发者用起来更简单,只写业务代码就可以了,它有几个特点: 零配置就是默认选型都给你做好了。 开箱即用就是技术栈都固化了。 约定大于配置,开发模式也固化好了。 下图是云谦介绍 umi 的定位的时候贴的一张架构图: 项目地址:https://github.com/umijs/umi 2019-10-17[工具]之前分享过《2019-09-23 - 为什么一行 80 个字符是一个值得遵守的规范,即使你拥有 4k 显示器?》,里面提到了并排窗口的问题。 多个显示器确实可以提高效率,如果你能高效地利用每一个显示器,效果会更棒。 配合 4k 大屏显示器效果更棒 今天介绍的这款工具就是一款窗口布局工具,能够快速修改当前窗口大小并放置在指定位置,Moom 默认操作点设立在了窗口左上角的绿色按钮上,将鼠标 hover 在绿钮上就会弹出一个选择菜单,里面有五种尺寸可选,单击选项即可变化窗口大小,并能将窗口移动到指定位置。 搭配使用快捷键效果更棒 2019-10-16[工具]今天给大家推荐的是一款非常好用的 Chrome 插件,可以用来查看网站是由什么技术栈构建的。其实类似的软件也有别的,但是这个是我使用过的最好的一个。 (这个是其官网的检测结果) (这个是 GitHub 的检测结果) 项目主页: https://www.wappalyzer.com 2019-10-15[技能]今天给大家分享一个微信小技巧,据说有的人还不知道,所以今天就把它分享出来,大家如果有什么微信使用小技巧也欢迎在下方进行评论。 今天的小技巧是判断对方是否把你拉黑或者删除: 给对方转账,是好友会让你输入密码,不是好友都不用你输入密码,直接弹出下图,整个过程好友不知情的! 如果拉黑会提示: 请确认你和他(她)的好友关系是否正常 如果删除,则会提示: 你不是收款方的好友 点开好友名片,如果显示左图,说明她真的没有发过一条朋友圈。若显示右图,点开个人相册却什么也看不到,那么你有可能被删除、拉黑、朋友圈屏蔽,或者发过朋友圈但设为私密了。 为了搞清楚对方是删除还是屏蔽,你就可以用到开头转账的那一招啦! 2019-10-14[好文]如果想做一些高级的东西,编译是一个绕不过的坎,Babel 是一个前端的转义工具,Babel 有着自己的插件系统,这是个系列文章,通过这个系列你可以学到 AST,以及 Babel 插件相关的东西,并且你可以自己动手写一个 Babel 插件。 文章地址: Step-by-step guide for writing a custom babel transformation Creating custom JavaScript syntax with Babel 2019-10-12[工具]前端在调试兼容性样式的时候是一个很头疼的问题,各个浏览器以及同一个浏览器不同版本支持的 css 都是不同的,比如有些不支持 Grid,有些不支持 cal 函数。如果你自己根据这些去修改代码肯定是非常低效的,这个 Chrome 插件就是解决这样的问题,你可以在高级的浏览器上调试,自行禁用一些 css 特性来 debug。 仓库地址: https://github.com/keithclark/css-feature-toggle-devtools-extension chrome 插件地址: https://chrome.google.com/webstore/detail/css-feature-toggles/aeinmfddnniiloadoappmdnffcbffnjg 2019-10-11[工具]常用正则大全, 支持 vscode 扩展插件 值得一提的是它支持 VSCode 插件形式使用: 目前有 57 个正则: 插件地址: https://github.com/any86/any-rule 2019-10-10[技能]傅里叶变换是一种在各个领域都经常使用的数学工具。这个网站将为你介绍傅里叶变换能干什么,为什么傅里叶变换非常有用,以及你如何利用傅里叶变换干漂亮的事。傅立叶变换有很多实际的应用,比如 MP3 的原理,MP3 是如何将声波转化为二进制,并如何进行压缩的? 比如 JPEG 的原理等。 这个文章(网站)是我见过傅立叶变换最直观的一个解释之一,并且支持交互式操作。 网站地址: http://www.jezzamon.com/fourier/zh-cn.html 2019-10-09[工具]VSCode 是我经常使用的一个软件,结合自己的开发习惯我也会增加很多配置和插件等,如何将这些插件进行备份以便将来换电脑可以及时同步过来,这里关于 VScode 的配置我用的是 VSCode setting sync 插件。 这个需要结合 Gist 使用,具体使用方式请查看官方文档: 其实我有一个专门的开发常用配置文件备份仓库用来存放这些东西,这是我的仓库存放的配置,这样我即使换了电脑也能很快地用到我最舒服的配置。 如果大家没有更好的方式,不妨采用这种方式,如果你有更好的方式欢迎给我留言。 2019-10-08[工具]今天是国庆结束的第一天,大家假期玩的怎么样? 希望大家可以尽快从假期的状态中转变回来。今天给大家推荐一个我个人使用比较多的一个功能,就是剪贴板历史。 我在使用手机的时候(笔者使用的是安卓机),会经常复制一些文字或者图片,然后进行粘贴,有时候会需要粘贴之前复制的一个东西,因此剪贴板历史就显得很重要,手机上我用的就是搜索输入法自带的剪贴板历史功能。 而在电脑上我使用的是 Alfred 自带的剪贴板历史功能,只不过默认不开启,你需要去配置一下才行。 然后你就可以查看你的剪贴板历史了: 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-10","slug":"每日一荐/2019-10","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-10/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"《LeetCode题解攻略》 - 草稿目录","slug":"draft","date":"2019-10-02T16:00:00.000Z","updated":"2023-01-05T12:24:49.655Z","comments":true,"path":"2019/10/03/draft/","link":"","permalink":"https://lucifer.ren/blog/2019/10/03/draft/","excerpt":"这个我为自己的新书写的一个目录,计划在一星期左右定下来大体目录,然后投入完善,希望大家多提意见,你的意见很可能会影响到这本书的内容,期待你以特别的方式参与进来,此致敬礼。","text":"这个我为自己的新书写的一个目录,计划在一星期左右定下来大体目录,然后投入完善,希望大家多提意见,你的意见很可能会影响到这本书的内容,期待你以特别的方式参与进来,此致敬礼。 1. 准备知识1.1 学习这本书之前需要什么基础很多人觉得算法很难,需要很多公式以及数学知识。 其实并不是这样的,除非你是做算法岗位,否则并不会要求你对数学,几何学,概率学有多深的造诣,其实更看重的是分析问题,解决问题的能力和基础编码能力。 但是我们不排除 LeetCode 有一些数学题目,我们会在后面的章节中讲到,但是说实话 LeetCode 的数学题目不会涉及很难的数学知识。而且通常我们也可以通过变通的方式解决,比如 LeetCode 有一道水壶倒水的问题,以下是题目描述: 1给你一个装满水的 8 升满壶和两个分别是 5 升、3 升的空壶,请想个优雅的办法,使得其中一个水壶恰好装 4 升水,每一步的操作只能是倒空或倒满。 这道题我们可以用 GCD(最大公约数)解决,如果你不知道这个数学知识点也没问题。 我们可以通过 BFS 来解决,其实学习算法尤其是刷 LeetCode 只需要我们掌握简单的数学知识,高中的数学知识通常来说就足够了。 另外一个大家需要掌握的数学知识是关于后面要讲的复杂度分析,这里需要一点简单的数学知识,不过不要担心,非常简单,不会有高等数学的内容。 学习本书最好你对一种编程语言比较熟悉,出于读者的受众群体和代码简洁性,我选取了 Python 作为本书的主要编程语言,如果大家对 Python 不熟悉也没有关系,我会尽量少使用语言特有的语法糖,从而减少大家对于语言层面的认知负担。 另外 Python 被誉为最容易入门的语言之一也是实至名归,大家可以放心。 退一步讲,语言不应该成为我们学习算法的障碍,不是么?那让我们一起进入 LeetCode 的世界吧! 1.2 基础数据结构和算法在真正的刷题之前,我们一定要先打好基础,学好基本的数据结构和算法,然后以练代学进行提升和消化。 从广义上来说,数据结构其实就是数据的存储结构,算法就是操作数据的方法。而平时以及本书所探讨的其实是更为狭义角度的数据结构和算法。其实指的是某些非常典型的数据结构和算法,比如数组,链表,栈,队列,树,图等数据结构,以及二分法,动态规划,快速排序等经典的算法。 数据结构是为算法所服务的,而算法是要建立在某一种或者几种数据结构之上才可以发挥作用,这两个是相辅相成的关系。某些算法一定要建立在某种数据结构之上才行,相信你读完这本书会对这句话产生更为深刻的印象。 本书要讲的内容就是在 LeetCode 上反复出现的算法,经过进一步提炼,抽取近百道题目在这里进行讲解,帮助大家理清整体结构,从而高效率地刷题。 我这里总结了 7 个常见的数据结构和 7 个常见的算法以及 5 个常见的算法思想。 7 个数据结构分别是: 数组,栈,队列,链表,二叉树,散列表,图 7 个算法分别是:二分法,递归,回溯法,排序,双指针,滑动窗口,并查集 5 个算法思想分别是:分治,贪心,深度优先遍历,广度优先遍历,动态规划 只有掌握了这些基础的数据结构和算法,更为复杂的算法才能得心应手,刷题才会事半功倍。而 LeetCode 的题目虽然不断出新,但是最终用到的算法永远是那几个,很多题目都是穿着新衣服的老面孔了。大家学好这些基础套路之后会更加明白这个道理。 1.3 如何刷 LeetCodeLeetcode 网站使用方法LeetCode 官网收录了许多互联网公司的算法题目,一度被称为刷题神器,今天我们就来介绍下如何使用 LeetCode 网站,以下所讲述的内容都是以力扣中国为例。 LeetCode 目前有 1000 多道题目,并且一直持续更新,其中有一些是带锁的,需要会员才能查看。 最上面标签栏的 Problems,给出了四个分类:Algorithms、Database、Shell 和 Concurrency,分别表示算法题、数据库题、Shell 和并发题,第一个就是我们所需要刷的算法题,并发是 2019 年才添加的新的模块。 点开 Algorithms 后,我们可以看到一列题目的列表,每个题目都有一个序号,后面的接受率(Acceptance)表示提交的正确率,Difficulty 表示难易程度。这里有个小技巧,衡量一道题目难不难除了看难度之外,还可以看下接受率,接受率越低代表题目越难,这个指标有时候比难度更靠谱。 LeetCode 按难易程度分成了三个级别,分别是 Easy、Medium 和 Hard。 Easy 通常不需要太多思考和也不会有复杂的细节,比较特别适合新手或者拿来热身。 Medium 级别就会有些难度,一般都会涉及到经典的算法,需要一定的思考。 Hard 级别是最难的,有些时候是算法本身的难度,有些时候特别需要你考虑到各种细节。 你可以对题目进行筛选和排序。 如果我们只想要找某一类型的题,可以通过 Tags 或 Company 来筛选。 另外我们在做某一题时,觉得还想再做一个类似的,可以点击题目描述下方 Show Similar Problems 或 Tags 来找到相似的问题。 每个题目都有各自的 Discuss 区域。在这里,许多人都把自己的思路和代码放到了上面,你可以发贴提问,也可以回复别人,里面大神很多,题解质量都很高,如果实在没有思路或者想看下有没有更好的思路可以来逛一下。通常来说我建议你优先看 Post 或者投票最多的。 点开某一个题目,会跳转到具体题目详情页面,你可以在右侧的代码区切换选择自己需要的编程语言。 代码编写完了之后,不要急着提交,先可以测试运行下(Run Code),你可以多写几个测试用力跑一下,没有问题再提交,要知道比赛的时候错误提交要加时间的。 我们可以点开 More Details 查看详细运行结果信息。 每道题旁边的 My Submissions 可以找到自己的对于该题的提交情况,这里可以看到自己过去所有的提交,点 Accepted 或 Wrong Answer 就可以查看自己过去提交的代码情况,包括代码是什么,跑的时间以及时间分布图等。 以上就是 LeetCode 的主要功能,希望通过这一节内容能让你对 LeetCode 网站有所了解,从而更快地进行刷题。 应该怎么刷 LeetCode我本人从开始准备算法以来刷了很多题,自己成长了很多,从刷题菜鸡,到现在对刷题套路,题型有了自己的理解,感受还是蛮多的。我本人不是算法高手,算是勤能补拙类型。不过经过几个月的学习和练习,不仅面试变得更加得心应手,而且在工作中写更容易写出干净优雅,性能好的代码。 对于我来说,刷题的过程其实就是学习数据结构和算法的过程, 不仅仅是为了刷题而刷题,这样你才能感受到刷题的乐趣。刷题至少要刷两遍,理想情况是根据自己的遗忘曲线刷多次,这个我后面也会讲到。 第一遍按 tag 刷 建议第一遍刷的时候可以先快速按照 tag 过一遍,快速感受一下常见数据结构和算法的套路,这样自己有一个感性的认识。 第二遍一题多解,多题同解 第二遍我们就不能像第一遍那样了,这个阶段我们需要多个角度思考问题,尽量做到一题多解,多题同解。我们需要对问题的本质做一些深度的理解,将来碰到类似的问题我们才能够触类旁通。 但是很多人做了几遍,碰到新题还是没有任何头绪,这是一个常见的问题,这怎么办呢? 总结并记忆是学习以及刷题过程中非常重要的一环, 不管哪个阶段,我们都需要做对应的总结,这样将来我们再回过头看的时候,才能够快读拾起来。 anki 就是根据艾宾浩斯记忆曲线开发的一个软件,它是一个使记忆变得更容易的学习软件。支持深度自定义。 对于我本人而言,我在 anki 里面写了很多 LeetCode 题目和套路的 Card,然后 anki 会自动帮我安排复习时间,大大减少我的认知负担,提高了我的复习效率。大家可以在书后的附录中下载 anki 卡片。 目前已更新卡片一览(仅列举正面) 二分法解决问题的关键点是什么,相关问题有哪些? 如何用栈的特点来简化操作, 涉及到的题目有哪些? 双指针问题的思路以及相关题目有哪些? 滑动窗口问题的思路以及相关题目有哪些? 回溯法解题的思路以及相关题目有哪些? 数论解决问题的关键点是什么,相关问题有哪些? 位运算解决问题的关键点是什么,相关问题有哪些? 大家刷了很多题之后,就会发现来来回回,题目就那么几种类型,因此掌握已有题目类型是多么重要。那样 LeetCode 出题的老师,很多也是在原有的题目基础上做了适当扩展(比如 two-sum,two-sum2,three-sum, four-sum 等等)或者改造(使得不那么一下子看出问题的本质,比如猴子吃香蕉问题)。 其中算法,主要是以下几种: 12345基础技巧:分治、二分、贪心排序算法:快速排序、归并排序、计数排序搜索算法:回溯、递归、深度优先遍历,广度优先遍历,二叉搜索树等图论:最短路径、最小生成树动态规划:背包问题、最长子序列 数据结构,主要有如下几种: 123456数组与链表:单 / 双向链表栈与队列哈希表堆:最大堆 / 最小堆树与图:最近公共祖先、并查集字符串:前缀树(字典树) / 后缀树 做到了以上几点,我们还需要坚持。这个其实是最难的,不管做什么事情,坚持都是最重要也是最难的。 为了督促自己,同时帮助大家成长,我在群里举办《每日一题》活动,每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块, 感兴趣的可以到书后的附录部分进群交流。 1.4 复杂度分析想学算法,首先要学的第一件事就是如何判断一个算法的好坏。 好的程序有很多的评判标准,包括但不限于可读性,扩展性性能等。 这里我们来看其中一种 - 性能。 坏的程序可能性能也很好,但是好的程序通常性能都比较好。那么如何分析一个算法的性能好坏呢?这就是我们要讲的复杂度分析,所有的数据结构教程都会把这个放在前面来讲,不仅仅是因为他们是基础,更因为他们真的非常重要。学会了复杂度分析,你才能够对你的算法进行分析,从而帮助你写出复杂度更优的算法。 那么怎么样才能衡量一个算法代码的执行效率呢? 如下是一个从 1 加到 n 的一个算法,这个算法用了一层循环来完成,并且借助了一个变量 res 来完成。 12345def sum(n): res = 0 for i in range(1, n + 1): res += i return res 我们将这个方法从更微观的角度来进行分析,上述代码会执行 n 次循环体的内容,每一次执行都是常数时间,我们不妨假设执行的时间是 x。我们假设赋值语句res = 0和return res的时间分别为 y 和 z 那么执行的总时间我们约等于 n _ x + y + z, 我们粗略将 x,y 和 z 都看成一样的,我们得出总时间为 (n + 2) _ x 换句话说算法的时间和数据的规模成正比。 实际上,这更是一种叫做大 O 表示法的基本思想, 它是一种描述算法性能的记法,这种描述和编译系统、机器结构、处理器的快慢等因素无关。 这种描述的参数是 n,表示数据的规模。 这里的 O 表示量级(order),比如说“二分查找是$O(logN)$的”,也就是说它需要“通过 logn 量级的操作去查找一个规模为 n 的数据结构(通常是数组)”。这种渐进地估计对算法的理论分析和大致比较是非常有价值,可以很快地对算法进行一个大致地估算。例如,一个拥有较小常数项的 $O(N^2)$算法在规模 n 较小的情况下可能比一个高常数项的$O(N)$算法运行得更快。但是随着 n 足够大以后,具有较慢上升函数的算法必然工作得更快,因此在采用大 O 标记复杂度的时候,可以忽略系数。 我们还应该区分算法的最好情况,最坏情况和平均情况,但是这不在本书的讨论范畴,本书的所有复杂度均指的是平均复杂度。 那么如何分析一个算法的复杂度呢?下面我们介绍几种常见时间复杂度,几乎所有的算法的复杂度都是以下中的一种 我对时间复杂度进行了一个小的分类。 第一类是常数阶。 一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是 Ο(1)。 1234cnt = 1l = 0r = len(list) - 1# 不管这种代码有多少行,都是常数复杂度,即$O(1)$,因此系数是被忽略的。 第二类是 n,n^2,n^3 … 一个简单的方法是关注循环执行次数最多的那一段代码就好了,这段执行次数最多的代码执行次数的 n 的量级,就是整个算法的时间复杂度。即如果是一层 N 的循环,那么时间复杂度就是$O(N)$, 如果嵌套了两层 N 的循环,那么时间复杂度就是$O(N^2)$,依次类推。 123456789101112class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: n = len(nums) mapper = {} for i in range(n): if (target - nums[i] in mapper): return [mapper[target - nums[i]], i] else: mapper[nums[i]] = i return [] 如上代码,我们进行了一层的循环,那么时间复杂度就是$O(N^2)$ 第三类是对数阶。 logn nlogn 这同样是一种非常常见的复杂度,多见于二分查找和一些排序算法。 123456789101112131415161718def numRescueBoats(self, people: List[int], limit: int) -> int: res = 0 l = 0 r = len(people) - 1 people.sort() while l < r: total = people[l] + people[r] if total > limit: r -= 1 res += 1 else: r -= 1 l += 1 res += 1 if (l == r): return res + 1 return res 上面的代码是一个典型的二分查找,其时间复杂度是 logn 第四类是指数阶 2^n 指数的增长已经非常恐怖了,一个典型的例子是 fabnicca 数列的递归实现版本。 1234def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) 如果你把上述的计算过程看成树的一个节点,那么整个计算过程就像是一颗很大的树。这棵树有很多的重复计算,大致算下来的话,是 2^n。 第五类是对数阶 n! 我们知道 n 个不相同的数字的全排列有 n!个。 1234def factorrail(n): if n == 1: return 1 return n * factorrail(n - 1) 很明显上面的代码就是 n! 下面给出上面列举的几种常见的时间复杂度的趋势图对比,大家直观感受一下。 (各种复杂度的渐进趋势对比) 从算法可以分析出时间复杂度,相反题目的时间复杂度要求,我们甚至可以猜测出可能用到的算法,比如算法要求 logn,那么就有可能是二分法。 空间复杂度分析比时间复杂度分析要简单地多,常见的空间复杂度有$O(1)$、$O(N)$、$O(N^2)$、$O(logN)$、$O(logN)$、$O(N!)$这样的对数阶复杂度基本不会有,关于空间复杂度这里不做更多介绍了。 总结时间复杂度是算法的基石,学习它对于我们学习后面的章节有很大的帮助。 我们引入了大 O 表示法来衡量算法的好坏。接着通过若干实例了解了各种时间复杂度,其实对于复杂度,掌握上面提到的几种常见的就已经够应付绝大多数场合了。 通过上面的学习,相信你对评估一个算法的时间复杂度有了初步的了解。随着学习的深入,相信你会对复杂度分析有着更为深入的理解。 2. 数学之美LeetCode 中有很多数学问题,截止到本书出版,LeetCode 中有数学标签的题目一共是 159,在所有标签的分类中排名第 3。这些题目中有些是包着数学外衣的伪数学问题,还有一些是真正数学问题。这需要大家有着极强的分辨能力。不要被数学两个字吓住了,本章不会讲非常复杂的数学概念和公式,实际上你只需要一些高中数学知识即可。 除非是面试算法岗位,笔试和面试题才会涉及到一些比较复杂度的数学知识,比如微积分,线性代数,概率论,信息论等。 虽然有的题目可以用数学公式轻松解决,但是这并不意味你需要对数学有很深的造诣。举例来说,LeetCode 69.实现开方,就是一道可以使用纯数学方法 - 牛顿迭代法来解决的一道题,但是你完全可以使用二分法解决,尽管效率远远不及牛顿迭代法,实际上现在的计算器计算开方也不是二分法计算的。但是这通常是一个加分项,如果你可以通过别的方法解决,也未尝不可。 很多题目一眼看上去就是个数学问题,如果你尝试使用数学的角度没有什么思路或者解不出来的时候,可以考虑换最常规,最符合直觉的做法,当然做之前要估算一下数据范围和时间,不要写完才发现超时。 有些题目只是涉及到一些数学名词,并且会在题目中详细地进行解释。 比如关于质数性质,阶乘性质的题目,还有一些造轮子题目,比如实现 Pow 等。还有一些干脆定义一个数学概念,让你去做。比如开心数,回文数,丑数等。 我们这章主要讲解纯数学问题,需要用到一些数学的性质类的题目,这或许是大家更想要看的。 2.1 N-SUM 题目LeetCode 上有很多经典的系列问题,今天我们就来看一下 N-SUM 系列问题。 2.2 连续整数和这是一个非常经典,被各种收录的一个题目,这道题好在虽然简单,但是却可以从多个纬度进行解决,非常适合用来查考一个人的真实水平,一些比较大的公司也会用它来进行算法面试的第一道题。 2.3 最大数2.4 分数到小数2.5 最大整除子集2.6 质数排列 质数 全排列 2.8 快乐数 这类题目是给定一个定义(情景),让你实现算法找出满足特定条件的数字 3. 回文的艺术回文是很多教材中被提到的一个题目,通常是用来学习栈的一个练习题,LeetCode 中有关回文的题目也是蛮多的,单从数据结构上来看就有字符串,数字和链表。今天我们就结合几个 LeetCode 题目来攻克它。 3.1 回文字符串3.2 回文链表3.3 回文数字3.4 回文总数4. 游戏之乐我很喜欢玩游戏,实际上很多游戏背后都是有很多算法存在的,我们通过 LeetCode 上一些关于游戏的题目来一窥究竟吧,虽然这里的题目和实际游戏用到的算法难度差很多,但是这里的基本思想是一样的。 4.1 生命游戏4.2 报数4.3 数独游戏5. BFS & DFS这是 LeetCode 后期新增的一个板块,题目还比较少。 6. 二分法二分法真的是一个非常普遍的算法了,更严格的说其实是一种思想,如果把二改成 N 其实就是一种分治思想。LeetCode 关于二分法的题目实在太多了,我们挑选几个代表性的来感受一下,LeetCode 到底是如何考察我们二分法的。 6.1 你真的了解二分法么?6.2 一些显然的二分6.3 隐藏的二分法二进制和二分法? 744 吃香蕉 循环数组 数学开方 等等 6.4 寻找峰值7. 神奇的比特前菜: 如何将一个 IP 地址用一个字节存储,支持序列化和反序列化操作。 计算机是用过二进制来表示信息的,有时候我们从二进制思考问题,会发现一个全新的世界。 7.1 那些特立独行的数字7.2 桶中摸黑白球7.3 实现加法7.4 二进制 1 的个数7.5 悲惨的老鼠8. 设计题有时候我们面对的不是一个算法题,而是一个设计题目,这种题目比较开放,让你自己设计数据结构和算法。这比限定数据结构和算法更能考察一个人综合运用知识的能力,是一个经常被拿来进行面试的一类题目。 8.1 设计 LRU8.2 设计 LFU8.3 最小栈8.4 队列实现栈8.5 设计 Trie 树9. 双指针双指针的题目真的非常多,可以看出这个是一个重要重要的知识点。在实际使用过程中,我将双指针问题分为两种,一种是头尾双指针,一种是快慢双指针。 9.1 头尾指针9.1.1 盛水问题9.1.2 两数相加 29.2 快慢指针9.2.1 删除有序数组的重复元素9.2.2 链表中的快慢指针10. 查表与动态规划如果说递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。 这句话需要一定的时间来消化, 如果不理解,可以过一段时间再来看。 递归的解决问题非常符合人的直觉,代码写起来比较简单。但是我们通过分析(可以尝试画一个递归树),可以看出递归在缩小问题规模的同时可能会 重复计算。 279.perfect-squares 中 我通过递归的方式来解决这个问题,同时内部维护了一个缓存 来存储计算过的运算,那么我们可以减少很多运算。 这其实和动态规划有着异曲同工的地方。 10.1 爬楼梯10.2 聪明的盗贼六(七)个版本,带你一步步进化,走向极致 10.3 不同路径10.4 硬币找零10.5 最短编辑距离11. 滑动窗口你可能听过 TCP 的滑动窗口,这里要讲的滑动窗口其实思想是一样的,这里要讲的滑动窗口通常被用在处理连续数组或者字符的问题上。 最长连续不重复子串最短子数组之和滑动窗口最大值12. 博弈博弈,词语解释是局戏、围棋、赌博。 现代数学中有博弈论,亦名“对策论”、“赛局理论”,属应用数学的一个分支, 表示在多决策主体之间行为具有相互作用时,各主体根据所掌握信息及对自身能力的认知,做出有利于自己的决策的一种行为理论。 这类问题通常没那么直接和好下手,需要你进行一定的推演才能发现问题的本质。 12.1 alec12.2 Nim12.3 486. 预测赢家13. 股票系列LeetCode 上有很多经典的系列问题,今天我们就来看一下这个股票系列问题。 13.1 股票买卖的最佳时机 113.2 股票买卖的最佳时机 213.3 股票买卖的最佳时机 313.4 股票买卖的最佳时机 414. 分治法分治是一种非常重要的算法思想,而不是一个算法。和具体算法不同,算法思想在任何数据结构下都可以使用。 14.1 合并 K 个排序链表14.2 数组中的第 K 个最大元素14.3 搜索二维矩阵15. 贪心法贪心或许是最难的一种算法思想了。 15.1 跳跃游戏15.2 任务调度器16. 回溯这是一种非常暴力的搜索算法,优点是书写简单有固定模板,且适用范围很广。 16.1 求组合数 116.2 求组合数 216.3 求所有子集16.4 全排列16.5 海岛问题17. 一些有趣的题目这里让我们来看一下 LeetCode 上那些惊人的算法。 17.1 求众数17.2 手撕排序17.3 星期几17.4 量筒问题17.5 实现开方17.6 4 的次方18. 一些通用解题模板不仅是平时做工程项目,刷题的过程也非常讲究风格一致,如果有一些非常优秀的模板可以直接拿来用,一方便减少做题时间和出错的可能,另一方面做题风格一致有利于自己回顾。 如果你是在面试,相信一定也会为你加分不少。 18.1 二分法18.2 回溯法18.3 递归18.4 并查集 朋友圈 计算小岛数 2 19. 融会贯通这里我们要把本书降到的知识进行融会贯通,纵向上我们不满足于一种解法,我们尝试使用多种解法去解决。 横向上我们需要去总结哪些题目和这道题目类似。 这通常被用在第二遍刷 LeetCode 的过程中。 19.1 最大子序列和问题19.2 循环移位问题19.3 k 问题20. 解题技巧&面试技巧在水平知识一样的情况下,如果能够 LeetCode 上效率更好?如何面试的时候加分,这是本章我们要探讨的主要内容。 一定要看限制条件,很多时候限制条件起到了提示的作用,并且可以帮助你过滤错误答案 21. 参考","categories":[{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/categories/书/"},{"name":"算法","slug":"书/算法","permalink":"https://lucifer.ren/blog/categories/书/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"LeetCode","slug":"LeetCode","permalink":"https://lucifer.ren/blog/tags/LeetCode/"},{"name":"书","slug":"书","permalink":"https://lucifer.ren/blog/tags/书/"},{"name":"草稿","slug":"草稿","permalink":"https://lucifer.ren/blog/tags/草稿/"}]},{"title":"数据结构与算法在前端领域的应用 - 第四篇","slug":"algorthimn-fe-4","date":"2019-09-20T16:00:00.000Z","updated":"2023-01-05T12:24:49.906Z","comments":true,"path":"2019/09/21/algorthimn-fe-4/","link":"","permalink":"https://lucifer.ren/blog/2019/09/21/algorthimn-fe-4/","excerpt":"前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶) 这一次我们顺着前面的内容,讲一些经典的数据结构与算法,本期我们来讲一下时下比较火热的React fiber。 这部分内容相对比较硬核和难以消化,否则 React 团队也不会花费两年的时间来搞这一个东西。建议大家多读几遍。","text":"前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶) 这一次我们顺着前面的内容,讲一些经典的数据结构与算法,本期我们来讲一下时下比较火热的React fiber。 这部分内容相对比较硬核和难以消化,否则 React 团队也不会花费两年的时间来搞这一个东西。建议大家多读几遍。 fiber - 一个用于增量更新的数据结构前面我的文章提到过 fiber 是一种数据结构,并且是一种链式的数据结构。 fiber 是为了下一代调和引擎而引入的一种新的数据结构,而下一代调和引擎最核心的部分就是“增量渲染”。为了明白这个“增量渲染”,我们需要打一点小小的基础。 分片执行为了做到上面我提到的“增量渲染”,我们首先要能够停下来。之前 React 的更新 UI 的策略是自顶向下进行渲染,如果没有人工的干涉,React 实际上会更新到所有的子组件,这在大多数情况下没有问题。 然而随着项目越来越复杂,这种弊端就非常明显了。单纯看这一点,Vue 在这方面做的更好,Vue 提供了更加细粒度的更新组件的方式,甚至无需用户参与。 这是两者设计理念上的差异,不关乎好坏,只是适用场景不一样罢了。 值得一提的是,Vue 的这种细粒度监听和更新的方式,实际上是内存层面和计算层面的权衡。社区中一些新的优秀框架,也借鉴了 Vue 的这种模式,并且完成了进一步的进化,对不同的类型进行划分,并采取不同的监听更新策略,实际上是一种更加“智能“的取舍罢了。 言归正传,我们如何才能做到”增量更新“呢? 首先你要能够在进行到中途的时候停下来 你能够继续刚才的工作,换句话说可以重用之前的计算结果 实现这两点靠的正是我们今天的主角 fiber,稍后我们再讲。 比如之前 React 执行了一个 100ms 的更新过程,对于新的调和算法,会将这个过程划分为多个过程,当然每一份时间很可能是不同的。 由于总时间不会减少,我们设置还增加了调度(上面我提到的两条)的代码,因此单纯从总时间上,甚至是一种倒退。但是为什么用户会感觉到更快了呢?这就是下面我们要讲的调度器。 三大核心组件 - Scheduler, Reconciliation, Renderer事实上, React 核心的算法包含了三个核心部分,分别是Scheduler,, Reconciliation,Renderer。 scheduler 用于决定何时去执行。 前面提到了,整个更新过程要比之前的做法要长。总时间变长的情况下,用户感觉性能更好的原因在于scheduler。 对于用户而言,界面交互,动画,界面更新的优先级实际上是不一样的。 通过保证高优先级的事件的执行,比如用户输入,动画。 可以让用户有性能很好的感觉。 做到这一点实际上原理很简单,即使前面提到的 chunks,再加上我们给每一个任务分配一个优先级,从而保证 chunks 的执行顺序中高优先级的在前面。 浏览器实际上自己也会对一些事件区分优先级。 Reconciliation 决定哪部分需要更新,以及如何“相对最小化”完成更新过程。这部分算法主要上衣基于VDOM这种数据结构来完成的。 这部分的算法实际上就是一个“阉割版的最小编辑树算法”。 renderer 使用 Reconciliation 的计算结果,然后将这部分差异,最小化更新到视图。可以是 DOM,也可以是native, 理论上可以是任何一种渲染容器。 在 DOM 中,这部分的工作由 React-DOM 来完成。它会生成一些 DOM 操作的 API,从而去完成一些副作用,这里指的是更新 DOM。 fiber - 一个虚拟调用栈实际上,fiber 做的事情就是将之前 react 更新策略进行了重构。 之前的更新策略是从顶向下,通过调用栈的形式保存已经更新的信息。这本身没有问题, 但是这和我们刚才想要实现的效果是不匹配的,我们需要 chunks 的效果。而之前的策略是从顶到下一口气执行完,不断 push stack,然后 pop stack,直到 stack 为空。 fiber 正是模拟了调用栈,并且通过链表来重新组织,一方面使得我们可以实现 chunks 的功能。另一方面可以和 VDOM 进行很好的对应和映射。 v = f(d)这是我从 React 官方介绍 fiber 的一个地方抄来的公式。 它想表达的是 react 是一个视图管理框架,并且是数据驱动的,唯一的数据会驱动产生唯一的视图。 我们可以把每一个组件都看成一个 view,然而我们的工作就是计算所有的组件的最新的 view。 那么 fiber 是如何完成“增量更新”呢? 秘诀就是它相当于“重新实现了浏览器调用栈”。 我们来看一下,fiber 是如何实现传统调用栈的功能的。 fiber 和 传统调用栈的区别传统的调用栈,我们实际上将生成 view 的工作放到栈里面执行,浏览器的栈有一个特点就是“你给它一个任务,它必须一口气执行完”。 而 fiber 由于是自己设计的,因此可以没有这个限制。 具体来说,两者的对应关系如下: 1234567传统调用栈 fiber 子函数 component type 函数嵌套 child 参数 props 返回地址 parent 返回值 DOM elements 用图来表示的话,大概是这样: 其中具体的算法,我预计会在我的从零开始开发一个 React 中更新。 总结本篇文章介绍了fiber,fiber其实是一种用于增量更新的数据结构。是为了下一代调和引擎而引入的一种新的数据结构,而下一代调和引擎最核心的部分就是“增量渲染”。 我们介绍了几个基本概念和组件,包括分片执行, react三大核心组件 - Scheduler, Reconciliation, Renderer。 最后我们说了“fiber实际上就是一个虚拟调用栈”,并结合传统调用栈的特点和弊端,讲解了fiber是如何组织,从而改进了传统调用栈带来的问题的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"数据结构与算法在前端领域的应用 - 第三篇","slug":"algorthimn-fe-3","date":"2019-09-19T16:00:00.000Z","updated":"2023-01-05T12:24:49.781Z","comments":true,"path":"2019/09/20/algorthimn-fe-3/","link":"","permalink":"https://lucifer.ren/blog/2019/09/20/algorthimn-fe-3/","excerpt":"这是本系列文章的第三篇,这里我将带你从新的视角来看当前的前端应用,虽然这其中涉及到的道理很简单,但是这部分知识很少被人看到,更不要说推广和应用了。 这里新的视角指的是我们从进程和线程的角度来思考我们前端应用的运行,从而从更高的层次去审视和优化我们的应用,甚至整个前端生态。 希望你看完之后从思维上也好,工作应用中也好能够有所收获。 前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶)","text":"这是本系列文章的第三篇,这里我将带你从新的视角来看当前的前端应用,虽然这其中涉及到的道理很简单,但是这部分知识很少被人看到,更不要说推广和应用了。 这里新的视角指的是我们从进程和线程的角度来思考我们前端应用的运行,从而从更高的层次去审视和优化我们的应用,甚至整个前端生态。 希望你看完之后从思维上也好,工作应用中也好能够有所收获。 前一段时间我分享了几篇关于《数据结构与算法在前端领域的应用》的文章。 文章链接: 数据结构和算法在前端领域的应用(前菜) 数据结构和算法在前端领域的应用(进阶) 关于我我是一个对技术充满兴趣的程序员, 擅长前端工程化,前端性能优化,前端标准化等。 做过.net, 搞过 Java,现在是一名前端工程师。 除了我的本职工作外,我会在开源社区进行一些输出和分享,GitHub 共计获得 1.5W star。比较受欢迎的项目有leetcode 题解 , 宇宙最强的前端面试指南 和我的第一本小书 浏览器的进程模型我们首先来看下浏览器的进程模型,我们以 chrome 为例。 Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。 (图来自 https://zhuanlan.zhihu.com/p/47407398) 这也是为什么 chrome 明明只打开了一个 tab,却出现了 4 个进程的原因。 这部分不是我们本节的主要内容,大家了解这么多就够了,接下来我们看下今天的主角 - 渲染进程。 浏览器的渲染进程渲染进程几乎负责 Tab 内的所有事情,渲染进程的核心目的在于转换 HTML CSS JS 为用户可交互的 web 页面。 渲染进程由以下四个线程组成:主线程 Main thread , 工作线程 Worker thread,光栅线程 Raster thread和排版线程 Compositor thread。 我们的今天的主角是主线程 Main thread 和 工作线程 Worker thread。 主线程 Main thread主线程负责: 构建 DOM 和网络进程(上文提到的)通信获取资源 对资源进行解析 JS 代码的执行 样式和布局的计算 可以看出主线程非常繁忙,需要做很多事情。 主线程很容易成为应用的性能瓶颈。 当然除了主线程, 我们的其他进程和线程也可能成为我们的性能瓶颈,比如网络进程,解决网络进程瓶颈的方法有很多,可以使用浏览器本身缓存,也可以使用 ServiceWorker,还可以通过资源本身的优化等。这个不是我们本篇文章的讨论重点,这里只是让你有一个新的视角而已,因此不赘述。 工作线程 Worker thread工作线程能够分担主线程的计算压力,进而主线程可以获得更多的空闲时间,从而更快地响应用户行为。 工作线程主要有 Web Woker 和 Service Worker 两种。 Web Worker以下摘自MDN Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,他们可以使用 XMLHttpRequest 执行 I/O(尽管 responseXML 和 channel 属性总是为空)。一旦创建, 一个 worker 可以将消息发送到创建它的 JavaScript 代码, Service Worker以下摘自MDN Service workers 本质上充当 Web 应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步 API。 重新思考我们的前端应用工作线程尤其是 Web Worker 的出现一部分原因就是为了分担主线程的压力。 整个过程就像主线程发布命令,然后工作线程执行,执行完毕将执行结果通过消息的形式传递给主线程。 我们以包工头包工程,然后将工作交给各个单位去做的角度来看的话,大概是这样的: 实际上工作工作进程,尤其是 WebWorker 已经出现很长时间了。但是很多时候我们并没有充分使用,甚至连使用都没使用。 下面以 Web Worker 为例, 我们来深度挖掘一下工作线程的潜力。 前面的文章,我们谈了很多前端领域的算法,有框架层面的也有应用层面的。 前面提到了 React 的调和算法,这部分代码耗时其实还是蛮大的,React16 重构了整个调和算法,但是总体的计算成本还是没有减少,甚至是增加的。 关于调和算法可以参考我的另外一篇文章前端领域的数据结构与算法解读 - fiber 我们有没有可能将这部分内容抽离出主线程,交给工作进程,就像上面的图展示的那样呢?我觉得可以, 另外我前面系列文章提到的所有东西,都可以放到工作线程中执行。比如状态机,时光机,自动完成,差异比对算法等等。 如果将这些抽离出我们主线程的话,我们的应用大概会是这样的: 这样做主线程只负责 UI 展示,以及事件分发处理等工作,这样就大大减轻了主线程的负担,我们就可以更快速地响应用户了。然后在计算结果完成之后,我们只需要通知主线程,主线程做出响应即可。可以看出,在项目复杂到一定程度,这种优化带来的效果是非常大的。 我们来开一下脑洞, 假如流行的前端框架比如 React 内置了这种线程分离的功能,即将调和算法交给 WebWorker 来处理,会给前端带来怎么样的变化? 假如我们可以涉及一个算法,智能地根据当前系统的硬件条件和网络状态,自动判断应该将哪部分交给工作线程,哪部分代码交给主线程,会是怎么样的场景? 这其实就是传说中的启发式算法, 大家有兴趣可以研究一下 挑战上述描述的场景非常美好,但是同样地也会有一些挑战。 第一个挑战就是操作繁琐,比如 webworker 只支持单独文件引入,再比如不支持函数序列化,以及反复序列化带来的性能问题, 还有和 webworker 通信是异步的等等。 但是这些问题都有很成熟的解决方案,比如对于操作比较繁琐这个问题我们就可以通过使用一些封装好 web worker 操作的库。comlink 就是一个非常不错的 web worker 的封装工具库。 对于不支持单文件引入,我们其实可以用Blob, createObjectURL的方式模拟,当然社区中其实也有了成熟的解决方案,如果你使用 webpack 构建的话,有一个worker-loader可以直接用。 对于函数序列化这个问题,我们无法传递函数给工作线程,其实上面提到的Comlink, 就很好地解决了这个问题,即使用 Comlink 提供的proxy,你可以将一个代理传递到工作线程。 对于反复序列化带来的性能问题,我们其实可以使用一种叫对象转移(Transferable Objects)的技术,幸运的是这个特性的浏览器兼容性也不错。 对于异步的问题,我们可以采取一定的取舍。 即我们本地每次保存一份最近一份的结果拷贝,我们只需要每次返回这个拷贝,然后在 webworker 计算结果返回的时候更新拷贝即可。 总结这篇文章的主要目的是让大家以新的视角来思考当前的前端应用,我们站在进程和线程的角度来看现在的前端应用,或许会有更多的不一样的理解和思考。 本文先是讲了浏览器的进程模型,然后讲了浏览器的渲染进程中的线程模型。 我们知道了渲染进程主要有四个线程组成,分别是主线程 Main thread , 工作线程 Worker thread,光栅线程 Raster thread和排版线程 Compositor thread。 然后详细介绍了主线程和工作线程,并以 webworker 为例,讲述了如何利用工作线程为我们的主线程分担负担。为了消化这部分知识,建议你自己动手实践一下。 虽然我们的愿望很好,但是这其中在应用的过程之中还是有一些坑的,我这里列觉了一些常见的坑,并给出了解决方案。 我相信工作线程的潜力还没有被充分发挥出来,希望可以看到前端应用真正的挖掘各个进程和线程潜力的时候吧,这不但需要前端工程师的努力,也需要浏览器的配合支持,甚至需要标准化组织去推送一些东西。 关注我最近我重新整理了下自己的公众号,并且我还给他换了一个名字《脑洞前端》,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解前端是我的目标。 之后我的文章同步到微信公众号 脑洞前端 ,您可以关注获取最新的文章,或者和我进行交流。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"数据结构与算法在前端领域的应用 - 第二篇","slug":"algorthimn-fe-2","date":"2019-09-18T16:00:00.000Z","updated":"2023-01-05T12:24:49.738Z","comments":true,"path":"2019/09/19/algorthimn-fe-2/","link":"","permalink":"https://lucifer.ren/blog/2019/09/19/algorthimn-fe-2/","excerpt":"这是一个我即将做的一个《数据结构与算法在前端领域的应用》主题演讲的一个主菜。如果你对这部分内容比较生疏,可以看我的数据结构和算法在前端领域的应用(前菜) 这里我会深入帮助大家如何根据业务抽离出纯粹的模型,从而转化为算法问题,","text":"这是一个我即将做的一个《数据结构与算法在前端领域的应用》主题演讲的一个主菜。如果你对这部分内容比较生疏,可以看我的数据结构和算法在前端领域的应用(前菜) 这里我会深入帮助大家如何根据业务抽离出纯粹的模型,从而转化为算法问题, 以上帝角度来看前端让我们以更高的层次来看一下,从大的范围上前端领域都在做什么? 从业务上来说,我们会去做各个端的开发、网关、接口、工程化。从技术上说,则是基于 WEB、Node 的通用技术,以及各平台(最常见的就是安卓和 IOS)的专有技术。 在这里我以自己的标准总结了以下三点: 架构和平台 其实平台建设也是架构中的一环,之所以列出来单独讲是因为这块内容相对比较大。每个公司,部门,项目都有自己的架构设计和规范。它们环环相套组成了整个公司的架构体系。 很多公司在做工具链,在做跨端方案,在做底层融合等,这些都属于这个范畴。比如最近比较火的 Serverless 也是属于这个范畴。 规范和标准化 前端行业规范目前来看的话就两个,一个是 ECMA 的规范,一个是 W3C 的规范。前端行业规范是非常重要的,不然前端会非常混乱,想一下前端刚刚诞生出来的时候就知道了。 公司内部也会有一些规范,但是很难上升到标准层次。 目前国内没有一个行业认可的标准化组织,这算是一个遗憾吧。 好消息是国人在标准化组织的参与感越来越强,做了更多的事情。 其实这部分我们的感知是比较弱的,一个原因就是我们一直在努力对接行业的标准,很少去自己创造一些标准。原因有几点,一方面自己做标准,维护更新标准很难,另一方面自己做标准需要学习成本和转换成本。 但是这并不意味这做公司标准或者行业领域规范就没有用,相反非常有用。我之前做过一个《标准化能给我们带来什么》的分享,详细介绍了标准化对于我们的重要性。 生态体系 其实前端的工作就是人机交互,这其中涉及的东西很多,相关领域非常广泛。 比如智能手表、智能 TV、智能眼镜、头戴 AR,VR 等新的交互模式我们如何去融入现有开发体系中 ?人工智能在前端开发可以发挥怎么样的作用 ? 这些其实很多公司已经在尝试,并且取得了非常不错的效果。 比如 IDE 是开发过程非常重要的工具,我们是否可以去做标准化的 IDE,甚至放到云端。 无处不在的算法上面我们从多个方面重新审视了一下前端,除了人工智能部分,其他部分根本没有提到算法。是不是算法在前端领域应用很少呢? 不是的。 一方面就像上一节介绍的,我们日常开发中使用的很多东西都是经过数据结构和算法的精心封装,比如 DOM 和 VDOM,以及 JSON。 JSON的序列化和反序列化是我们无时无刻使用的方法,比如我们需要和后端进行数据交互,需要和其他线程(比如webworker)进行数据交互都要经过序列化和反序列化,如何减少数据传输,如何提高序列化和反序列化的效率,如何在两者之间寻求一种平衡都是我们需要研究的。 JSON 也是一种树结构 甚至还有很多框架以数据结构直接命名,比如 GraphQL,就是用图这种数据结构来命名,从而体现其强大的关联查询能力。 比如 tensorflow 以张量(tensor)来加深大家对上面两点的印象命名, TensorFlow™ 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。 上面提到的各个环节都或多或少会用到算法。首先网络部分就涉及到很多算法,比如有限状态机,滑动窗口,各种压缩算法,保障我们信息不泄漏的各种加密算法等等,简直不要太多。虽然这些网络部分很多都是现成的,但是也不排除有一些需要我们自己根据当前实际场景自己去搭建一套的可能。这在大公司之中是非常常见的。 我们再来看下执行我们代码的引擎,以 V8 为例,其本身涉及的算法不算在内。但是当我们基于 V8 去做一些事情,我们就需要了解一些编译相关的原理。这里我举个例子,下图是支付宝的小程序架构。 如果我们不懂一些算法的话,是很难像支付宝一样结合自己的业务去做一些突破的。 (图片来自 https://www.infoq.cn/article/ullETz7q_Ue4dUptKgKC) 另外一些高层的架构中也会有很多算法方面的东西,比如我需要在前端做增量更新的功能。增量更新在APP中早已不是新鲜的东西了,但是真正做JS等静态资源的实时增量更新还比较少,这里面会涉及非常复杂的交互和算法。 上面提到的更多的是高层面上,事实上即使是业务层面也有很多值得挖掘的算法模型。我们需要从复杂的业务中提炼出算法模型,才能得到实际应用。可惜的是很多时候我们缺乏这种抽象能力和意志。 除了上一节我讲述的常见场景之外,我还会在下一节介绍几个实际业务场景,从而加深大家的理解。希望大家看了之后,能够在自己的实际业务中有所帮助。 性能和优雅,我全都要从表象上看,使用合适的数据结构和算法有两方面的好处。 第一个是性能,这个比较好理解一点,我们追求更好的时间复杂度和空间复杂度,并且我们需要不断地在两者之间做合理的取舍。 第二点是优雅,使用合适的数据结构和算法。能让我们处理问题更加简洁优雅。 下面我会举几个我在实际业务场景中的例子,来加深大家对上面两点的印象。 权限系统假如你现在开发一款类似石墨文档的多人在线协作编辑文档系统。 这里面有一个小功能是权限系统。 用户可以在我们的系统中创建文件夹和文件,并且管理角色,不同的角色可以分配不同的文件权限。 比如查看,下载,编辑,审批等。 我们既可以给文件夹分配权限,又可以给文件分配权限,如果对应文件该角色没有权限,我们需要递归往上搜索,看有没有相应权限,如果有,则这个角色有文件的该操作权限。 如图,fileA 的权限就需要从 fileA 开始看有没有对应权限,如果有,则返回有权限。如果没有,则查找 app 文件夹的权限,重复这个过程,直到根节点。 如果你是这个系统的前端负责人,你会如何设计这个系统呢? 其实做这个功能的方案有很多,我这里参考了 linux 的设计。我们使用一个二进制来标示一个权限有还是没有。 这样的话,一方面我们只需要 4 个 bit 就可以存储权限信息,存储已经是极限了。另一方面我们通过位运算即可算出有没有权限,二进制运算在计算性能上也是极限了。 另外代码写起来,也会非常简洁,感兴趣的可以自己试试。 扩展: 假如文件权限不是只有两种可能,比如有三个取值怎么办? 状态机什么是状态机状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。 我们以现实中广泛使用的有限状态机(以下简称 FSM)为例进行讲解 FSM 应用非常广泛, 比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。 其中正则中使用的是一种特殊的 FSM, 叫 DFA(Deterministic Finite Automaton), 通过分裂树形式来运行。 为什么要使用状态机第一个原因,也是大家感触最深的一个原因就是通过状态机去控制系统内部的状态以及状态流转,逻辑会比较清晰,尤其在逻辑比较复杂的时候,这种作用越发明显。 第二个原因是通过状态机,我们可以实现数据以及系统的可视化。刚才我提到了正则表达式用到了状态机,那么正则是否可以可视化呢? 答案是肯定的,这里我介绍一个可视化正则表达式的一个网站。 实际业务中如果使用状态机来设计系统也可以进行可视化。类似这样子: (图来自 https://statecharts.github.io/xstate-viz/) 可以看出,逻辑流转非常清晰,我们甚至可以基于此进行调试。当然,将它作为文档的一部分也是极好的,关于状态机的实际意义还有很多,我们接下来举几个例子说明。 状态机的实际应用场景匹配三的倍数实现一个功能,判断一个数字是否是三的倍数。 数字可以非常大,以至于超过 Number 的表示范围,因此我们需要用 string 来存储。 一个简单直观的做法是直接将每一位都加起来,然后看加起来的数字是否是三的倍数。但是如果数字大到一定程度,导致加起来的数字也超过了 Number 的表示范围呢? 一个方法是使用状态机来解决。 我们发现一个数字除以 3 的余数一共有三种状态,即 0,1,2。 基于此我们可以构建一个 FSM。0,1,2 之间的状态流转也不难得出。 举个例子,假设当前我们是余数为 0 的状态,这时候再来一个字符。 如果这个字符是 0,3 或者 9,那么我们的余数还是 0 如果这个字符是 1,4 或者 7,那么我们的余数是 1 如果这个字符是 2,5 或者 8,那么我们的余数还是 2 用图大概是这个样子: 如果用代码大概是这样的: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374function createFSM() { return { initial: 0, states: { 0: { on: { read(ch) { return { 0: 0, 3: 0, 9: 0, 1: 1, 4: 1, 7: 1, 2: 2, 5: 2, 8: 2 }[ch]; } } }, 1: { on: { read(ch) { return { 0: 1, 3: 1, 9: 1, 1: 2, 4: 2, 7: 2, 2: 0, 5: 0, 8: 0 }[ch]; } } }, 2: { on: { read(ch) { return { 0: 2, 3: 2, 9: 2, 1: 0, 4: 0, 7: 0, 2: 1, 5: 1, 8: 1 }[ch]; } } } } };}const fsm = createFSM();const str = \"281902812894839483047309573843389230298329038293829329\";let cur = fsm.initial;for (let i = 0; i < str.length; i++) { if (![\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"].includes(str[i])) { throw new Error(\"非法数字\"); } cur = fsm.states[cur].on.read(str[i]);}if (cur === 0) { console.log(\"可以被3整除\");} else { console.log(\"不可以被3整除\");} 其实代码还可以简化,读者可以下去尝试一下。 可以看出,我们这种方式逻辑清晰,且内存占用很少,不会出现溢出的情况。 正则是基于自动机实现的,那么使用正则匹配会是怎么样的呢?大家可以自己试一下。 答题活动经过上面的热身,我们来一个真实的项目来练练手。 有这样一个业务场景,我们需要设计一款答题活动,让用户过来进行答题,我们预先设置 N 道题目。 规则如下: 初始状态用户会进入欢迎页面 答对之后可以直接进入下一个题目 答错了可以使用复活卡重新答,也可以使用过关卡,直接进入下一题 用户可以通过其他途径获取复活卡和过关卡 答对全部 N 道题之后用户过关,否则失败 不管是过关还是失败都展示结果页面,只不过展示不同的文字和图片 这其实是一个简化版本的真实项目。 如果要你设计这样的一个系统,你会如何设计? 相信你肯定能想出很多种方法来完成这样的需求,接下来我会用 FSM 来实现。 我们很容易画出整理的流程图: 对于答题部分则稍微有一点麻烦,但是如果你用状态机的思维去思考就很容易,我们不难画出这样的图: JS 中有很多 FSM 的框架, 大家都可以直接拿过来使用。 笔者之前所在的项目也用到了这样的技术,但是笔者是自己手写的简化版本 FSM,基本思想是一致的。 其他事实上,还有很多例子可以举。 假设我们后端服务器是一主一备,我们将所有的数据都同时存储在两个服务器上。假如某一天,有一份数据丢失了,我们如何快速找到有问题的服务器。 这其实可以抽象成【Signle Number问题】。 因此很多时候,不是缺乏应用算法的场景,而是缺乏这种将现实业务进行抽象为纯算法问题的能力。我们会被各种细枝末节的问题遮蔽双眼,无法洞察隐藏在背后的深层次的规律。 编程最难是抽象能力,前几年我写了一篇文章《为什么我们的代码难以维护》,其中一个非常重要的原因就是缺乏抽象。 从现在开始,让我们来锻炼抽象能力吧。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"数据结构与算法在前端领域的应用 - 第一篇","slug":"algorthimn-fe-1","date":"2019-09-17T16:00:00.000Z","updated":"2023-01-05T12:24:49.990Z","comments":true,"path":"2019/09/18/algorthimn-fe-1/","link":"","permalink":"https://lucifer.ren/blog/2019/09/18/algorthimn-fe-1/","excerpt":"这是一个我在公司内部做的一个《数据结构与算法在前端领域的应用》主题演讲的一个前菜。希望通过这个分享让大家认识到其实前端领域也有很多算法的,从而加深前端同学对算法的认识。","text":"这是一个我在公司内部做的一个《数据结构与算法在前端领域的应用》主题演讲的一个前菜。希望通过这个分享让大家认识到其实前端领域也有很多算法的,从而加深前端同学对算法的认识。 为什么大家觉得算法没用在我的职业生涯中,碰到很多非算法岗的研发同学吐槽“算法在实际业务中没什么用”,甚至在面试官也问过我这个问题。我们姑且不来判断这句话正确与否,我们先来看下为什么大家会有这样的想法。 我发现很多人喜欢用冰山这个图来表示这种只看到总体的一小部分的情况。我也来借用一下这个创意。 根据我的经验,我们写的业务代码通常不到总包体的 5%, 下面是我以前做过的一个实际项目的代码分布。 12$ du -sh node_modules # 429M$ du -sh src # 7.7M 大家可以拿自己的实际项目看一下,看是不是这样的。 其实不难看出业务代码在整个应用的比例是很小的,软件工程有一个至理名言,“软件开发的 90%的工作是由 10%的人完成的”,这句话很对,那么剩下的 10 的工作却由剩下的 90%来完成。 因此我们感觉算法没用,是因为我们没用深入剩下的“90%”很多场景我们接触不到,并且没有思考过,就很容易“井底之蛙”,到头来就变成“只会用别人造好的轮子组装代码”的“前端打字员”。 那剩下的 90% 究竟有哪些涉及到算法呢?是否可以举例说明呢?那接下来让我们来继续看一下。 前端中算法的应用场景都有哪些说实话,这部分内容实在太多啦,为了让大家有一个直观的感受,我画了一个图。 图中黄色的代表我自己实现过。 这些都是前端开发过程的一些东西, 他们多多少少涉及到了数据结构和算法的知识 下面我们来简单分析一下。 VDOM事实上 VDOM 就是一种数据结构,但是它并不是我们在《数据结构与算法》课程中学习到的一些现成的数据结构。 逻辑上 VDOM 就是用来抽象 DOM 的,底层上 VDOM 普遍实现是基于 hash table 这种数据结构的。 一个典型的 VDOM 可以是: 123456789101112{ type: 'div', props: { name: 'lucifer' }, children: [{ type: 'span', props: {}, children: [] }]} 不难看出,VDOM 是一种递归的数据结构,因此使用递归的方式来处理是非常直观和容易的。 我上面提到了 VDOM 是 DOM 的抽象(ye, a new level of abstraction)。根据 VDOM 我们可以创建一个对应的真实 DOM。 如果我们只是做了这一层抽象的话,我们相当于引入了一种编程模式,即从面向 DOM 编程,切换到面向 VDOM 编程,而现在 VDOM 又是由数据驱动的,因此我们的编程模式切换到了“数据驱动”。 事实上,VDOM 部分还有一个 VDOM diff 算法,相信大家都听说过。关于DOM diff 的算法,以及它是如何取舍和创新的,我之前在一个地方回答过,这里给一个链接地址:https://juejin.im/post/5d3d8cf15188256ac355a9f0 HooksHooks 是 React16 添加的一个新功能, 它要解决的问题是状态逻辑复用。 Hooks 逻辑上解决了纯函数无法持久化状态的“问题”,从而拓宽了纯函数组件的适用范围。 底层上 Hooks 使用数据来实现状态的对应关系,关于这部分可以参考我的[第一期]实现一个简化版的 React Hook - useState FiberFiber 也是 React16 添加的一个新功能。 事实上 Fiber 类似 VDOM,也是一个数据结构,而且同样也是一个递归的数据结构。 为了解决 React 之前一次全量更新的”问题”, React 引入了 fiber 这种数据结构,并重写了整个调和算法,并且划分了多个阶段。 关于这部分内容,我只推荐一篇文章,Inside Fiber: in-depth overview of the new reconciliation algorithm in React 其实之前我的从零开始实现 React 系列教程 也欠了 fiber 😄, 等我开心的时候补充上去哈。 Git我之前写过一个 Git 终端(代码被我 rm -rf 啦)。 这过程还是用到了很多数据结构和算法的,我也学到了很多东西, 甚至 React16 新的调和算法也有 Git 思想。 很直观的,Git 在推送本地仓库到远程的时候会进行压缩,其实这里就用到了最小编辑距离算法。Leetcode 有一个题目72. Edit Distance,官方难度hard, Git 的算法要是这个算法的复杂版本。 另外 Git 其实很多存储文件,数据的时候也用到了特殊的数据结构,我在这里进行了详细的描述,感兴趣的可以去看看。 WebpackWebpack 是众所周知的一个前端构建工具,我们可以用它做很多事情。至今在前端构建领域还是龙头老大 🐲 的位置。 Webpack 中最核心的 tapable 是什么,是如何配合插件系统的? webpack 是如何对资源进行抽象的,webpack 是如何对依赖进行处理的?更复杂一点 Tree Shaking 如何做,分包怎么做,加速打包怎么做。 其实 webpack 的执行过程是基于事件驱动的,tapable 提供了一系列钩子,让 plugin 深入到这些过程之中去。听起来有点像事件总线,其实其中的设计思想和算法细节要复杂和精妙很多。 关于这部分细节,我在我的从零实现一个 Webpack 之后会加入更多特性,比如 tapable ASTAST(抽象语法树)是前端 编译(严格意义上是转义)的理论基础,你如果想深入前端编译,就一定不能不会这个知识点。 和 AST 相似的,其实还有 CST,prettier 等格式化工具会用到, 有兴趣可以搜一下。 这个网站 可以让你对 AST 有一个直观的认识。 AST 厉害就厉害在它本身不涉及到任何语法,因此你只要编写相应的转义规则,就可以将任何语法转义到任何语法。这就是babel, PostCSS, prettier, typescript 等的原理,除此之外,还有很多应用场景,比如编辑器。 之前自己写过一个小型的生成 AST 的程序,源代码忘记放哪了。😅 Browser History像浏览器中的历史页面,移动端 webview 的 view stack, 都用到了栈这种数据结构。 剩下的我就不一一说了。其实都是有很多数据结构和算法的影子的。 OK,说了那么多。 这些都是“大牛”们做的事情,好像和我日常开发没关系啊。我只要用他们做好的东西,调用一下,一样可以完成我的日常工作啊。让我们带着这个问题继续往下看。 算法在日常开发中的应用都有哪些123大神: “你可以先这样,再这样,然后就会抽象为纯算法问题了。”我: “哇,好厉害。” 其实就是你没有掌握,或者“再思考”,以至于不能融汇贯通。 比如你可以用 vue 组件写一个递归,实现一些递归的功能,也是可以的,但是大多数人都想不到。 接下来,我会举几个例子来说明“算法在日常开发中的应用”。注意,以下所有的例子均来自我的实际业务需求。 第一个例子 - 撤销与重做业务描述某一天,可(gai)爱(si)的产品提了一个需求,”我们的系统需要支持用户撤销和重做最近十次的操作。“ 让我们来回忆一下纯函数。 纯函数有一个特性是给定输入,输出总是一样的。 关于函数式编程可以参考我之前的几篇文章和一个分享 我们对问题进行一些简化,假设我们的应用是纯粹的数据驱动,也就是说满足纯的特性。 我们继续引入第二个知识点 - reducer. reducer 是一个纯函数,函数签名为(store1, action1) => store2。即给定 state 和 action,一定会返回确定的新的 state。 本质上 reducer 是 reduce 的空间版本。 假设我们的应用初始 state 为 state1, 我们按照时间先后顺序分别发送了三个 action,action1, action2, action3。 我们用一个图来表示就是这样的: 运用简单的数据知识,我们不难推导出如下关系: 如果对这部分知识点还比较迷茫,可以看下我之前的一篇文章,从零实现一个 Redux 解决方案基础知识铺垫完了,我们来看一下怎么解决这个问题。 第一种方案,我们可以将每次的store,即store1, store2, store3都存起来。比如我想回退到第二步,我们只需要将store2取出来,覆盖当前store,然后重新渲染即可。这种方案很直观,可以满足我们的业务需求。但是缺点也很明显,store在这里被存储了很多。 每次发送一个action都会有一个新的store被存起来。当我们应用比较大的时候,或者用户触发了很多action的时候,会占据大量内存。实际场景中性能上我们很难接受。 第二种方案,有了上面的铺垫,我们发现, 事实上我们没必要存储所有的store。因为store可以被计算出来。因此我们只要存储action即可。比如我们要回退到第二步,我们拿出来store1,然后和action运算一次,得到store2,然后将store2覆盖到当前的store即可。 这种做法,只需要存储一个store1, 以及若干个action。 action相对于store来说要小很多。这是这种做法相比与上一种的优势。同时由于每次都需要从头(store1)开始计算,因此是一种典型的“时间换空间”的优化手段。 实际上这种做法,我们可以进行小小的优化。比如我们设置多个snapshot,然后我们就不必每次从头开始计算,而是算出最近的一个snapshot,然后计算即可。 无疑这种做法可以减少很多计算量,但是会增加空间占用。这是典型的“空间换时间”, 如果根据实际业务进行取舍是关键。 第三种方案,我们可以用树来表示我们的store。每次修改store,我们不是将整个store销毁然后创建一个新的,而是重用可以重用的部分。 如图我要修改 store.user.age。我们只需要将root和user的引用进行修改,同时替换age节点即可。 如果大家对immutable研究比较深的话应该能发现,这其实就是immutable的原理 第二个例子 - 巨型Mapper的优化业务描述由于业务需要,我们需要在前端缓存一些HTTP请求。我们设计了如下的数据结构,其中key表示资源的URL,value会上次服务端的返回值。 现在我们的项目中已经有上千个接口,当接口多起来之后,缓存占用会比较大,我们如何对此进行优化? 注: 我们的key中的前缀是有规律的,即有很多重复的数据在。 返回值也有可能是有很多重复的。 这是一个典型的数据压缩算法。数据压缩算法有很多,我这里就不介绍了,大家可以自行了解一下。 对数据压缩算法感兴趣的,可以看下我之前写的游程编码和哈夫曼编码 第三个例子 - 实现自动联想功能业务描述现在很多输入框都带了自动联想的功能, 很多组件库也实现了自动填充组件。 现在需要你完成这个功能,你会怎么做? 我们可以用前缀树,很高效的完成这个工作。 对这部分原理感兴趣的可以看下我的这个题解 第四个例子 - 相似度检测业务描述由于业务需要,我们需要对字符串进行相似度检测。对于相似度超过一定阀值的数据,我们认为它是同一个数据。 关于相似度检测,我们其实可以借助“最小编辑距离”算法。对于两个字符串a和b,如果a和b的编辑距离越小,我们认为越相似,反之越不相似。 特殊情况,如果编辑距离为0表示是相同的字符串,相似度为100%。 我们可以加入自己的计算因子,将相似度离散在0 - 100%之间。 这部分的内容,我在介绍Git的时候介绍过了,这里不再重复。 其实我们可以进一步扩展一下,比如对于一些无意义的词不计入统计范围,我们可以怎么做? 算法不好会怎么样这恐怕是很多人最关心的问题。 我虽然知道了算法有用,但是我不会怎么办?会有什么样的影响呢? 这就回到了我们开头的问题,“为什么很多人觉得算法没用”。事实上,我们日常开发中真正用到算法的场景很少,大部分都被别人封装好了。即使真正需要用到一些算法,我们也可以通过一些“低劣”的手段完成,在很多对性能和质量要求不高的业务场景都是没有问题的。 这就是为什么“前端同学更加觉得算法没用”的原因之一。 那既然这么说,是不是真的算法就没用呢? 或者说算法不好也不会怎么样了么?当然不是, 如果算法不好,会很难创新和突破。 想想如今前端框架,工具的演进,哪一个不是建立在无数的算法之上。 将视角聚焦到我们当下的业务上,如果算法不好,我们也同样很难让业务不断精进,不断赋能业务。 React框架就是一个非常典型的例子,它的出现改变了传统的编程模式。Redux的作者,React团队现任领导者 dan 最近发表了一篇个人博客 Algebraic Effects for the Rest of Us这里面也有很多算法相关的东西,大家有兴趣的可以读读看。 另外我最近在做的一个 stack-visualizer,一个用于跟踪浏览器堆栈信息,以便更好地调试地工具, 这个也是和算法有关系的。","categories":[{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/categories/前端/"},{"name":"算法","slug":"前端/算法","permalink":"https://lucifer.ren/blog/categories/前端/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"},{"name":"前端","slug":"前端","permalink":"https://lucifer.ren/blog/tags/前端/"}]},{"title":"每日一荐 2019-09 汇总","slug":"daily-featured-2019-09","date":"2019-09-04T16:00:00.000Z","updated":"2023-01-05T12:24:49.674Z","comments":true,"path":"2019/09/05/daily-featured-2019-09/","link":"","permalink":"https://lucifer.ren/blog/2019/09/05/daily-featured-2019-09/","excerpt":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。","text":"每天给你推荐一个新奇,好玩,高品质的开源库,好文,观点或言论等。 项目主页维护当前月份的内容,想看往期内容,可以去每日一荐主仓库翻到下方历史汇总部分,然后选择自己感兴趣的月份点进去即可。 2019-092019-09-30[工具]今天是 9 月的最后一天,明天就是十一了,提前祝大家国庆节快乐 ~~~ 🎉🎉🎉🎉🎉🎉🎉 ! 今天再来给大家安利 6 个 chrome 扩展程序,排名不分先后。 Proxy SwitchyOmega 此扩展为 SwitchySharp 的升级版,可替代 SwitchyPlus 或 Proxy Switchy. 可轻松快捷地管理和切换多个代理设置,是一个我使用多年的一个插件,配合 PAC 食用味道更好。 下载地址 OneTab 节省高达 95%的内存,并减轻标签页混乱现象。 有些标签关了舍不得,不关太多看的很乱并且更关键的是占用内存。有了这个工具就不存在这些问题了。 下载地址 AdBlock Plus Adblock Plus 是世界上最流行的浏览器扩展,世界各地有数百万用户在使用它。这是一个社区驱动的开源项目,有数百名志愿者为 Adblock Plus 的成功作出了贡献,以实现所有烦人的广告被自动阻挡。 下载地址 Multi-highlight 普通的网页搜索只能一个一个搜索,不能同时搜索多个关键字,这个扩展提供了这种功能上的扩展。 下载地址 HTML5 Outliner 我平时再看文章或者文档的时候习惯先看一遍目录或者大纲,然后再决定我到底要不要看,看哪里。我相信很多人和我一样有着同样的习惯。但是很多网站,包括 infoq,知乎等大网站这方面都做的比较差。下图是我的个人博客的大纲功能: 因此这款工具对于我这种人来说就非常重要了,他能根据当前网页的结果快速生成大纲,并且支持锚点功能,当然它也有很多覆盖不到的情况,因为标题的种类实现太多了,大家完全可以写一个div class = 'header'从而让这个工具无用武之地。 这也从侧面说明了语义化的重要性,不仅对于障碍人士重要,对于无障碍人士也有很大的意义。 下载地址 眼睛护航 把网页背景变成眼睛保护色或适合夜晚阅读的夜间模式,一些无法变色的小区块可以通过单击进行变色。到了晚上将自动从绿色护眼模式变为夜间阅读模式。当然,也可以手动强制使用绿色模式或夜间模式。 这在一些体验差的网站上极为重要,还有一些大量使用亮瞎眼的颜色网站也有很好的作用,类似提升阅读体验的扩展还有简悦。 下载地址 2019-09-29[工具]如果你是一个站长,那么你一定需要一个网站速度测试工具。 你的用户如果都是中国用户,那么用站长工具-国内测试应该就够用了。 如果你的用户有海外的话,可以试试站长工具-国际测试 (国内测速) (国际测试) 不得不吐槽下,网站体验做的不太好,而且广告有点多。 另外还有一个网站,不过这个只能够测试国内的网速,17ce的体验做的稍微好一点,广告也没有那么显眼,如果你的客户只是国内,不妨考虑这个。 最后介绍一个国外的网站pingdom,如果客户是全球的,可以考虑用这个,这个是这几个中用户体验做的最好的。给出的技术指标比较多一点,但是他没有区域分布热力图, 并且支持的区域也不多。 2019-09-27[类库]之前我写了一篇 【前端测试】 的草稿,一直搁置到现在,之前我做后端的时候,写测试尤其是单元测试是很普遍的。但是做前端这么久发现大家对这方面不是很重视, 其实前端也是非常需要做测试的。 今天给大家推荐的是一个非常流行的前端测试框架 jest 的 GUI 工具majestic (⚡ Zero config GUI for Jest) 2019-09-26[工具]你一定有想用某一个功能的快捷键,却想不出来的情况。也一定面临过多个软件快捷键不统一带来的烦恼,比如 A 软件CMD + S是保存, 另外一个软件 B 是Shift + S。 对于第一种问题,我们可以用一个叫 cheatsheet 的 mac 软件,有了它你就可以通过长按 command 键,从而查看当前应用的快捷键。 cheatsheet 下载地址: https://cheatsheet-mac.en.softonic.com/mac 顺便吐槽一下,cheatsheet 官网用户体验这块做的不怎么样 对于按键不统一的问题,我们可以直接修改对应软件的快捷键即可,毕竟大多数软件都是支持快捷键定制的,还有一些服务快捷键我们可以去系统偏好设置 - 键盘 - 服务中修改。 另外给大家安利一个软件Karabiner-Elements, 它是一个 mac 上好用的键盘定制工作,可以用来改键,定制超级键等,更多功能等你挖掘。 配合 HHKB 效果更佳 Karabiner-Elements 下载地址: https://github.com/tekezo/Karabiner-Elements 2019-09-25[技能]熟练使用命令行是一种常常被忽视,或者被认为难以掌握的技能,一旦掌握,会极大提高你工作的效率。当你能够熟练掌握这里列出的所有技巧时,你就学会了很多关于命令行的东西了。 今天介绍的这个仓库,首发于 Quora, 后面迁移到了 Github,并由众多高手做出了许多改进,现在已经有 6W+ Star 了。 仓库目录(目录是我用工具自己抓的,非官方提供): 仓库地址: https://github.com/jlevy/the-art-of-command-line/blob/master/README-zh.md 2019-09-24[工具]今天给大家分享的是 VSCode 前一段时间推出的 SSH 扩展,实际使用效果非常棒,除了延迟,让你感觉不到你是在操作远程的文件。虽然有延迟,但是也仅仅限于你和服务器有 IO 交互的情况下才会有感知,结合我的使用体验来说,是“几乎”感觉不到差异(当然也有可能我的网比较快)。 VSCode SSH 扩展允许你连接到远程的主机,虚拟机或者是容器。而你所需要做的仅仅是点击 SSH 面板,然后配置一下就好了,配置也极其简单,对于经常使用 SSH 的你来说千万不要错过了。 下面是官方提供的原理架构图: 地址: https://code.visualstudio.com/docs/remote/ssh 2019-09-23[好文]为什么一行 80 个字符是一个值得遵守的规范,即使你拥有 4k 显示器? 我个人一直是 80 字符的践行者,不仅仅是因为是这大家都普遍采用的标准,更重要的是我个人更习惯多窗口平铺的方式来展示我的窗口,这样效率更高一点,因此太大肯定会影响窗口平铺,太小又不方便阅读,80 对我来说其实刚刚好,其他比较常见的还有 100 字符等, 现在就让我们来看下为什么一行 80 个字符是一个值得遵守的规范吧。 文章地址: https://nickjanetakis.com/blog/80-characters-per-line-is-a-standard-worth-sticking-to-even-today 2019-09-20[工具]我开启了个人博客,增加了评论,分类,统计,RSS,歌单等功能, 之后的文章会在博客首发。 感兴趣的可以 RSS 订阅我的博客。订阅方法我画了个图。 RSS 是一种消息来源格式规范,用以聚合经常发布更新数据的网站,例如博客文章、新闻、音频或视频的网摘。RSS 文件包含全文或是节录的文字,再加上发布者所订阅之网摘数据和授权的元数据。 简单来说只要提供了符合 RSS 格式的数据源,你就可以订阅,然后在 RSS 阅读器上进行查看更新内容。 关于 RSS 订阅,今天我推荐的就是一个 RSS 的聚合器 feedly。https://feedly.com Feedly 是一个 RSS 聚合器应用程序,支持各种网页浏览器和运行 iOS 或 Android 的移动设备,也是一个基于云端的服务。其从各种在线资源聚合用户自定的新闻订阅源,并可与他人分享。 后续有机会我会向大家推荐我的 RSS 订阅源。 2019-09-19[工具]今天给大家推荐一款 MarkDown 编辑器。 MarkDown 在程序员中的使用频率是非常高的,Github 是最早一批对 MarkDown 语法支持度比较好的平台之一。我日常写文档,记笔记等都采用 MarkDown 格式来书写。 它不仅书写方便,而且由于其格式比较规范,因此理论上可以通过一些“转换规则”将其转化为各种表现形式,市面上也有很多基于 Markdown 的渲染器,比如markdown-it,也有很多基于这些渲染器制作的产品,比如docsify。 早些时候,我使用的比较多的是MacDown和 VSCode 自带的 Markdown 功能。这两个功能非常简单,但是却能满足我当时的需求,之后我开始经常用 Markdown 更新文章之类的,因此这些就显得不太够用了,现在我用的是 Yu Writer, 算是一个值得推荐的国人开发的 MarkDown 编辑器,功能非常强大而且免费。 你可能听过 MWeb,但是它是收费的,功能和这个比起来也并不占优势。 下载地址:https://ivarptr.github.io/yu-writer.site/ 2019-09-18[工具]前天分享了我的 chrome 插件管理器,今天我们就来分享我的《娱乐插件》。 listen1 娱乐插件第一个要分享的是一个听歌的插件,各个平台都有一些独家的音乐,就像视频网站一样,这就可怜了我们这些消费者。如果想要听所有的音乐就要办理各个 APP 的会员,或者在多个音乐 APP 中切换。 这个插件能让我们听到所有国内几个主流大平台的所有音乐,足不出户畅享所有的音乐,并且值得称赞的是它支持会员系统,你可以保存你的歌单,甚至可以直接登陆你的 Github 账户同步多端的数据。 仓库地址:https://github.com/listen1/listen1 Video Downloader Professional 我主要用它来下载 Youtube 的视频,据说可以下载任何视频网站的视频,但是我亲测了几个网站不可以。 扩展下载地址:https://chrome.google.com/webstore/detail/jpaglkhbmbmhlnpnehlffkgaaapoicnk Bilibili 全家桶 经常看番的朋友怎么能少的了几个好用的插件护体呢? 这几个插件的功能基本满足了我看番的所有需求,包括弹幕合并,查找弹幕,自动签到,一键直达,猜你喜欢等等,大家可以安装下自己体验。 bilibili 助手 pakku 哔哩哔哩弹幕过滤器 bilibili 猜你喜欢 2019-09-17[学习方法]很多人想要问我“你的成长史是怎么样的?能不能分享一下你的菜鸡成长史”。 开始我是抵触的,这种东西写的不好大家会骂你,写的“太好”也会骂你。 今天我就来做个“lucifer”系列的开篇吧,用图来描述“lucifer 的一天”。 lucifer 的早晨: lucifer 搬砖的一天开始了: lucifer 的晚上: 2019-09-16[工具]经常有同学问我“你的这个扩展看着不错,叫什么”, “有什么好用的扩展程序推荐么?”。 因此我打算出一个《工具推荐》专题, 然后细分一个类别《工具推荐 - chrome 插件》。 这个算是这个系列的开篇之作,我默默翻开自己的 chrome 插件列表来看,有什么好用的推荐给大家。突然灵机一动,干脆把这个“扩展插件管理器”安利给大家好了。之后我会向大家推荐更多好用好玩的插件,有“工具”,“效率”, “娱乐”,“前端”等等。 我的 chrome 插件差不多有 60 多个,插件多起来的时候,良好的分类,开启关闭,禁用,卸载等管理就变得非常重要了。毕竟谁也不想在众多插件中寻寻觅觅的感觉,也不想因为开启太多插件吃我们宝贵的内存吧?这个插件的名字是扩展管理器(Extension Manager) 对于没有梯子的同学,我还贴心地给大家准备了我从官方下载的扩展文件。 链接 2019-09-12[类库]今天给大家推荐的是一个在给 Git 提交记录增加 emojis 的仓库。 或许你知道AngularJS Git Commit Message Conventions , 现在很多开源项目和内部项目都在使用,是一道非常流行的 git commit message 规范。 它的好处就是你可以很轻松地通过提交信息,看出这次提交干的什么,影响范围等,比如是新特性,修复 bug,增加文档, 重构代码等。 这个项目可以说更进一步,通过图的形式,让你更快地感知到这些信息,可视化形式的沟通毫无疑问是最最有效的。因为人的大脑更擅长处理图像。 项目提供了几十种 emoji,几乎覆盖了所有的场景。 仓库地址: https://gitmoji.carloscuesta.me/ 2019-09-11[技能]Google 内部有很多通用的最佳实践,在这我推荐一个项目,这是挂在 google group 下的一套通用的工程实战指南,被各个项目广泛使用,覆盖全部的编程语言。 这个仓库分成两部分: 这部分是给 Code Reviewer(代码评审者)的指南 这部分是给 Change Author(CL 作者)的指南 代码评审者指南本来是一个完整的文档,一共分为 6 部分,读者可根据需要阅读。 修改列表(Change List/CL)制定者指南包括一些浏览代码评审的最佳方式,开发者可以快速处理评审结果。 项目地址: https://github.com/google/eng-practices 2019-09-10[类库]今天给大家推荐的是一个打包平台,不知道大家有没有听说过“polyfill.io”,用法有点像。 这个仓库是 fork 自 packed,并进行了魔改,你可以将多个包打包成一个单独的 ESM,支持多种 options, 仓库地址: https://github.com/webcomponents-dev/packd-es 2019-09-09[类库]一个可以将草稿转化 HTML 的工具,利用了机器学习来生成页面。 你可以手画一些东西,然后将其直接生成静态页面。缺点也很明显,一方面是静态的,因此没有什么交互,对于交互强的应用没什么用。其次就是生成的是 HTML,可维护性会比较差,如果生成类似 JSX 这样的中间产物可能会好一点。当然市面上其实已经有了生成 JSX 产物的开源框架了。 地址:https://github.com/ashnkumar/sketch-code 2019-09-06[学习方法, 好文]如何培养自己的程序员思维。- Problem-solving is the meta-skill. 文章地址: https://learnworthy.net/how-to-think-like-a-programmer/?utm_source=quora&utm_medium=referral 2019-09-05[类库]这是微软开源的内部用来构建大型应用的工具库,包括接口管理,文档管理,代码仓库管理等。 地址: https://github.com/microsoft/web-build-tools 历史汇总 暂无历史汇总 关注我我重新整理了下自己的公众号,并且我还给它换了一个名字脑洞前端,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 之后我的文章会同步到微信公众号 脑洞前端 ,你可以关注获取最新的文章,并和我进行交流。 另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 贡献 如果有想法和创意,请提issue或者进群提 如果想贡献代码,请提PR 如果需要修改项目中图片,这里存放了项目中绘制图的源代码, 大家可以用draw.io打开进行编辑。 LicenseApache-2.0","categories":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/categories/每日一荐/"},{"name":"2019-09","slug":"每日一荐/2019-09","permalink":"https://lucifer.ren/blog/categories/每日一荐/2019-09/"}],"tags":[{"name":"每日一荐","slug":"每日一荐","permalink":"https://lucifer.ren/blog/tags/每日一荐/"}]},{"title":"布隆过滤器","slug":"bloom-filter","date":"2019-09-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.851Z","comments":true,"path":"2019/09/04/bloom-filter/","link":"","permalink":"https://lucifer.ren/blog/2019/09/04/bloom-filter/","excerpt":"假设你现在要处理这样一个问题,你有一个网站并且拥有很多访客,每当有用户访问时,你想知道这个 ip 是不是第一次访问你的网站。你会怎么做呢?","text":"假设你现在要处理这样一个问题,你有一个网站并且拥有很多访客,每当有用户访问时,你想知道这个 ip 是不是第一次访问你的网站。你会怎么做呢? hashtable 可以么一个显而易见的答案是将所有的 ip 用 hashtable 存起来,每次访问都去 hashtable 中取,然后判断即可。但是题目说了网站有很多访客,假如有 10 亿个用户访问过,每个 ip 的长度是 4 byte,那么你一共需要 4 * 1000000000 = 4000000000Bytes = 4G , 如果是判断 URL 黑名单,由于每个 URL 会更长,那么需要的空间可能会远远大于你的期望。 bit另一个稍微难想到的解法是 bit, 我们知道 bit 有 0 和 1 两种状态,那么用来表示存在,不存在再合适不过了。 加入有 10 亿个 ip,我们就可以用 10 亿个 bit 来存储,那么你一共需要 1 * 1000000000 = (4000000000 / 8) Bytes = 128M, 变为原来的 1/32,如果是存储 URL 这种更长的字符串,效率会更高。 基于这种想法,我们只需要两个操作,set(ip) 和 has(ip) 这样做有两个非常致命的缺点: 当样本分布极度不均匀的时候,会造成很大空间上的浪费 我们可以通过散列函数来解决 当元素不是整型(比如 URL)的时候,BitSet 就不适用了 我们还是可以使用散列函数来解决, 甚至可以多 hash 几次 布隆过滤器布隆过滤器其实就是bit + 多个散列函数, 如果经过多次散列的值再 bit 上都为 1,那么可能存在(可能有冲突)。 如果有一个不为 1,那么一定不存在(一个值经过散列函数得到的值一定是唯一的),这也是布隆过滤器的一个重要特点。 布隆过滤器的应用 网络爬虫判断某个 URL 是否已经被爬取过 K-V 数据库 判断某个 key 是否存在 比如 Hbase 的每个 Region 中都包含一个 BloomFilter,用于在查询时快速判断某个 key 在该 region 中是否存在。 钓鱼网站识别 浏览器有时候会警告用户,访问的网站很可能是钓鱼网站,用的就是这种技术 从这个算法大家可以对 tradeoff(取舍) 有更入的理解。","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"布隆过滤器","slug":"算法/布隆过滤器","permalink":"https://lucifer.ren/blog/categories/算法/布隆过滤器/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"}]},{"title":"从老鼠试毒问题来看二分法","slug":"laoshushidu","date":"2019-06-12T16:00:00.000Z","updated":"2023-01-05T12:24:49.555Z","comments":true,"path":"2019/06/13/laoshushidu/","link":"","permalink":"https://lucifer.ren/blog/2019/06/13/laoshushidu/","excerpt":"很多人对于二分法的理解比较片面,之前碰到一个题目,从一个先升序后降序的数列中,比如 1 2 3 7 4 3 2 中运用二分法去查找一个给定的元素,很多人说根本不能二分,因为没有排序。其实 这道题完全可以使用二分查找进行解答, 如果你觉得不可以的话,很可能对二分法理解还比较片面。 这里以另外一个更加有趣(至少我认为)的例子来讲解一下二分法。","text":"很多人对于二分法的理解比较片面,之前碰到一个题目,从一个先升序后降序的数列中,比如 1 2 3 7 4 3 2 中运用二分法去查找一个给定的元素,很多人说根本不能二分,因为没有排序。其实 这道题完全可以使用二分查找进行解答, 如果你觉得不可以的话,很可能对二分法理解还比较片面。 这里以另外一个更加有趣(至少我认为)的例子来讲解一下二分法。 题目面试题: 有 1000 个一模一样的瓶子,其中有 1 瓶是毒药,老鼠喝了有毒的会在 24h 之后死亡。求最少需要多少老鼠才能在 24h 里找到有毒的那瓶。 解法这道题的解法有很多,今天我们来聊下用二分法来解这道题。 这道题似乎和我们看的的常见的二分法有很大的区别,但是仔细想一下, 二分法本质是将问题的规模缩小到原有的一半,带着这样的思想我们再来看一下。类似的,三分法就是将问题规模缩小为原来的 1/3. 我们先对 1000 个瓶子进行编号,从 1-1000 这样子。 不过我们不是通过我们大家平时生活中使用的十进制,而是使用再计算机中使用的二进制, 同时让大家感受一下二进制的魅力。 为了方便讲解,我们假设不是 1000 个瓶子,而是 4 个。 我们来编一下号: 123400 // #101 // #210 // #311 // #4 我们的目标是找到哪个瓶子有毒,换句话说我们目标是找到有毒瓶子的编号,再换句话说我们的目标是找到有毒瓶子的 3 个 bit 分别是什么,是 0 还是 1. 比如有毒的是 3 号瓶子,那么我们就是想确认第一个 bit 是 0,第二个 bit 是 1,第三个 bit 是 1,即 011,转化为 10 进制就是 3 号。 那么如何确定每一个 bit 是什么呢? 回想一下,我们手上有老鼠,老鼠有两个 state,alive 或者 died,这就是我们拥有的全部。 接下来我们逐一对瓶子进行分组,分组的依据就是每一个 bit 的值。 比如: 12345// 00 01 #g1:1 第一个bit是0// 10 11 #g1:2 第一个bit是1// 00 10 #g2:1 第二个bit是0// 01 11 #g2:2 第二个bit是1 我们来找第一个老鼠#1 来喝 g:1:1, 如果他死了,那么毒就在这一组,也就是说毒的第一个 bit 是 0,否则是 1 我们来找第二个老鼠#2 来喝 g:2:1, 如果他死了,那么毒就在这一组,也就是说毒的第二个 bit 是 0,否则是 1 所以我们可以看出, 两只老鼠就搞定了,我们按照这个思路,可以推到出 1000 个瓶子只需要 10 个瓶子, 即 log2 1000, 2 的 10 次方是 1024,因此 10 个老鼠够了,如果 1025 个瓶子的话,就需要 11 个老鼠了。 如果你仔细思考的话,不难看出,我们是在用老鼠喝了水之后的反应(生或死)来进行判断每一个 bit 的数字,不管生死,我们总能得出这个 bit 的值,是 0 还是 1. 因此每使用一只老鼠我们都将问题规模缩小为原来的 1/2. 这是典型的二分法。 这是最优解么是的,这是最优解,如果你愿意用严格的数学来证明的话,你可以试一下数学归纳法。 如果你想感性的判断一下的话,可以继续往下读。 什么是最优解? 最优解就是要让未知世界无机可乘,也就是说在最坏的情况下得到最优(现实世界都是未知的)。上面的例子,不管小老鼠是生还是死,我们都可以将问题规模缩小到 1/2. 也就是说最坏的情况就是最好的情况,也就是说没有最坏情况。 那么我们是否可以将问题规模缩小的 1/3 或者更小呢? 我们可以三分么简单来说,不可以, 因为老鼠只有两种 observable state, 即 alive, died. 假如我们有 10 个小球,其中有一个是异常的,其他 9 个都是一样的,我们怎么才能通过最少的称量来确定是哪一个异常,是重还是轻? 这个时候我们就可以使用三分法了,为什么?因为天平有三个 state, 平衡,左倾,右倾,使得我们”有可能“ 将问题规模缩小为 1/3, 事实上,确实可以实现将问题规模缩小到 1/3。 我会在之后的文章中进行讲解小球的问题最优策略, 并解释为什么这是最优策略。 Bonus基于比较的排序都无法逃脱 nlogn 时间复杂度的命运,这是为什么?能否利用本篇文章的思想进行解释?","categories":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/categories/算法/"},{"name":"二分法","slug":"算法/二分法","permalink":"https://lucifer.ren/blog/categories/算法/二分法/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"}]},{"title":"《数据结构与算法之美》-为工程师量身打造的数据结构与算法私教课","slug":"ad","date":"2010-03-03T16:00:00.000Z","updated":"2023-01-05T12:24:49.710Z","comments":true,"path":"2010/03/04/ad/","link":"","permalink":"https://lucifer.ren/blog/2010/03/04/ad/","excerpt":"很多人问我如何系统地学习数据结构与算法,是看书,刷题还是看视频? 这个问题没有一个放之四海而皆准的答案,这是一个因人而异的东西,我之前给初学者推荐过邓俊辉老师免费的《数据结构与算法》课程,以及为《算法图解》这本书。 然而这些只是适合初学者,真正想要掌握数据结构与算法还是不够的,学习了基础之后如何进阶呢?像《算法导论》这些经典书籍,虽然很全面,但是过于缺乏重点。很多人学起来都非常困难。而市面很多在线课程或者线下课程,大多是为了“应试”,只讲一些看似通用,实则脱离真实开发场景的内容。因此这里给大家推荐一本书《数据结构与算法之美》。","text":"很多人问我如何系统地学习数据结构与算法,是看书,刷题还是看视频? 这个问题没有一个放之四海而皆准的答案,这是一个因人而异的东西,我之前给初学者推荐过邓俊辉老师免费的《数据结构与算法》课程,以及为《算法图解》这本书。 然而这些只是适合初学者,真正想要掌握数据结构与算法还是不够的,学习了基础之后如何进阶呢?像《算法导论》这些经典书籍,虽然很全面,但是过于缺乏重点。很多人学起来都非常困难。而市面很多在线课程或者线下课程,大多是为了“应试”,只讲一些看似通用,实则脱离真实开发场景的内容。因此这里给大家推荐一本书《数据结构与算法之美》。 程序员必会的数据结构与算法 订阅量 TOP1这本书是订阅量 Top1,50000+程序员的算法课堂,整个专栏会涵盖 100 多个算法真实项目场景案例,更难得的是它跟市面上晦涩的算法书籍不同的是,还手绘了一些清晰易懂的详解图(总共有 300 多张),市面上的大多数的算法教程都看过,走心的说,这个专栏是市面上唯一一门真正适用于工程师的专栏,作者是前 Google 工程师王争,相信会开启你的趣味学习算法之旅。 作者简介本书作者王争,前 Google 工程师,从事 Google 翻译相关的开发工作,深入研究算法数十年。现任某金融公司资深系统架构师,核心业务接口平台负责人,负责公司核心业务的架构设计、开发,以及可用性、高性能等相关技术问题的解决。 你能获得什么?1、掌握数据结构与算法的核心知识 我根据自己研读数十本算法书籍和多年项目开发的经验,精选了 20 个最实用数据结构和算法结合具体的软件开发实例,由浅入深进行讲解背后的设计思想,并适时总结一些实用“宝典”,保证你印象深刻,并且能够迅速对应到实际工作场景中。 2、提升算法思维,训练解决实际开发工作难题的强能力 这部分我会讲一些不是那么常用的数据结构和算法。虽然不常用,但是并不是说他们没用。设置这一部分的目的,是为了让你开拓视野,强化训练算法思维、逻辑思维。如果说学完基础部分可以考 80 分,那掌握这一部分就能让你成为尖子生。再回过来说,不管是现在流行的区块链技术还是人工智能,核心代码实现中都会涉及到这些算法。 3、学习开源框架、底层系统的设计原理,提升工作实战技能 最后我会通过实战部分串讲一下前面讲到的数据结构和算法,并且结合 Redis、Disruptor 这样的开源项目,剖析它们背后的数据结构和算法,帮你提升读懂源码的能力(JDK 很多源码,不乏大量的数据结构,例如大家喜闻乐见的面试题 HashMap)。 我掰着指头数了下,整个专栏会涵盖 100 多个算法真实项目场景案例。我还手绘了一些清晰易懂的详解图,帮你理解核心概念和实现过程,展示每个知识点的框架逻辑,让晦涩难懂的算法变得轻松有趣。 课程目录 订阅福利扫描下方二维码订阅即可,新人立减 30 元,另外我本人提供返现 11 元(到手 88 元),直接加我微信DevelopeEngineer即可。另外再送你 199 元限时学习礼包,你可以先领券再购买,领券地址:http://gk.link/a/108qc","categories":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/categories/数据结构/"},{"name":"算法","slug":"数据结构/算法","permalink":"https://lucifer.ren/blog/categories/数据结构/算法/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"https://lucifer.ren/blog/tags/数据结构/"},{"name":"算法","slug":"算法","permalink":"https://lucifer.ren/blog/tags/算法/"}]},{"title":"搬迁声明","slug":"oschina","date":"2008-12-31T16:00:00.000Z","updated":"2023-01-05T12:24:49.521Z","comments":true,"path":"2009/01/01/oschina/","link":"","permalink":"https://lucifer.ren/blog/2009/01/01/oschina/","excerpt":"我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:lucifer210,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply","text":"我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:lucifer210,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply","categories":[],"tags":[]}]} \ No newline at end of file diff --git a/index.html b/index.html index 4899a8782d..e0f604d3ea 100644 --- a/index.html +++ b/index.html @@ -343,6 +343,8 @@

    lucifer

    + + @@ -811,8 +813,6 @@

    - - @@ -833,8 +833,8 @@

    - - kuma - css-in-js 的未来? + + How To Make Monney

    @@ -864,7 +864,7 @@

    @@ -874,9 +874,9 @@

    @@ -896,7 +896,7 @@

      |   @@ -904,7 +904,7 @@

    @@ -922,10 +922,10 @@

    -

    kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。

    +

    有哪些赚钱的方法?具体如何操作?它们的赚钱逻辑是什么?什么情况下会亏钱?

    - + 阅读全文 @@ -935,9 +935,7 @@

    @@ -949,7 +947,7 @@

    -
    @@ -1041,10 +1047,10 @@

    - +

    kuma 是一个炙手可热的 css-in-js 的解决方案,有人甚至说他是 css-in-js 的未来,这篇文章我们来探讨一下 css-in-js 与 kuma。

    - + 阅读全文 @@ -1052,6 +1058,14 @@

    +
    + + 前端 + + css-in-js + +
    +
    @@ -2481,6 +2495,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2629,7 +2645,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/mylist/index.html b/mylist/index.html index b065803200..1f79e16e8e 100644 --- a/mylist/index.html +++ b/mylist/index.html @@ -818,6 +818,8 @@

     评论

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -966,7 +968,7 @@

     评论

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/10/index.html b/page/10/index.html index 826e18b7aa..12e066acc7 100644 --- a/page/10/index.html +++ b/page/10/index.html @@ -2089,6 +2089,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2237,7 +2239,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/page/11/index.html b/page/11/index.html index 0eb03d51b8..6592a92567 100644 --- a/page/11/index.html +++ b/page/11/index.html @@ -539,8 +539,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -582,7 +582,7 @@

    @@ -602,7 +602,7 @@

      |   @@ -610,7 +610,7 @@

    @@ -628,14 +628,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -765,11 +762,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -985,7 +985,7 @@

    @@ -2157,6 +2157,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2305,7 +2307,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/page/12/index.html b/page/12/index.html index 973617a4d0..812c146532 100644 --- a/page/12/index.html +++ b/page/12/index.html @@ -1181,12 +1181,12 @@

    @@ -1386,9 +1386,9 @@

    @@ -1477,8 +1477,8 @@

    - - 一文看懂《最大子序列和问题》 + + 穿上衣服我就不认识你了?来聊聊最长上升子序列

    @@ -1518,9 +1518,9 @@

    @@ -1540,7 +1540,7 @@

      |   @@ -1548,7 +1548,7 @@

    @@ -1566,10 +1566,15 @@

    -

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    +

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    +

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    +

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    +
    +

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    +
    - + 阅读全文 @@ -1579,13 +1584,11 @@

    @@ -2122,6 +2125,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2270,7 +2275,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/page/13/index.html b/page/13/index.html index cb471ca208..8603ee5e52 100644 --- a/page/13/index.html +++ b/page/13/index.html @@ -233,8 +233,8 @@

    - - 穿上衣服我就不认识你了?来聊聊最长上升子序列 + + 一文看懂《最大子序列和问题》

    @@ -274,9 +274,9 @@

    @@ -296,7 +296,7 @@

      |   @@ -304,7 +304,7 @@

    @@ -322,15 +322,10 @@

    -

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    -

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    -

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    -
    -

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    -
    +

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    - + 阅读全文 @@ -340,11 +335,13 @@

    @@ -1256,8 +1253,8 @@

    - - 阿里面试题:如何寻找「两个数组」的中位数? + + 力扣加加闪亮登场~

    @@ -1297,9 +1294,9 @@

    @@ -1319,7 +1316,7 @@

      |   @@ -1327,7 +1324,7 @@

    @@ -1345,11 +1342,12 @@

    -

    一个数组的中位数很容易求,那两个数组呢?

    +

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    +

    - + 阅读全文 @@ -1359,15 +1357,13 @@

    @@ -1431,9 +1427,9 @@

    @@ -2038,6 +2034,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2186,7 +2184,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/page/14/index.html b/page/14/index.html index be1cb7822b..5b9eb25a81 100644 --- a/page/14/index.html +++ b/page/14/index.html @@ -233,8 +233,8 @@

    - - 力扣加加闪亮登场~ + + 阿里面试题:如何寻找「两个数组」的中位数?

    @@ -274,9 +274,9 @@

    @@ -296,7 +296,7 @@

      |   @@ -304,7 +304,7 @@

    @@ -322,12 +322,11 @@

    -

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    -

    +

    一个数组的中位数很容易求,那两个数组呢?

    @@ -1190,7 +1191,7 @@

    @@ -1319,7 +1320,7 @@

    @@ -2054,6 +2055,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2202,7 +2205,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/page/15/index.html b/page/15/index.html index 9770521592..129cbdd118 100644 --- a/page/15/index.html +++ b/page/15/index.html @@ -411,7 +411,7 @@

    @@ -2039,6 +2039,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2187,7 +2189,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/page/16/index.html b/page/16/index.html index 10215aed77..af189b1f21 100644 --- a/page/16/index.html +++ b/page/16/index.html @@ -2031,6 +2031,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2179,7 +2181,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/17/index.html b/page/17/index.html index 3464ed9b4d..a07d718167 100644 --- a/page/17/index.html +++ b/page/17/index.html @@ -407,7 +407,7 @@

    @@ -798,7 +798,7 @@

    @@ -1454,7 +1454,7 @@

    @@ -2057,6 +2057,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2205,7 +2207,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/18/index.html b/page/18/index.html index 02b1dd8f6b..fba5020bcc 100644 --- a/page/18/index.html +++ b/page/18/index.html @@ -276,7 +276,7 @@

    @@ -408,7 +408,7 @@

    @@ -658,7 +658,7 @@

    @@ -916,7 +916,7 @@

    @@ -1443,7 +1443,7 @@

    @@ -1503,12 +1503,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -2050,6 +2050,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2198,7 +2200,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/19/index.html b/page/19/index.html index 68324e0c79..e5194e01db 100644 --- a/page/19/index.html +++ b/page/19/index.html @@ -2048,6 +2048,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2196,7 +2198,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/2/index.html b/page/2/index.html index 790cb1ec50..f429338032 100644 --- a/page/2/index.html +++ b/page/2/index.html @@ -2059,6 +2059,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2207,7 +2209,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/20/index.html b/page/20/index.html index 838098f565..aab7f59cca 100644 --- a/page/20/index.html +++ b/page/20/index.html @@ -1489,6 +1489,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1637,7 +1639,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/3/index.html b/page/3/index.html index 385a2edfcd..067c9e1b14 100644 --- a/page/3/index.html +++ b/page/3/index.html @@ -2080,6 +2080,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2228,7 +2230,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/4/index.html b/page/4/index.html index 71b18729fd..b9bf06866a 100644 --- a/page/4/index.html +++ b/page/4/index.html @@ -2002,6 +2002,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2150,7 +2152,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/5/index.html b/page/5/index.html index 59e6588004..1aa9c75097 100644 --- a/page/5/index.html +++ b/page/5/index.html @@ -2062,6 +2062,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2210,7 +2212,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/6/index.html b/page/6/index.html index a93cf8136b..c1913bb153 100644 --- a/page/6/index.html +++ b/page/6/index.html @@ -2068,6 +2068,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2216,7 +2218,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/page/7/index.html b/page/7/index.html index 43e9f12aee..c69606ad23 100644 --- a/page/7/index.html +++ b/page/7/index.html @@ -2119,6 +2119,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2267,7 +2269,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/8/index.html b/page/8/index.html index 22e6edcc69..452bcf1beb 100644 --- a/page/8/index.html +++ b/page/8/index.html @@ -874,12 +874,12 @@

    @@ -2085,6 +2085,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2233,7 +2235,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/page/9/index.html b/page/9/index.html index 15100c1cb1..e842cb1604 100644 --- a/page/9/index.html +++ b/page/9/index.html @@ -2100,6 +2100,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2248,7 +2250,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/search.xml b/search.xml index ecc22e91e2..87564c1718 100644 --- a/search.xml +++ b/search.xml @@ -3,6 +3,31 @@ + + How To Make Monney + + /blog/2024/03/10/make-money/ + + 有哪些赚钱的方法?具体如何操作?它们的赚钱逻辑是什么?什么情况下会亏钱?

    1. 了解你赚的是什么钱

    首先你要知道你赚的是什么钱。即什么情况下会赚钱,什么情况下会赔钱。

    这个问题可以很简单,也可以很复杂。

    了解赚的什么钱可以很简单。比如赌马,你赌对了就赚钱,赌错了就赔钱。再比如做生意,利润大于 0 就赚钱,否则就赔钱。再比如做股票,你买了股票,股票涨了你赚钱,股票跌了你赔钱。

    了解赚的什么钱也可以很复杂。

    比如做空股票,你融券卖了股票,股票跌了你赚钱,股票涨了你赔钱,因为你是借的股。做空外汇也是一样的。

    再比如结构化理财产品,你买了一个结构化理财产品,产品的收益是由多种金融工具的表现决定的,比如股票、债券、商品、汇率等。这时候你就需要了解这些金融工具的表现,才能知道你赚的是什么钱。除此之外,结构化产品还会有很多附加条件。

    后面我们了解赚钱的手段的时候,一定先要搞清楚赚的是什么钱。即什么时候我会赚钱,什么时候我会赔钱。

    2. 赚钱的手段

    存款,撸空投,新币挖矿基本是无风险的。而铭文和基金是有风险的。其中撸空投,只需要时间(有手就行)。

    利用信息差则是一个很大的话题,这里不做详细介绍。只是有一些有意思的信息差例子的时候会提一下,帮助大家打开思路。

    2.0 存款

    将钱存到银行,银行会给你利息。利息是你赚的钱。利息是由存款金额、存款期限、利率决定的。

    也可以存货币基金,可以 T+0 支取。部分货币基金,比如支付宝,微信,招商银行都提供随时消费的货币基金。

    另外你可以转化为 u,然后存活期。部分时间 u 的利率会特别高,比如最近(2024-02) u 的利率普遍都是年化 10%。

    如果不考虑国家和社会的重大变故,这类理财几乎没有风险,因此风险账面上不会赔钱。

    什么时候我会赚钱?账面上一定会赚钱,不过有可能是约定日子才能取。由于这种理财方式时间会比较久,因此可能需要考虑通货膨胀的影响。这样的话存款利率大于通货膨胀率的时候会赚钱。

    什么时候我会赔钱?存款利率小于通货膨胀率的时候。

    2.1 撸空投

    撸空投是一种赚钱的手段。空投是指项目方免费发放代币给用户。撸空投就是指用户通过参与项目方的活动,获取免费的代币。

    比如之前参与的 strk 空投,就可以通过绑定 github 免费获取 111 strk 代币。

    什么时候我会赚钱?空投的代币上线交易所,且价格大于 0 的时候。什么时候我会赔钱?理论上不存在,因为你没有任何资金投入。

    也有的空投是直接不需要你参与,叫零交互空投。直接给你转 token 的,这种空投是最好的,因为你不需要任何成本(时间成本都没有),直接就有钱了。

    那么如何提前埋伏零交互空投呢?

    首先要储备头部资产:

    • 蓝筹 NFT(人数多,共识高),如 NodeMonkey、Bitcoin Frog、Bitcoin Puppets 等。

    • BRC 20 头部铭文,如 ORDI、SATS。

    • 各个协议的头部资产,如 ARC 20 中的 Atom 和 Quark,BRC 420 中的蓝盒子等。

    其次,要做好资产分钱包,通过上述的例子发现,各个钱包空投的资产近乎相同,为了体现出比特币生态的公平性,未来大部分空投还是会设定一个下界限(像 Runestone 的空投规则一样,只要满足要求就可以获得等额的空投份额)。

    最后,做好资产隔离,避免不同协议资产混用。

    Reference:

    2.2 铭文

    铭文是一种利用新技术在比特币上创建的数字印记。如果将比特币比作数字黄金,那么铭文便是黄金饰品,它们共享相同的本质。

    有人为铭文起了一个好名字——BRC-20,称其为新的代币分发方式,没有项目方、没有跑路、没有 rug pull 风险,让大家机会均等。然而,事实真的如此吗??

    BRC20 代币是一种在比特币链上铸造的实验性 meme 代币,利用 ordinals 协议把代币部署在链上,任何人都可以铸造,按照先到先得原理,不支持智能合约。

    ERC-20 的出现才是重头戏,解决了高昂 gas 费,提高了出快速度,完美解决了 BRC-20 的弊端,现在铭文市场乱七八糟什么项目都来凑一下热闹,但把问题简单化,BRC-20 提出了概念,ERC-20 解决了问题,也就是说目前只有这两个具备实际价值,其它都是蹭热度!

    整个打铭文的流程简单来讲就分为三个步骤。

    一、准备好 web3 钱包并充好 btc 也就是 gas 费,一般选择 unisat 或者 uni web3 钱包。二、查询要打的铭文的铭刻情况看是否已经被打完,在 unisat 网站都可以查到。三、选择一个手续费比较低的时间点进行 mint,一般零晨会低一点

    流程看似是很简单的,但是注意事项目,小伙伴们一定要搞清,以免出现一些不必要损失。打铭文最大的开支就是 gas 费也就是手续费,一旦参与不管你是否最后能不能够打到,手续费都会花掉,不会退回的。所在,选择适当的手续费设置是很重要的。第二个注意事项,就是要打一些龙头的项目,有热度的项目,这样才能让你打的铭有价值可以流通

    铸造好的铭文可以在铭文市场进行出售,价格相差非常大,涨跌幅也非常大,价格就是铭文市场的交易价格,当然如果铸造失败,就可以理解为你的价格是 0。

    铸币的成本约等于 gas 费。

    什么时候会赚钱?铭文的代币上线交易所,且价格大于铸币成本。什么时候会赔钱?铸币成本大于代币价格。

    2.3 新币挖矿

    新币挖矿是一种赚钱的手段。新币挖矿是指用户通过参与项目方的活动,获取代币。新币挖矿的活动有很多种,比如质押、流动性挖矿、空投等。

    你可以将 bnb 存到活期,当有新币可以挖的时候,就会自动挖矿。挖矿结束一般一天左右就可以交易,你可以将其转化为 usdt 等其他币种。

    什么时候我会赚钱?赚的钱是新币,付出的是 bnb 等其他货币。因此 bnb 等其他货币的跌幅小于新币的卖出价的时候会赚钱。反之会赔钱。

    2.4 基金

    刚刚提到了货币基金。除此之外,也可以投资股票基金、债券基金、混合基金、指数基金等。

    目前我买的比较多的是指数基金和股票基金。指数基金是一种被动投资的方式。指数基金的收益是由指数的表现决定的。比如沪深 300 指数基金,就是跟踪沪深 300 指数的表现。而股票基金是一种主动投资的方式。股票基金的收益是由基金经理的投资决策决定的。

    债券基金基本上就是起到对冲的作用,债券基金的收益是由债券的表现决定的。比如债务人还不上钱,债券基金就会亏钱。

    什么时候我会赚钱?基金的收益大于 0 的时候会赚钱。什么时候我会赔钱?基金的收益小于 0 的时候会赔钱。

    那什么是基金的收益,如何看基金的收益呢?基金的收益是由基金的净值决定的。比如你买了一个基金,基金的净值是 1 ,过了一段时间,基金的净值变成了 1.1 ,那么你赚了 0.1 。反之亏了 0.1 。

    那什么影响基金的净值。答案是基金经理持有的股票的表现。比如你买了一个股票基金,基金经理持有的股票价格都涨了,那么基金的净值就会涨。

    那什么影响股票的价格?答案是股票的成交价格。具体来说就是出价中最低的。比如你卖了一个股票,你出价 10 元,别人出价 9 元,那么交易所优先处理 9 元的,如果有人愿意用大于等于 9 元的买就可能成交了,成交后这个股票的价格就是 9 元。

    相反,如果你买了一个股票,你出价 10 元,别人出价 11 元,那么交易所优先处理 11 元的,如果有人愿意用小于等于 11 元的卖就可能成交了,成交后这个股票的价格就是 11 元。

    这个成交价格的变动就是股价的变动。股价的变动就是基金的净值的变动。基金的净值的变动就是基金的收益的变动。

    可以看出基金的收益其实就是大家的心理预期。比如大家都认为股票会涨,那么股票的价格就会涨,基金的净值就会涨,基金的收益就会涨。那么什么时候大家会认为股票会涨呢?答案是有利好的消息。这也是大家都对一些利好和利空消息特别敏感的原因。

    利好和利空消息在短期上基本上和股价是正相关的。长期上,还是要看公司的盈利能力。因此如果你是短期投资者,那么你就要关注利好和利空消息。如果你是长期投资者,那么你就要关注公司的盈利能力。

    2.5 股票(或等价产品)

    这里的等价产品指的是金融衍生品,比如虚拟币,期权,期货,ETF 等。

    什么是网格交易?

    2.6 利用信息差

    比如最近 sora 特别火,很多人就在卖 sora 内测账号,卖 sora 课程。(虽然 sora 截止到目前还没开放内测,但这并不影响他们赚钱,因为韭菜不知道,这也是一种信息差)

    当然信息差并不一定是这种消极的。比如很多网站都有推荐返佣,比如推荐返佣 10%,那么你可以通过推荐别人来赚钱。这个时候你可以搞一些路边的扫码送礼品的活动,比如你可以在路边发放一些小礼品,然后让别人扫码,这样你就可以赚到推荐返佣。相信大家在街上也遇到过这种人。

    2.7 杠杆和合约

    杠杆和合约是两种风险比较大的赚钱的手段,两者有很多相似的地方。

    杠杆是一种利用小额的资金来进行数倍于原始金额的投资,以期望获取相对投资标的物波动的数倍收益率,抑或亏损。

    合约是买方同意在一段指定时间之后按特定价格接收某种资产,卖方同意在一段指定时间之后按特定价格交付某种资产的协议。

    杠杆和合约有什么区别?

    • 【1】.操作方法不一样:杠杆是通过平台借币的方式,来在现货市场中超额配置资产,操作过程就会包含借币费率+交易费率。合约是采用交割合约的模式,意味着在进行交易前就可以选择产品本身的杠杆倍数,这种模式去除了现货市场中需要借币才能进行杠杆的操作。

    • 【2】.定义不一样: 杠杆交易就是利用小额的资金来进行数倍于原始金额的投资,以期望获取相对投资标的物波动的数倍收益率,抑或亏损。合约是买方同意在一段指定时间之后按特定价格接收某种资产,卖方同意在一段指定时间之后按特定价格交付某种资产的协议。

    • 【3】.规则不一样:杠杆交易是投资者用自有资金作为担保,从银行或经纪商处提供的融资放大来进行外汇交易,也就是放大投资者的交易资金。期货合约是由交易所设计,经国家监管机构审批上市的标准化的合约。期货合约的持有者可借交收现货或进行对冲交易来履行或解除合约义务。

    • 【4】.特点不一样:杠杆交易有 24 小时交易、全球性市场、交易品种少、风险可灵活控制,双向交易,操作灵活,高杠杆比例、交易费用低、入市门槛低等特点。期货合约的特点则是以小博大、双向交易、不必担心履约问题、市场透明以及组织严密,效率高。

    什么时候我会赚钱?

    • 如果做多,价格上涨超过手续费和交易费,你会赚钱。
    • 如果做空,价格下跌超过手续费和交易费,你会赚钱。

    需要特别注意的是,杠杆和合约都可能会发生爆仓,风险很大。

    3. 庄家对散户的影响(你是如何亏钱的)

    TBD

    ]]>
    + + + + + 理财 + + + + + + + 理财 + + + +
    + + + kuma - css-in-js 的未来? @@ -32,19 +57,6 @@ - - - - /blog/2024/02/15/make-money/ - - How To Make Monney

    1. 了解你赚的是什么钱

    首先你要知道你赚的是什么钱。即什么情况下会赚钱,什么情况下会赔钱。

    这个问题可以很简单,也可以很复杂。

    了解赚的什么钱可以很简单。比如赌马,你赌对了就赚钱,赌错了就赔钱。再比如做生意,利润大于 0 就赚钱,否则就赔钱。再比如做股票,你买了股票,股票涨了你赚钱,股票跌了你赔钱。

    了解赚的什么钱也可以很复杂。

    比如做空股票,你融券卖了股票,股票跌了你赚钱,股票涨了你赔钱,因为你是借的股。做空外汇也是一样的。

    再比如结构化理财产品,你买了一个结构化理财产品,产品的收益是由多种金融工具的表现决定的,比如股票、债券、商品、汇率等。这时候你就需要了解这些金融工具的表现,才能知道你赚的是什么钱。除此之外,结构化产品还会有很多附加条件。

    后面我们了解赚钱的手段的时候,一定先要搞清楚赚的是什么钱。即什么时候我会赚钱,什么时候我会赔钱。

    2. 赚钱的手段

    存款,撸空投,新币挖矿基本是无风险的。而铭文和基金是有风险的。其中撸空投,只需要时间(有手就行)。

    利用信息差则是一个很大的话题,这里不做详细介绍。只是有一些有意思的信息差例子的时候会提一下,帮助大家打开思路。

    2.0 存款

    将钱存到银行,银行会给你利息。利息是你赚的钱。利息是由存款金额、存款期限、利率决定的。

    也可以存货币基金,可以 T+0 支取。部分货币基金,比如支付宝,微信,招商银行都提供随时消费的货币基金。

    另外你可以转化为 u,然后存活期。部分时间 u 的利率会特别高,比如最近(2024-02) u 的利率普遍都是年化 10%。

    如果不考虑国家和社会的重大变故,这类理财几乎没有风险,因此风险账面上不会赔钱。

    什么时候我会赚钱?账面上一定会赚钱,不过有可能是约定日子才能取。由于这种理财方式时间会比较久,因此可能需要考虑通货膨胀的影响。这样的话存款利率大于通货膨胀率的时候会赚钱。

    什么时候我会赔钱?存款利率小于通货膨胀率的时候。

    2.1 撸空投

    撸空投是一种赚钱的手段。空投是指项目方免费发放代币给用户。撸空投就是指用户通过参与项目方的活动,获取免费的代币。

    比如之前参与的 strk 空投,就可以通过绑定 github 免费获取 111 strk 代币。

    什么时候我会赚钱?空投的代币上线交易所,且价格大于 0 的时候。什么时候我会赔钱?理论上不存在,因为你没有任何资金投入。

    也有的空投是直接不需要你参与,叫零交互空投。直接给你转 token 的,这种空投是最好的,因为你不需要任何成本(时间成本都没有),直接就有钱了。

    那么如何提前埋伏零交互空投呢?

    首先要储备头部资产:

    其次,要做好资产分钱包,通过上述的例子发现,各个钱包空投的资产近乎相同,为了体现出比特币生态的公平性,未来大部分空投还是会设定一个下界限(像 Runestone 的空投规则一样,只要满足要求就可以获得等额的空投份额)。

    最后,做好资产隔离,避免不同协议资产混用。

    Reference:

    2.2 铭文

    铭文是一种利用新技术在比特币上创建的数字印记。如果将比特币比作数字黄金,那么铭文便是黄金饰品,它们共享相同的本质。

    有人为铭文起了一个好名字——BRC-20,称其为新的代币分发方式,没有项目方、没有跑路、没有 rug pull 风险,让大家机会均等。然而,事实真的如此吗??

    BRC20 代币是一种在比特币链上铸造的实验性 meme 代币,利用 ordinals 协议把代币部署在链上,任何人都可以铸造,按照先到先得原理,不支持智能合约。

    ERC-20 的出现才是重头戏,解决了高昂 gas 费,提高了出快速度,完美解决了 BRC-20 的弊端,现在铭文市场乱七八糟什么项目都来凑一下热闹,但把问题简单化,BRC-20 提出了概念,ERC-20 解决了问题,也就是说目前只有这两个具备实际价值,其它都是蹭热度!

    整个打铭文的流程简单来讲就分为三个步骤。

    一、准备好 web3 钱包并充好 btc 也就是 gas 费,一般选择 unisat 或者 uni web3 钱包。二、查询要打的铭文的铭刻情况看是否已经被打完,在 unisat 网站都可以查到。三、选择一个手续费比较低的时间点进行 mint,一般零晨会低一点

    流程看似是很简单的,但是注意事项目,小伙伴们一定要搞清,以免出现一些不必要损失。打铭文最大的开支就是 gas 费也就是手续费,一旦参与不管你是否最后能不能够打到,手续费都会花掉,不会退回的。所在,选择适当的手续费设置是很重要的。第二个注意事项,就是要打一些龙头的项目,有热度的项目,这样才能让你打的铭有价值可以流通

    铸造好的铭文可以在铭文市场进行出售,价格相差非常大,涨跌幅也非常大,价格就是铭文市场的交易价格,当然如果铸造失败,就可以理解为你的价格是 0。

    铸币的成本约等于 gas 费。

    什么时候会赚钱?铭文的代币上线交易所,且价格大于铸币成本。什么时候会赔钱?铸币成本大于代币价格。

    2.3 新币挖矿

    新币挖矿是一种赚钱的手段。新币挖矿是指用户通过参与项目方的活动,获取代币。新币挖矿的活动有很多种,比如质押、流动性挖矿、空投等。

    你可以将 bnb 存到活期,当有新币可以挖的时候,就会自动挖矿。挖矿结束一般一天左右就可以交易,你可以将其转化为 usdt 等其他币种。

    什么时候我会赚钱?赚的钱是新币,付出的是 bnb 等其他货币。因此 bnb 等其他货币的跌幅小于新币的卖出价的时候会赚钱。反之会赔钱。

    2.4 基金

    刚刚提到了货币基金。除此之外,也可以投资股票基金、债券基金、混合基金、指数基金等。

    目前我买的比较多的是指数基金和股票基金。指数基金是一种被动投资的方式。指数基金的收益是由指数的表现决定的。比如沪深 300 指数基金,就是跟踪沪深 300 指数的表现。而股票基金是一种主动投资的方式。股票基金的收益是由基金经理的投资决策决定的。

    债券基金基本上就是起到对冲的作用,债券基金的收益是由债券的表现决定的。比如债务人还不上钱,债券基金就会亏钱。

    什么时候我会赚钱?基金的收益大于 0 的时候会赚钱。什么时候我会赔钱?基金的收益小于 0 的时候会赔钱。

    那什么是基金的收益,如何看基金的收益呢?基金的收益是由基金的净值决定的。比如你买了一个基金,基金的净值是 1 ,过了一段时间,基金的净值变成了 1.1 ,那么你赚了 0.1 。反之亏了 0.1 。

    那什么影响基金的净值。答案是基金经理持有的股票的表现。比如你买了一个股票基金,基金经理持有的股票价格都涨了,那么基金的净值就会涨。

    那什么影响股票的价格?答案是股票的成交价格。具体来说就是出价中最低的。比如你卖了一个股票,你出价 10 元,别人出价 9 元,那么交易所优先处理 9 元的,如果有人愿意用大于等于 9 元的买就可能成交了,成交后这个股票的价格就是 9 元。

    相反,如果你买了一个股票,你出价 10 元,别人出价 11 元,那么交易所优先处理 11 元的,如果有人愿意用小于等于 11 元的卖就可能成交了,成交后这个股票的价格就是 11 元。

    这个成交价格的变动就是股价的变动。股价的变动就是基金的净值的变动。基金的净值的变动就是基金的收益的变动。

    可以看出基金的收益其实就是大家的心理预期。比如大家都认为股票会涨,那么股票的价格就会涨,基金的净值就会涨,基金的收益就会涨。那么什么时候大家会认为股票会涨呢?答案是有利好的消息。这也是大家都对一些利好和利空消息特别敏感的原因。

    利好和利空消息在短期上基本上和股价是正相关的。长期上,还是要看公司的盈利能力。因此如果你是短期投资者,那么你就要关注利好和利空消息。如果你是长期投资者,那么你就要关注公司的盈利能力。

    2.5 股票(或等价产品)

    这里的等价产品指的是金融衍生品,比如虚拟币,期权,期货,ETF 等。

    什么是网格交易?

    2.6 利用信息差

    比如最近 sora 特别火,很多人就在卖 sora 内测账号,卖 sora 课程。(虽然 sora 截止到目前还没开放内测,但这并不影响他们赚钱,因为韭菜不知道,这也是一种信息差)

    当然信息差并不一定是这种消极的。比如很多网站都有推荐返佣,比如推荐返佣 10%,那么你可以通过推荐别人来赚钱。这个时候你可以搞一些路边的扫码送礼品的活动,比如你可以在路边发放一些小礼品,然后让别人扫码,这样你就可以赚到推荐返佣。相信大家在街上也遇到过这种人。

    2.7 杠杆和合约

    杠杆和合约是两种风险比较大的赚钱的手段,两者有很多相似的地方。

    杠杆是一种利用小额的资金来进行数倍于原始金额的投资,以期望获取相对投资标的物波动的数倍收益率,抑或亏损。

    合约是买方同意在一段指定时间之后按特定价格接收某种资产,卖方同意在一段指定时间之后按特定价格交付某种资产的协议。

    杠杆和合约有什么区别?

    什么时候我会赚钱?

    需要特别注意的是,杠杆和合约都可能会发生爆仓,风险很大。

    3. 庄家对散户的影响(你是如何亏钱的)

    TBD

    ]]> - - - - - - - 关于 Error Boundaries, 你需要知道的一切 @@ -2120,12 +2132,12 @@ - 数学 - 数据结构 算法 + 数学 + 最大公约数 @@ -2934,19 +2946,19 @@ - 字节跳动的算法面试题是什么难度? - - /blog/2020/09/06/byte-dance-algo-ex/ + 字节跳动的算法面试题是什么难度?(第二弹) + + /blog/2020/09/06/byte-dance-algo-ex-2017/ - 由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    这套题一共四道题, 两道问答题, 两道编程题。

    其中一道问答题是 LeetCode 426 的原题,只不过题型变成了找茬(改错)。可惜的是 LeetCode 的 426 题是一个会员题目,没有会员的就看不来了。不过,剑指 Offer 正好也有这个题,并且力扣将剑指 Offer 全部的题目都 OJ 化了。 这道题大家可以去 https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof 提交答案。简单说一下这个题目的思路,我们只需要中序遍历即可得到一个有序的数列,同时在中序遍历过程中将 pre 和 cur 节点通过指针串起来即可。

    另一个问答是红包题目,这里不多说了。我们重点看一下剩下两个算法编程题。

    两个问答题由于不能在线判题,我没有做,只做了剩下两个编程题。

    球队比赛

    第一个编程题是一个球队比赛的题目。

    题目描述

    有三只球队,每只球队编号分别为球队 1,球队 2,球队 3,这三只球队一共需要进行 n 场比赛。现在已经踢完了 k 场比赛,每场比赛不能打平,踢赢一场比赛得一分,输了不得分不减分。已知球队 1 和球队 2 的比分相差 d1 分,球队 2 和球队 3 的比分相差 d2 分,每场比赛可以任意选择两只队伍进行。求如果打完最后的 (n-k) 场比赛,有没有可能三只球队的分数打平。

    思路

    假设球队 1,球队 2,球队 3 此时的胜利次数分别为 a,b,c,球队 1,球队 2,球队 3 总的胜利次数分别为 n1,n2,n3。

    我一开始的想法是只要保证 n1,n2,n3 相等且都小于等于 n / 3 即可。如果题目给了 n1,n2,n3 的值就直接:

    1
    print(n1 == n2 == n3 == n / 3)

    可是不仅 n1,n2,n3 没给, a,b,c 也没有给。

    实际上此时我们的信息仅仅是:

    1
    2
    3
    ① a + b + c = k
    ② a - b = d1 or b - a = d1
    ③ b - c = d2 or c - b = d2

    其中 k 和 d1,d2 是已知的。a ,b,c 是未知的。 也就是说我们需要枚举所有的 a,b,c 可能性,解方程求出合法的 a,b,c,并且 合法的 a,b,c 都小于等于 n / 3 即可。

    这个 a,b,c 的求解数学方程就是中学数学难度, 三个等式化简一下即可,具体见下方代码区域。

    • a 只需要再次赢得 n / 3 - a 次
    • b 只需要再次赢得 n / 3 - b 次
    • c 只需要再次赢得 n / 3 - c 次
    1
    2
    3
    n1 = a + n / 3 - a = n / 3
    n2 = b + (n / 3 - b) = n / 3
    n3 = c + (n / 3 - c) = n / 3

    代码(Python)

    牛客有点让人不爽, 需要 print 而不是 return

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    t = int(input())
    for i in range(t):
    n, k, d1, d2 = map(int, input().split(" "))
    if n % 3 != 0:
    print('no')
    continue
    abcs = []
    for r1 in [-1, 1]:
    for r2 in [-1, 1]:
    a = (k + 2 * r1 * d1 + r2 * d2) / 3
    b = (k + -1 * r1 * d1 + r2 * d2) / 3
    c = (k + -1 * r1 * d1 + -2 * r2 * d2) / 3
    a + r1
    if 0 <= a <= k and 0 <= b <= k and 0 <= c <= k and a.is_integer() and b.is_integer() and c.is_integer():
    abcs.append([a, b, c])
    flag = False
    for abc in abcs:
    if len(abc) > 0 and max(abc) <= n / 3:
    flag = True
    break
    if flag:
    print('yes')
    else:
    print('no')

    复杂度分析

    • 时间复杂度:$O(t)$
    • 空间复杂度:$O(t)$

    小结

    感觉这个难度也就是力扣中等水平吧,力扣也有一些数学等式转换的题目, 比如 494.target-sum

    转换字符串

    题目描述

    有一个仅包含’a’和’b’两种字符的字符串 s,长度为 n,每次操作可以把一个字符做一次转换(把一个’a’设置为’b’,或者把一个’b’置成’a’);但是操作的次数有上限 m,问在有限的操作数范围内,能够得到最大连续的相同字符的子串的长度是多少。

    思路

    看完题我就有种似曾相识的感觉。

    每次对妹子说出这句话的时候,她们都会觉得好假 ^_^

    不过这次是真的。 ”哦,不!每次都是真的“。 这道题其实就是我之前写的滑动窗口的一道题【1004. 最大连续 1 的个数 III】滑动窗口(Python3)的换皮题。 专题地址:https://github.com/azl397985856/leetcode/blob/master/thinkings/slide-window.md

    所以说,如果这道题你完全没有思路的话。说明:

    • 抽象能力不够。
    • 滑动窗口问题理解不到位。

    第二个问题可以看我上面贴的地址,仔细读读,并完成课后练习即可解决。

    第一个问题就比较困难了, 不过多看我的题解也可以慢慢提升的。比如:

    回归这道题。其实我们只需要稍微抽象一下, 就是一个纯算法题。 抽象的另外一个好处则是将很多不同的题目返璞归真,从而可以在茫茫题海中逃脱。这也是我开启《我是你的妈妈呀》 的原因之一。

    如果我们把 a 看成是 0 , b 看成是 1。或者将 b 看成 1, a 看成 0。不就抽象成了:

    1
    2
    3
    4

    给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 m 个值从 0 变成 1 。

    返回仅包含 1 的最长(连续)子数组的长度。

    这就是 力扣 1004. 最大连续 1 的个数 III 原题。

    因此实际上我们要求的是上面两种情况:

    1. a 表示 0, b 表示 1
    2. a 表示 1, b 表示 0

    的较大值。

    lucifer 小提示: 其实我们也可以仅仅考虑一种情况,比如 a 看成是 0 , b 看成是 1。这个时候, 我们操作变成了两种情况,0 变成 1 或者 1 变成 0,同时求解的也变成了最长连续 0 或者 最长连续 1 。 由于这种抽象操作起来更麻烦, 我们不考虑。

    问题得到了抽象就好解决了。我们只需要记录下加入窗口的是 0 还是 1:

    • 如果是 1,我们什么都不用做
    • 如果是 0,我们将 m 减 1

    相应地,我们需要记录移除窗口的是 0 还是 1:

    • 如果是 1,我们什么都不做
    • 如果是 0,说明加进来的时候就是 1,加进来的时候我们 m 减去了 1,这个时候我们再加 1。

    lucifer 小提示: 实际上题目中是求连续 a 或者 b 的长度。看到连续,大家也应该有滑动窗口的敏感度, 别管行不行, 想到总该有的。

    我们拿 A = [1, 1, 0, 1, 0, 1], m = 1 来说。看下算法的具体过程:

    lucifer 小提示: 左侧的数字表示此时窗口大小,黄色格子表示修补的墙,黑色方框表示的是窗口。

    这里我形象地将 0 看成是洞,1 看成是墙, 我们的目标就是补洞,使得连续的墙最长。

    每次碰到一个洞,我们都去不加选择地修补。由于 m 等于 1, 也就是说我们最多补一个洞。因此需要在修补超过一个洞的时候,我们需要调整窗口范围,使得窗口内最多修补一个墙。由于窗口表示的就是连续的墙(已有的或者修补的),因此最终我们返回窗口的最大值即可。

    由于下面的图窗口内有两个洞,这和”最多补一个洞“冲突, 我们需要收缩窗口使得满足“最多补一个洞”的先决条件。

    因此最大的窗口就是 max(2, 3, 4, …) = 4。

    lucifer 小提示: 可以看出我们不加选择地修补了所有的洞,并调整窗口,使得窗口内最多有 m 个修补的洞,因此窗口的最大值就是答案。然而实际上,我们并不需要真的”修补“(0 变成 1),而是仅仅修改 m 的值即可。

    我们先来看下抽象之后的其中一种情况的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Solution:
    def longestOnes(self, A: List[int], m: int) -> int:
    i = 0
    for j in range(len(A)):
    m -= 1 - A[j]
    if m < 0:
    m += 1 - A[i]
    i += 1
    return j - i + 1

    因此完整代码就是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def longestOnes(self, A: List[int], m: int) -> int:
    i = 0
    for j in range(len(A)):
    m -= 1 - A[j]
    if m < 0:
    m += 1 - A[i]
    i += 1
    return j - i + 1
    def longestAorB(self, A:List[int], m: int) -> int:
    return max(self.longestOnes(map(lambda x: 0 if x == 'a' else 1, A) ,m), self.longestOnes(map(lambda x: 1 if x == 'a' else 0, A),m))

    这里的两个 map 会生成两个不同的数组。 我只是为了方便大家理解才新建的两个数组, 实际上根本不需要,具体见后面的代码.

    代码(Python)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    i = 0
    n, m = map(int, input().split(" "))
    s = input()
    ans = 0
    k = m # 存一下,后面也要用这个初始值
    # 修补 b
    for j in range(n):
    m -= ord(s[j]) - ord('a')
    if m < 0:
    m += ord(s[i]) - ord('a')
    i += 1
    ans = j - i + 1
    i = 0
    # 修补 a
    for j in range(n):
    k += ord(s[j]) - ord('b')
    if k < 0:
    k -= ord(s[i]) - ord('b')
    i += 1
    print(max(ans, j - i + 1))

    复杂度分析

    • 时间复杂度:$O(N)$
    • 空间复杂度:$O(1)$

    小结

    这道题就是一道换了皮的力扣题,难度中等。如果你能将问题抽象,同时又懂得滑动窗口,那这道题就很容易。我看了题解区的参考答案, 内容比较混乱,不够清晰。这也是我写下这篇文章的原因之一。

    总结

    这一套字节跳动的题目一共四道,一道设计题,三道算法题。

    其中三道算法题从难度上来说,基本都是中等难度。从内容来看,基本都是力扣的换皮题。但是如果我不说他们是换皮题, 你们能发现么? 如果你可以的话,说明你的抽象能力已经略有小成了。如果看不出来也没有关系,关注我。 手把手扒皮给你们看,扒多了慢慢就会了。切记,不要盲目做题!如果你做了很多题, 这几道题还是看不出套路,说明你该缓缓,改变下刷题方式了。

    更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。

    关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

    ]]>
    + 由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    这套题一共 11 道题, 三道编程题, 八道问答题。本次给大家带来的就是这三道编程题。更多精彩内容,请期待我的搞定算法面试专栏。

    其中有一道题《异或》我没有通过所有的测试用例, 小伙伴可以找找茬,第一个找到并在公众号力扣加加留言的小伙伴奖励现金红包 10 元。

    1. 头条校招

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    头条的 2017 校招开始了!为了这次校招,我们组织了一个规模宏大的出题团队,每个出题人都出了一些有趣的题目,而我们现在想把这些题目组合成若干场考试出来,在选题之前,我们对题目进行了盲审,并定出了每道题的难度系统。一场考试包含 3 道开放性题目,假设他们的难度从小到大分别为 a,b,c,我们希望这 3 道题能满足下列条件:
    a<=b<=c
    b-a<=10
    c-b<=10
    所有出题人一共出了 n 道开放性题目。现在我们想把这 n 道题分布到若干场考试中(1 场或多场,每道题都必须使用且只能用一次),然而由于上述条件的限制,可能有一些考试没法凑够 3 道题,因此出题人就需要多出一些适当难度的题目来让每场考试都达到要求,然而我们出题已经出得很累了,你能计算出我们最少还需要再出几道题吗?

    输入描述:
    输入的第一行包含一个整数 n,表示目前已经出好的题目数量。

    第二行给出每道题目的难度系数 d1,d2,...,dn。

    数据范围

    对于 30%的数据,1 ≤ n,di ≤ 5;

    对于 100%的数据,1 ≤ n ≤ 10^5,1 ≤ di ≤ 100。

    在样例中,一种可行的方案是添加 2 个难度分别为 20 和 50 的题目,这样可以组合成两场考试:(20 20 23)和(35,40,50)。

    输出描述:
    输出只包括一行,即所求的答案。
    示例 1
    输入
    4
    20 35 23 40
    输出
    2

    思路

    这道题看起来很复杂, 你需要考虑很多的情况。,属于那种没有技术含量,但是考验编程能力的题目,需要思维足够严密。这种模拟的题目,就是题目让我干什么我干什么。 类似之前写的囚徒房间问题,约瑟夫环也是模拟,只不过模拟之后需要你剪枝优化。

    这道题的情况其实很多, 我们需要考虑每一套题中的难度情况, 而不需要考虑不同套题的难度情况。题目要求我们满足:a<=b<=c b-a<=10 c-b<=10,也就是题目难度从小到大排序之后,相邻的难度不能大于 10 。

    因此我们的思路就是先排序,之后从小到大遍历,如果满足相邻的难度不大于 10 ,则继续。如果不满足, 我们就只能让字节的老师出一道题使得满足条件。

    由于只需要比较同一套题目的难度,因此我的想法就是比较同一套题目的第二个和第一个,以及第三个和第二个的 diff

    • 如果 diff 小于 10,什么都不做,继续。
    • 如果 diff 大于 10,我们必须补充题目。

    这里有几个点需要注意。

    对于第二题来说:

    • 比如 1 30 40 这样的难度。 我可以在 1,30 之间加一个 21,这样 1,21,30 就可以组成一一套。
    • 比如 1 50 60 这样的难度。 我可以在 1,50 之间加 21, 41 才可以组成一套,自身(50)是无论如何都没办法组到这套题中的。

    不难看出, 第二道题的临界点是 diff = 20 。 小于等于 20 都可以将自身组到套题,增加一道即可,否则需要增加两个,并且自身不能组到当前套题。

    对于第三题来说:

    • 比如 1 20 40。 我可以在 20,40 之间加一个 30,这样 1,20,30 就可以组成一一套,自身(40)是无法组到这套题的。
    • 比如 1 20 60。 也是一样的,我可以在 20,60 之间加一个 30,自身(60)同样是没办法组到这套题中的。

    不难看出, 第三道题的临界点是 diff = 10 。 小于等于 10 都可以将自身组到套题,否则需要增加一个,并且自身不能组到当前套题。

    这就是所有的情况了。

    有的同学比较好奇,我是怎么思考的。 我是怎么保障不重不漏的。

    实际上,这道题就是一个决策树, 我画个决策树出来你就明白了。

    图中红色边框表示自身可以组成套题的一部分, 我也用文字进行了说明。#2 代表第二题, #3 代表第三题。

    从图中可以看出, 我已经考虑了所有情况。如果你能够像我一样画出这个决策图,我想你也不会漏的。当然我的解法并不一定是最优的,不过确实是一个非常好用,具有普适性的思维框架。

    需要特别注意的是,由于需要凑整, 因此你需要使得题目的总数是 3 的倍数向上取整。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    n = int(input())
    nums = list(map(int, input().split()))
    cnt = 0
    cur = 1
    nums.sort()
    for i in range(1, n):
    if cur == 3:
    cur = 1
    continue
    diff = nums[i] - nums[i - 1]
    if diff <= 10:
    cur += 1
    if 10 < diff <= 20:
    if cur == 1:
    cur = 3
    if cur == 2:
    cur = 1
    cnt += 1
    if diff > 20:
    if cur == 1:
    cnt += 2
    if cur == 2:
    cnt += 1
    cur = 1
    print(cnt + 3 - cur)

    复杂度分析

    • 时间复杂度:由于使用了排序, 因此时间复杂度为 $O(NlogN)$。(假设使用了基于比较的排序)
    • 空间复杂度:$O(1)$

    2. 异或

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    给定整数 m 以及 n 各数字 A1,A2,..An,将数列 A 中所有元素两两异或,共能得到 n(n-1)/2 个结果,请求出这些结果中大于 m 的有多少个。

    输入描述:
    第一行包含两个整数 n,m.

    第二行给出 n 个整数 A1,A2,...,An。

    数据范围

    对于 30%的数据,1 <= n, m <= 1000

    对于 100%的数据,1 <= n, m, Ai <= 10^5

    输出描述:
    输出仅包括一行,即所求的答案

    输入例子 1:
    3 10
    6 5 10

    输出例子 1:
    2

    前置知识

    • 异或运算的性质
    • 如何高效比较两个数的大小(从高位到低位)

    首先普及一下前置知识。 第一个是异或运算:

    异或的性质:两个数字异或的结果 a^b 是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是如果同一位的数字相同则为 0,不同则为 1

    异或的规律:

    1. 任何数和本身异或则为 0

    2. 任何数和 0 异或是本身

    3. 异或运算满足交换律,即: a ^ b ^ c = a ^ c ^ b

    同时建议大家去看下我总结的几道位运算的经典题目。 位运算系列

    其次要知道一个常识, 即比较两个数的大小, 我们是从高位到低位比较,这样才比较高效。

    比如:

    1
    2
    3
    123
    456
    1234

    这三个数比较大小, 为了方便我们先补 0 ,使得大家的位数保持一致。

    1
    2
    3
    0123
    0456
    1234

    先比较第一位,1 比较 0 大, 因此 1234 最大。再比较第二位, 4 比 1 大, 因此 456 大于 123,后面位不需要比较了。这其实就是剪枝的思想。

    有了这两个前提,我们来试下暴力法解决这道题。

    思路

    暴力法就是枚举 $N^2 / 2$ 中组合, 让其两两按位异或,将得到的结果和 m 进行比较, 如果比 m 大, 则计数器 + 1, 最后返回计数器的值即可。

    暴力的方法就如同题目描述的那样, 复杂度为 $N^2$。 一定过不了所有的测试用例, 不过大家实在没有好的解法的情况可以兜底。不管是牛客笔试还是实际的面试都是可行的。

    接下来,让我们来分析一下暴力为什么低效,以及如何选取数据结构和算法能够使得这个过程变得高效。 记住这句话, 几乎所有的优化都是基于这种思维产生的,除非你开启了上帝模式,直接看了答案。 只不过等你熟悉了之后,这个思维过程会非常短, 以至于变成条件反射, 你感觉不到有这个过程, 这就是有了题感。

    其实我刚才说的第二个前置知识就是我们优化的关键之一。

    我举个例子, 比如 3 和 5 按位异或。

    3 的二进制是 011, 5 的二进制是 101,

    1
    2
    011
    101

    按照我前面讲的异或知识, 不难得出其异或结构就是 110。

    上面我进行了三次异或:

    1. 第一次是最高位的 0 和 1 的异或, 结果为 1。
    2. 第二次是次高位的 1 和 0 的异或, 结果为 1。
    3. 第三次是最低位的 1 和 1 的异或, 结果为 0。

    那如何 m 是 1 呢? 我们有必要进行三次异或么? 实际上进行第一次异或的时候已经知道了一定比 m(m 是 1) 大。因为第一次异或的结构导致其最高位为 1,也就是说其最小也不过是 100,也就是 4,一定是大于 1 的。这就是剪枝, 这就是算法优化的关键。

    看出我一步一步的思维过程了么?所有的算法优化都需要经过类似的过程。

    因此我的算法就是从高位开始两两异或,并且异或的结果和 m 对应的二进制位比较大小。

    • 如果比 m 对应的二进制位大或者小,我们提前退出即可。
    • 如果相等,我们继续往低位移动重复这个过程。

    这虽然已经剪枝了,但是极端情况下,性能还是很差。比如:

    1
    2
    3
    m: 1111
    a: 1010
    b: 0101

    a,b 表示两个数,我们比较到最后才发现,其异或的值和 m 相等。因此极端情况,算法效率没有得到改进。

    这里我想到了一点,就是如果一个数 a 的前缀和另外一个数 b 的前缀是一样的,那么 c 和 a 或者 c 和 b 的异或的结构前缀部分一定也是一样的。比如:

    1
    2
    3
    a: 111000
    b: 111101
    c: 101011

    a 和 b 有共同的前缀 111,c 和 a 异或过了,当再次和 b 异或的时候,实际上前三位是没有必要进行的,这也是重复的部分。这就是算法可以优化的部分, 这就是剪枝。

    分析算法,找到算法的瓶颈部分,然后选取合适的数据结构和算法来优化到。 这句话很重要, 请务必记住。

    在这里,我们用的就是剪枝技术,关于剪枝,91 天学算法也有详细的介绍。

    回到前面讲到的算法瓶颈, 多个数是有共同前缀的, 前缀部分就是我们浪费的运算次数, 说到前缀大家应该可以想到前缀树。如果不熟悉前缀树的话,看下我的这个前缀树专题,里面的题全部手写一遍就差不多了。

    因此一种想法就是建立一个前缀树, 树的根就是最高的位。 由于题目要求异或, 我们知道异或是二进制的位运算, 因此这棵树要存二进制才比较好。

    反手看了一眼数据范围:m, n<=10^5 。 10^5 = 2 ^ x,我们的目标是求出 满足条件的 x 的 ceil(向上取整),因此 x 应该是 17。

    树的每一个节点存储的是:n 个数中,从根节点到当前节点形成的前缀有多少个是一样的,即多少个数的前缀是一样的。这样可以剪枝,提前退出的时候,就直接取出来用了。比如异或的结果是 1, m 当前二进制位是 0 ,那么这个前缀有 10 个,我都不需要比较了, 计数器直接 + 10 。

    我用 17 直接复杂度过高,目前仅仅通过了 70 % - 80 % 测试用例, 希望大家可以帮我找找毛病,我猜测是语言的锅。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

    class TreeNode:
    def __init__(self):
    self.cnt = 1
    self.children = [None] * 2
    def solve(num, i, cur):
    if cur == None or i == -1: return 0
    bit = (num >> i) & 1
    mbit = (m >> i) & 1
    if bit == 0 and mbit == 0:
    return (cur.children[1].cnt if cur.children[1] else 0) + solve(num, i - 1, cur.children[0])
    if bit == 1 and mbit == 0:
    return (cur.children[0].cnt if cur.children[0] else 0) + solve(num, i - 1, cur.children[1])
    if bit == 0 and mbit == 1:
    return solve(num, i - 1, cur.children[1])
    if bit == 1 and mbit == 1:
    return solve(num, i - 1, cur.children[0])

    def preprocess(nums, root):
    for num in nums:
    cur = root
    for i in range(16, -1, -1):
    bit = (num >> i) & 1
    if cur.children[bit]:
    cur.children[bit].cnt += 1
    else:
    cur.children[bit] = TreeNode()
    cur = cur.children[bit]

    n, m = map(int, input().split())
    nums = list(map(int, input().split()))
    root = TreeNode()
    preprocess(nums, root)
    ans = 0
    for num in nums:
    ans += solve(num, 16, root)
    print(ans // 2)

    复杂度分析

    • 时间复杂度:$O(N)$
    • 空间复杂度:$O(N)$

    3. 字典序

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    给定整数 n 和 m, 将 1 到 n 的这 n 个整数按字典序排列之后, 求其中的第 m 个数。
    对于 n=11, m=4, 按字典序排列依次为 1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9, 因此第 4 个数是 2.
    对于 n=200, m=25, 按字典序排列依次为 1 10 100 101 102 103 104 105 106 107 108 109 11 110 111 112 113 114 115 116 117 118 119 12 120 121 122 123 124 125 126 127 128 129 13 130 131 132 133 134 135 136 137 138 139 14 140 141 142 143 144 145 146 147 148 149 15 150 151 152 153 154 155 156 157 158 159 16 160 161 162 163 164 165 166 167 168 169 17 170 171 172 173 174 175 176 177 178 179 18 180 181 182 183 184 185 186 187 188 189 19 190 191 192 193 194 195 196 197 198 199 2 20 200 21 22 23 24 25 26 27 28 29 3 30 31 32 33 34 35 36 37 38 39 4 40 41 42 43 44 45 46 47 48 49 5 50 51 52 53 54 55 56 57 58 59 6 60 61 62 63 64 65 66 67 68 69 7 70 71 72 73 74 75 76 77 78 79 8 80 81 82 83 84 85 86 87 88 89 9 90 91 92 93 94 95 96 97 98 99 因此第 25 个数是 120…

    输入描述:
    输入仅包含两个整数 n 和 m。

    数据范围:

    对于 20%的数据, 1 <= m <= n <= 5 ;

    对于 80%的数据, 1 <= m <= n <= 10^7 ;

    对于 100%的数据, 1 <= m <= n <= 10^18.

    输出描述:
    输出仅包括一行, 即所求排列中的第 m 个数字.
    示例 1
    输入
    11 4
    输出
    2

    前置知识

    • 十叉树
    • 完全十叉树
    • 计算完全十叉树的节点个数
    • 字典树

    思路

    和上面题目思路一样, 先从暴力解法开始,尝试打开思路。

    暴力兜底的思路是直接生成一个长度为 n 的数组, 排序,选第 m 个即可。代码:

    1
    2
    3
    4
    n, m = map(int, input().split())

    nums = [str(i) for i in range(1, n + 1)]
    print(sorted(nums)[m - 1])

    复杂度分析

    • 时间复杂度:取决于排序算法, 不妨认为是 $O(NlogN)$
    • 空间复杂度: $O(N)$

    这种算法可以 pass 50 % case。

    上面算法低效的原因是开辟了 N 的空间,并对整 N 个 元素进行了排序。

    一种简单的优化方法是将排序换成堆,利用堆的特性求第 k 大的数, 这样时间复杂度可以减低到 $mlogN$。

    我们继续优化。实际上,你如果把字典序的排序结构画出来, 可以发现他本质就是一个十叉树,并且是一个完全十叉树。

    接下来,我带你继续分析。

    如图, 红色表示根节点。节点表示一个十进制数, 树的路径存储真正的数字,比如图上的 100,109 等。 这不就是上面讲的前缀树么?

    如图黄色部分, 表示字典序的顺序,注意箭头的方向。因此本质上,求字典序第 m 个数, 就是求这棵树的前序遍历的第 m 个节点。

    因此一种优化思路就是构建一颗这样的树,然后去遍历。 构建的复杂度是 $O(N)$,遍历的复杂度是 $O(M)$。因此这种算法的复杂度可以达到 $O(max(m, n))$ ,由于 n >= m,因此就是 $O(N)$。

    实际上, 这样的优化算法依然是无法 AC 全部测试用例的,会超内存限制。 因此我们的思路只能是不使用 N 的空间去构造树。想想也知道, 由于 N 最大可能为 10^18,一个数按照 4 字节来算, 那么这就有 400000000 字节,大约是 381 M,这是不能接受的。

    上面提到这道题就是一个完全十叉树的前序遍历,问题转化为求完全十叉树的前序遍历的第 m 个数。

    十叉树和二叉树没有本质不同, 我在二叉树专题部分, 也提到了 N 叉树都可以用二叉树来表示。

    对于一个节点来说,第 m 个节点:

    • 要么就是它本身
    • 要么其孩子节点中
    • 要么在其兄弟节点
    • 要么在兄弟节点的孩子节点中

    究竟在上面的四个部分的哪,取决于其孩子节点的个数。

    • count > m ,m 在其孩子节点中,我们需要深入到子节点。
    • count <= m ,m 不在自身和孩子节点, 我们应该跳过所有孩子节点,直接到兄弟节点。

    这本质就是一个递归的过程。

    需要注意的是,我们并不会真正的在树上走,因此上面提到的深入到子节点, 以及 跳过所有孩子节点,直接到兄弟节点如何操作呢?

    你仔细观察会发现: 如果当前节点的前缀是 x ,那么其第一个子节点(就是最小的子节点)是 x * 10,第二个就是 x * 10 + 1,以此类推。因此:

    • 深入到子节点就是 x * 10。
    • 跳过所有孩子节点,直接到兄弟节点就是 x + 1。

    ok,铺垫地差不多了。

    接下来,我们的重点是如何计算给定节点的孩子节点的个数

    这个过程和完全二叉树计算节点个数并无二致,这个算法的时间复杂度应该是 $O(logN*logN)$。 如果不会的同学,可以参考力扣原题: 222. 完全二叉树的节点个数 ,这是一个难度为中等的题目。

    因此这道题本身被划分为 hard,一点都不为过。

    这里简单说下,计算给定节点的孩子节点的个数的思路, 我的 91 天学算法里出过这道题。

    一种简单但非最优的思路是分别计算左右子树的深度。

    • 如果当前节点的左右子树高度相同,那么左子树是一个满二叉树,右子树是一个完全二叉树。
    • 否则(左边的高度大于右边),那么左子树是一个完全二叉树,右子树是一个满二叉树。

    如果是满二叉树,当前节点数 是 2 ^ depth,而对于完全二叉树,我们继续递归即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def countNodes(self, root):
    if not root:
    return 0
    ld = self.getDepth(root.left)
    rd = self.getDepth(root.right)
    if ld == rd:
    return 2 ** ld + self.countNodes(root.right)
    else:
    return 2 ** rd + self.countNodes(root.left)

    def getDepth(self, root):
    if not root:
    return 0
    return 1 + self.getDepth(root.left)

    复杂度分析

    • 时间复杂度:$O(logN * log N)$
    • 空间复杂度:$O(logN)$

    而这道题, 我们可以更简单和高效。

    比如我们要计算 1 号节点的子节点个数。

    • 它的孩子节点个数是 。。。
    • 它的孙子节点个数是 。。。
    • 。。。

    全部加起来即可。

    它的孩子节点个数是 20 - 10 = 10 。 也就是它的右边的兄弟节点的第一个子节点 减去 它的第一个子节点

    由于是完全十叉树,而不是满十叉树 。因此你需要考虑边界情况,比如题目的 n 是 15。 那么 1 的子节点个数就不是 20 - 10 = 10 了, 而是 15 - 10 + 1 = 16。

    其他也是类似的过程, 我们只要:

    • Go deeper and do the same thing

    或者:

    • Move to next neighbor and do the same thing

    不断重复,直到 m 降低到 0 。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    def count(c1, c2, n):
    steps = 0
    while c1 <= n:
    steps += min(n + 1, c2) - c1
    c1 *= 10
    c2 *= 10
    return steps
    def findKthNumber(n: int, k: int) -> int:
    cur = 1
    k = k - 1
    while k > 0:
    steps = count(cur, cur + 1, n)
    if steps <= k:
    cur += 1
    k -= steps
    else:
    cur *= 10
    k -= 1
    return cur
    n, m = map(int, input().split())
    print(findKthNumber(n, m))

    复杂度分析

    • 时间复杂度:$O(logM * log N)$
    • 空间复杂度:$O(1)$

    总结

    其中三道算法题从难度上来说,基本都是困难难度。从内容来看,基本都是力扣的换皮题,且都或多或少和树有关。如果大家一开始没有思路,建议大家先给出暴力的解法兜底,再画图或举简单例子打开思路。

    我也刷了很多字节的题了,还有一些难度比较大的题。如果你第一次做,那么需要你思考比较久才能想出来。加上面试紧张,很可能做不出来。这个时候就更需要你冷静分析,先暴力打底,慢慢优化。有时候即使给不了最优解,让面试官看出你的思路也很重要。 比如小兔的棋盘 想出最优解难度就不低,不过你可以先暴力 DFS 解决,再 DP 优化会慢慢帮你打开思路。有时候面试官也会引导你,给你提示, 加上你刚才“发挥不错”,说不定一下子就做出最优解了,这个我深有体会。

    另外要提醒大家的是, 刷题要适量,不要贪多。要完全理清一道题的来龙去脉。多问几个为什么。 这道题暴力法怎么做?暴力法哪有问题?怎么优化?为什么选了这个算法就可以优化?为什么这种算法要用这种数据结构来实现?

    扩展

    最近的力扣的周赛 1803. 统计异或值在范围内的数对有多少竟然也和这篇文章中的第二道题撞了。lucifer 我直接将第二道题的代码稍微改下就 AC 了。 这道题涉及了二进制前缀和的考点,看来大家还是比较喜欢考察的。一般二进制前缀树的题目难度基本都是困难。

    更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。

    关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

    ]]>
    - 算法 - 数据结构 + 算法 + 数组 滑动窗口 @@ -2977,19 +2989,19 @@ - 字节跳动的算法面试题是什么难度?(第二弹) - - /blog/2020/09/06/byte-dance-algo-ex-2017/ + 字节跳动的算法面试题是什么难度? + + /blog/2020/09/06/byte-dance-algo-ex/ - 由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    这套题一共 11 道题, 三道编程题, 八道问答题。本次给大家带来的就是这三道编程题。更多精彩内容,请期待我的搞定算法面试专栏。

    其中有一道题《异或》我没有通过所有的测试用例, 小伙伴可以找找茬,第一个找到并在公众号力扣加加留言的小伙伴奖励现金红包 10 元。

    1. 头条校招

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    头条的 2017 校招开始了!为了这次校招,我们组织了一个规模宏大的出题团队,每个出题人都出了一些有趣的题目,而我们现在想把这些题目组合成若干场考试出来,在选题之前,我们对题目进行了盲审,并定出了每道题的难度系统。一场考试包含 3 道开放性题目,假设他们的难度从小到大分别为 a,b,c,我们希望这 3 道题能满足下列条件:
    a<=b<=c
    b-a<=10
    c-b<=10
    所有出题人一共出了 n 道开放性题目。现在我们想把这 n 道题分布到若干场考试中(1 场或多场,每道题都必须使用且只能用一次),然而由于上述条件的限制,可能有一些考试没法凑够 3 道题,因此出题人就需要多出一些适当难度的题目来让每场考试都达到要求,然而我们出题已经出得很累了,你能计算出我们最少还需要再出几道题吗?

    输入描述:
    输入的第一行包含一个整数 n,表示目前已经出好的题目数量。

    第二行给出每道题目的难度系数 d1,d2,...,dn。

    数据范围

    对于 30%的数据,1 ≤ n,di ≤ 5;

    对于 100%的数据,1 ≤ n ≤ 10^5,1 ≤ di ≤ 100。

    在样例中,一种可行的方案是添加 2 个难度分别为 20 和 50 的题目,这样可以组合成两场考试:(20 20 23)和(35,40,50)。

    输出描述:
    输出只包括一行,即所求的答案。
    示例 1
    输入
    4
    20 35 23 40
    输出
    2

    思路

    这道题看起来很复杂, 你需要考虑很多的情况。,属于那种没有技术含量,但是考验编程能力的题目,需要思维足够严密。这种模拟的题目,就是题目让我干什么我干什么。 类似之前写的囚徒房间问题,约瑟夫环也是模拟,只不过模拟之后需要你剪枝优化。

    这道题的情况其实很多, 我们需要考虑每一套题中的难度情况, 而不需要考虑不同套题的难度情况。题目要求我们满足:a<=b<=c b-a<=10 c-b<=10,也就是题目难度从小到大排序之后,相邻的难度不能大于 10 。

    因此我们的思路就是先排序,之后从小到大遍历,如果满足相邻的难度不大于 10 ,则继续。如果不满足, 我们就只能让字节的老师出一道题使得满足条件。

    由于只需要比较同一套题目的难度,因此我的想法就是比较同一套题目的第二个和第一个,以及第三个和第二个的 diff

    • 如果 diff 小于 10,什么都不做,继续。
    • 如果 diff 大于 10,我们必须补充题目。

    这里有几个点需要注意。

    对于第二题来说:

    • 比如 1 30 40 这样的难度。 我可以在 1,30 之间加一个 21,这样 1,21,30 就可以组成一一套。
    • 比如 1 50 60 这样的难度。 我可以在 1,50 之间加 21, 41 才可以组成一套,自身(50)是无论如何都没办法组到这套题中的。

    不难看出, 第二道题的临界点是 diff = 20 。 小于等于 20 都可以将自身组到套题,增加一道即可,否则需要增加两个,并且自身不能组到当前套题。

    对于第三题来说:

    • 比如 1 20 40。 我可以在 20,40 之间加一个 30,这样 1,20,30 就可以组成一一套,自身(40)是无法组到这套题的。
    • 比如 1 20 60。 也是一样的,我可以在 20,60 之间加一个 30,自身(60)同样是没办法组到这套题中的。

    不难看出, 第三道题的临界点是 diff = 10 。 小于等于 10 都可以将自身组到套题,否则需要增加一个,并且自身不能组到当前套题。

    这就是所有的情况了。

    有的同学比较好奇,我是怎么思考的。 我是怎么保障不重不漏的。

    实际上,这道题就是一个决策树, 我画个决策树出来你就明白了。

    图中红色边框表示自身可以组成套题的一部分, 我也用文字进行了说明。#2 代表第二题, #3 代表第三题。

    从图中可以看出, 我已经考虑了所有情况。如果你能够像我一样画出这个决策图,我想你也不会漏的。当然我的解法并不一定是最优的,不过确实是一个非常好用,具有普适性的思维框架。

    需要特别注意的是,由于需要凑整, 因此你需要使得题目的总数是 3 的倍数向上取整。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    n = int(input())
    nums = list(map(int, input().split()))
    cnt = 0
    cur = 1
    nums.sort()
    for i in range(1, n):
    if cur == 3:
    cur = 1
    continue
    diff = nums[i] - nums[i - 1]
    if diff <= 10:
    cur += 1
    if 10 < diff <= 20:
    if cur == 1:
    cur = 3
    if cur == 2:
    cur = 1
    cnt += 1
    if diff > 20:
    if cur == 1:
    cnt += 2
    if cur == 2:
    cnt += 1
    cur = 1
    print(cnt + 3 - cur)

    复杂度分析

    • 时间复杂度:由于使用了排序, 因此时间复杂度为 $O(NlogN)$。(假设使用了基于比较的排序)
    • 空间复杂度:$O(1)$

    2. 异或

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    给定整数 m 以及 n 各数字 A1,A2,..An,将数列 A 中所有元素两两异或,共能得到 n(n-1)/2 个结果,请求出这些结果中大于 m 的有多少个。

    输入描述:
    第一行包含两个整数 n,m.

    第二行给出 n 个整数 A1,A2,...,An。

    数据范围

    对于 30%的数据,1 <= n, m <= 1000

    对于 100%的数据,1 <= n, m, Ai <= 10^5

    输出描述:
    输出仅包括一行,即所求的答案

    输入例子 1:
    3 10
    6 5 10

    输出例子 1:
    2

    前置知识

    • 异或运算的性质
    • 如何高效比较两个数的大小(从高位到低位)

    首先普及一下前置知识。 第一个是异或运算:

    异或的性质:两个数字异或的结果 a^b 是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是如果同一位的数字相同则为 0,不同则为 1

    异或的规律:

    1. 任何数和本身异或则为 0

    2. 任何数和 0 异或是本身

    3. 异或运算满足交换律,即: a ^ b ^ c = a ^ c ^ b

    同时建议大家去看下我总结的几道位运算的经典题目。 位运算系列

    其次要知道一个常识, 即比较两个数的大小, 我们是从高位到低位比较,这样才比较高效。

    比如:

    1
    2
    3
    123
    456
    1234

    这三个数比较大小, 为了方便我们先补 0 ,使得大家的位数保持一致。

    1
    2
    3
    0123
    0456
    1234

    先比较第一位,1 比较 0 大, 因此 1234 最大。再比较第二位, 4 比 1 大, 因此 456 大于 123,后面位不需要比较了。这其实就是剪枝的思想。

    有了这两个前提,我们来试下暴力法解决这道题。

    思路

    暴力法就是枚举 $N^2 / 2$ 中组合, 让其两两按位异或,将得到的结果和 m 进行比较, 如果比 m 大, 则计数器 + 1, 最后返回计数器的值即可。

    暴力的方法就如同题目描述的那样, 复杂度为 $N^2$。 一定过不了所有的测试用例, 不过大家实在没有好的解法的情况可以兜底。不管是牛客笔试还是实际的面试都是可行的。

    接下来,让我们来分析一下暴力为什么低效,以及如何选取数据结构和算法能够使得这个过程变得高效。 记住这句话, 几乎所有的优化都是基于这种思维产生的,除非你开启了上帝模式,直接看了答案。 只不过等你熟悉了之后,这个思维过程会非常短, 以至于变成条件反射, 你感觉不到有这个过程, 这就是有了题感。

    其实我刚才说的第二个前置知识就是我们优化的关键之一。

    我举个例子, 比如 3 和 5 按位异或。

    3 的二进制是 011, 5 的二进制是 101,

    1
    2
    011
    101

    按照我前面讲的异或知识, 不难得出其异或结构就是 110。

    上面我进行了三次异或:

    1. 第一次是最高位的 0 和 1 的异或, 结果为 1。
    2. 第二次是次高位的 1 和 0 的异或, 结果为 1。
    3. 第三次是最低位的 1 和 1 的异或, 结果为 0。

    那如何 m 是 1 呢? 我们有必要进行三次异或么? 实际上进行第一次异或的时候已经知道了一定比 m(m 是 1) 大。因为第一次异或的结构导致其最高位为 1,也就是说其最小也不过是 100,也就是 4,一定是大于 1 的。这就是剪枝, 这就是算法优化的关键。

    看出我一步一步的思维过程了么?所有的算法优化都需要经过类似的过程。

    因此我的算法就是从高位开始两两异或,并且异或的结果和 m 对应的二进制位比较大小。

    • 如果比 m 对应的二进制位大或者小,我们提前退出即可。
    • 如果相等,我们继续往低位移动重复这个过程。

    这虽然已经剪枝了,但是极端情况下,性能还是很差。比如:

    1
    2
    3
    m: 1111
    a: 1010
    b: 0101

    a,b 表示两个数,我们比较到最后才发现,其异或的值和 m 相等。因此极端情况,算法效率没有得到改进。

    这里我想到了一点,就是如果一个数 a 的前缀和另外一个数 b 的前缀是一样的,那么 c 和 a 或者 c 和 b 的异或的结构前缀部分一定也是一样的。比如:

    1
    2
    3
    a: 111000
    b: 111101
    c: 101011

    a 和 b 有共同的前缀 111,c 和 a 异或过了,当再次和 b 异或的时候,实际上前三位是没有必要进行的,这也是重复的部分。这就是算法可以优化的部分, 这就是剪枝。

    分析算法,找到算法的瓶颈部分,然后选取合适的数据结构和算法来优化到。 这句话很重要, 请务必记住。

    在这里,我们用的就是剪枝技术,关于剪枝,91 天学算法也有详细的介绍。

    回到前面讲到的算法瓶颈, 多个数是有共同前缀的, 前缀部分就是我们浪费的运算次数, 说到前缀大家应该可以想到前缀树。如果不熟悉前缀树的话,看下我的这个前缀树专题,里面的题全部手写一遍就差不多了。

    因此一种想法就是建立一个前缀树, 树的根就是最高的位。 由于题目要求异或, 我们知道异或是二进制的位运算, 因此这棵树要存二进制才比较好。

    反手看了一眼数据范围:m, n<=10^5 。 10^5 = 2 ^ x,我们的目标是求出 满足条件的 x 的 ceil(向上取整),因此 x 应该是 17。

    树的每一个节点存储的是:n 个数中,从根节点到当前节点形成的前缀有多少个是一样的,即多少个数的前缀是一样的。这样可以剪枝,提前退出的时候,就直接取出来用了。比如异或的结果是 1, m 当前二进制位是 0 ,那么这个前缀有 10 个,我都不需要比较了, 计数器直接 + 10 。

    我用 17 直接复杂度过高,目前仅仅通过了 70 % - 80 % 测试用例, 希望大家可以帮我找找毛病,我猜测是语言的锅。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

    class TreeNode:
    def __init__(self):
    self.cnt = 1
    self.children = [None] * 2
    def solve(num, i, cur):
    if cur == None or i == -1: return 0
    bit = (num >> i) & 1
    mbit = (m >> i) & 1
    if bit == 0 and mbit == 0:
    return (cur.children[1].cnt if cur.children[1] else 0) + solve(num, i - 1, cur.children[0])
    if bit == 1 and mbit == 0:
    return (cur.children[0].cnt if cur.children[0] else 0) + solve(num, i - 1, cur.children[1])
    if bit == 0 and mbit == 1:
    return solve(num, i - 1, cur.children[1])
    if bit == 1 and mbit == 1:
    return solve(num, i - 1, cur.children[0])

    def preprocess(nums, root):
    for num in nums:
    cur = root
    for i in range(16, -1, -1):
    bit = (num >> i) & 1
    if cur.children[bit]:
    cur.children[bit].cnt += 1
    else:
    cur.children[bit] = TreeNode()
    cur = cur.children[bit]

    n, m = map(int, input().split())
    nums = list(map(int, input().split()))
    root = TreeNode()
    preprocess(nums, root)
    ans = 0
    for num in nums:
    ans += solve(num, 16, root)
    print(ans // 2)

    复杂度分析

    • 时间复杂度:$O(N)$
    • 空间复杂度:$O(N)$

    3. 字典序

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    给定整数 n 和 m, 将 1 到 n 的这 n 个整数按字典序排列之后, 求其中的第 m 个数。
    对于 n=11, m=4, 按字典序排列依次为 1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9, 因此第 4 个数是 2.
    对于 n=200, m=25, 按字典序排列依次为 1 10 100 101 102 103 104 105 106 107 108 109 11 110 111 112 113 114 115 116 117 118 119 12 120 121 122 123 124 125 126 127 128 129 13 130 131 132 133 134 135 136 137 138 139 14 140 141 142 143 144 145 146 147 148 149 15 150 151 152 153 154 155 156 157 158 159 16 160 161 162 163 164 165 166 167 168 169 17 170 171 172 173 174 175 176 177 178 179 18 180 181 182 183 184 185 186 187 188 189 19 190 191 192 193 194 195 196 197 198 199 2 20 200 21 22 23 24 25 26 27 28 29 3 30 31 32 33 34 35 36 37 38 39 4 40 41 42 43 44 45 46 47 48 49 5 50 51 52 53 54 55 56 57 58 59 6 60 61 62 63 64 65 66 67 68 69 7 70 71 72 73 74 75 76 77 78 79 8 80 81 82 83 84 85 86 87 88 89 9 90 91 92 93 94 95 96 97 98 99 因此第 25 个数是 120…

    输入描述:
    输入仅包含两个整数 n 和 m。

    数据范围:

    对于 20%的数据, 1 <= m <= n <= 5 ;

    对于 80%的数据, 1 <= m <= n <= 10^7 ;

    对于 100%的数据, 1 <= m <= n <= 10^18.

    输出描述:
    输出仅包括一行, 即所求排列中的第 m 个数字.
    示例 1
    输入
    11 4
    输出
    2

    前置知识

    • 十叉树
    • 完全十叉树
    • 计算完全十叉树的节点个数
    • 字典树

    思路

    和上面题目思路一样, 先从暴力解法开始,尝试打开思路。

    暴力兜底的思路是直接生成一个长度为 n 的数组, 排序,选第 m 个即可。代码:

    1
    2
    3
    4
    n, m = map(int, input().split())

    nums = [str(i) for i in range(1, n + 1)]
    print(sorted(nums)[m - 1])

    复杂度分析

    • 时间复杂度:取决于排序算法, 不妨认为是 $O(NlogN)$
    • 空间复杂度: $O(N)$

    这种算法可以 pass 50 % case。

    上面算法低效的原因是开辟了 N 的空间,并对整 N 个 元素进行了排序。

    一种简单的优化方法是将排序换成堆,利用堆的特性求第 k 大的数, 这样时间复杂度可以减低到 $mlogN$。

    我们继续优化。实际上,你如果把字典序的排序结构画出来, 可以发现他本质就是一个十叉树,并且是一个完全十叉树。

    接下来,我带你继续分析。

    如图, 红色表示根节点。节点表示一个十进制数, 树的路径存储真正的数字,比如图上的 100,109 等。 这不就是上面讲的前缀树么?

    如图黄色部分, 表示字典序的顺序,注意箭头的方向。因此本质上,求字典序第 m 个数, 就是求这棵树的前序遍历的第 m 个节点。

    因此一种优化思路就是构建一颗这样的树,然后去遍历。 构建的复杂度是 $O(N)$,遍历的复杂度是 $O(M)$。因此这种算法的复杂度可以达到 $O(max(m, n))$ ,由于 n >= m,因此就是 $O(N)$。

    实际上, 这样的优化算法依然是无法 AC 全部测试用例的,会超内存限制。 因此我们的思路只能是不使用 N 的空间去构造树。想想也知道, 由于 N 最大可能为 10^18,一个数按照 4 字节来算, 那么这就有 400000000 字节,大约是 381 M,这是不能接受的。

    上面提到这道题就是一个完全十叉树的前序遍历,问题转化为求完全十叉树的前序遍历的第 m 个数。

    十叉树和二叉树没有本质不同, 我在二叉树专题部分, 也提到了 N 叉树都可以用二叉树来表示。

    对于一个节点来说,第 m 个节点:

    • 要么就是它本身
    • 要么其孩子节点中
    • 要么在其兄弟节点
    • 要么在兄弟节点的孩子节点中

    究竟在上面的四个部分的哪,取决于其孩子节点的个数。

    • count > m ,m 在其孩子节点中,我们需要深入到子节点。
    • count <= m ,m 不在自身和孩子节点, 我们应该跳过所有孩子节点,直接到兄弟节点。

    这本质就是一个递归的过程。

    需要注意的是,我们并不会真正的在树上走,因此上面提到的深入到子节点, 以及 跳过所有孩子节点,直接到兄弟节点如何操作呢?

    你仔细观察会发现: 如果当前节点的前缀是 x ,那么其第一个子节点(就是最小的子节点)是 x * 10,第二个就是 x * 10 + 1,以此类推。因此:

    • 深入到子节点就是 x * 10。
    • 跳过所有孩子节点,直接到兄弟节点就是 x + 1。

    ok,铺垫地差不多了。

    接下来,我们的重点是如何计算给定节点的孩子节点的个数

    这个过程和完全二叉树计算节点个数并无二致,这个算法的时间复杂度应该是 $O(logN*logN)$。 如果不会的同学,可以参考力扣原题: 222. 完全二叉树的节点个数 ,这是一个难度为中等的题目。

    因此这道题本身被划分为 hard,一点都不为过。

    这里简单说下,计算给定节点的孩子节点的个数的思路, 我的 91 天学算法里出过这道题。

    一种简单但非最优的思路是分别计算左右子树的深度。

    • 如果当前节点的左右子树高度相同,那么左子树是一个满二叉树,右子树是一个完全二叉树。
    • 否则(左边的高度大于右边),那么左子树是一个完全二叉树,右子树是一个满二叉树。

    如果是满二叉树,当前节点数 是 2 ^ depth,而对于完全二叉树,我们继续递归即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def countNodes(self, root):
    if not root:
    return 0
    ld = self.getDepth(root.left)
    rd = self.getDepth(root.right)
    if ld == rd:
    return 2 ** ld + self.countNodes(root.right)
    else:
    return 2 ** rd + self.countNodes(root.left)

    def getDepth(self, root):
    if not root:
    return 0
    return 1 + self.getDepth(root.left)

    复杂度分析

    • 时间复杂度:$O(logN * log N)$
    • 空间复杂度:$O(logN)$

    而这道题, 我们可以更简单和高效。

    比如我们要计算 1 号节点的子节点个数。

    • 它的孩子节点个数是 。。。
    • 它的孙子节点个数是 。。。
    • 。。。

    全部加起来即可。

    它的孩子节点个数是 20 - 10 = 10 。 也就是它的右边的兄弟节点的第一个子节点 减去 它的第一个子节点

    由于是完全十叉树,而不是满十叉树 。因此你需要考虑边界情况,比如题目的 n 是 15。 那么 1 的子节点个数就不是 20 - 10 = 10 了, 而是 15 - 10 + 1 = 16。

    其他也是类似的过程, 我们只要:

    • Go deeper and do the same thing

    或者:

    • Move to next neighbor and do the same thing

    不断重复,直到 m 降低到 0 。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    def count(c1, c2, n):
    steps = 0
    while c1 <= n:
    steps += min(n + 1, c2) - c1
    c1 *= 10
    c2 *= 10
    return steps
    def findKthNumber(n: int, k: int) -> int:
    cur = 1
    k = k - 1
    while k > 0:
    steps = count(cur, cur + 1, n)
    if steps <= k:
    cur += 1
    k -= steps
    else:
    cur *= 10
    k -= 1
    return cur
    n, m = map(int, input().split())
    print(findKthNumber(n, m))

    复杂度分析

    • 时间复杂度:$O(logM * log N)$
    • 空间复杂度:$O(1)$

    总结

    其中三道算法题从难度上来说,基本都是困难难度。从内容来看,基本都是力扣的换皮题,且都或多或少和树有关。如果大家一开始没有思路,建议大家先给出暴力的解法兜底,再画图或举简单例子打开思路。

    我也刷了很多字节的题了,还有一些难度比较大的题。如果你第一次做,那么需要你思考比较久才能想出来。加上面试紧张,很可能做不出来。这个时候就更需要你冷静分析,先暴力打底,慢慢优化。有时候即使给不了最优解,让面试官看出你的思路也很重要。 比如小兔的棋盘 想出最优解难度就不低,不过你可以先暴力 DFS 解决,再 DP 优化会慢慢帮你打开思路。有时候面试官也会引导你,给你提示, 加上你刚才“发挥不错”,说不定一下子就做出最优解了,这个我深有体会。

    另外要提醒大家的是, 刷题要适量,不要贪多。要完全理清一道题的来龙去脉。多问几个为什么。 这道题暴力法怎么做?暴力法哪有问题?怎么优化?为什么选了这个算法就可以优化?为什么这种算法要用这种数据结构来实现?

    扩展

    最近的力扣的周赛 1803. 统计异或值在范围内的数对有多少竟然也和这篇文章中的第二道题撞了。lucifer 我直接将第二道题的代码稍微改下就 AC 了。 这道题涉及了二进制前缀和的考点,看来大家还是比较喜欢考察的。一般二进制前缀树的题目难度基本都是困难。

    更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。

    关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

    ]]>
    + 由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    这套题一共四道题, 两道问答题, 两道编程题。

    其中一道问答题是 LeetCode 426 的原题,只不过题型变成了找茬(改错)。可惜的是 LeetCode 的 426 题是一个会员题目,没有会员的就看不来了。不过,剑指 Offer 正好也有这个题,并且力扣将剑指 Offer 全部的题目都 OJ 化了。 这道题大家可以去 https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof 提交答案。简单说一下这个题目的思路,我们只需要中序遍历即可得到一个有序的数列,同时在中序遍历过程中将 pre 和 cur 节点通过指针串起来即可。

    另一个问答是红包题目,这里不多说了。我们重点看一下剩下两个算法编程题。

    两个问答题由于不能在线判题,我没有做,只做了剩下两个编程题。

    球队比赛

    第一个编程题是一个球队比赛的题目。

    题目描述

    有三只球队,每只球队编号分别为球队 1,球队 2,球队 3,这三只球队一共需要进行 n 场比赛。现在已经踢完了 k 场比赛,每场比赛不能打平,踢赢一场比赛得一分,输了不得分不减分。已知球队 1 和球队 2 的比分相差 d1 分,球队 2 和球队 3 的比分相差 d2 分,每场比赛可以任意选择两只队伍进行。求如果打完最后的 (n-k) 场比赛,有没有可能三只球队的分数打平。

    思路

    假设球队 1,球队 2,球队 3 此时的胜利次数分别为 a,b,c,球队 1,球队 2,球队 3 总的胜利次数分别为 n1,n2,n3。

    我一开始的想法是只要保证 n1,n2,n3 相等且都小于等于 n / 3 即可。如果题目给了 n1,n2,n3 的值就直接:

    1
    print(n1 == n2 == n3 == n / 3)

    可是不仅 n1,n2,n3 没给, a,b,c 也没有给。

    实际上此时我们的信息仅仅是:

    1
    2
    3
    ① a + b + c = k
    ② a - b = d1 or b - a = d1
    ③ b - c = d2 or c - b = d2

    其中 k 和 d1,d2 是已知的。a ,b,c 是未知的。 也就是说我们需要枚举所有的 a,b,c 可能性,解方程求出合法的 a,b,c,并且 合法的 a,b,c 都小于等于 n / 3 即可。

    这个 a,b,c 的求解数学方程就是中学数学难度, 三个等式化简一下即可,具体见下方代码区域。

    • a 只需要再次赢得 n / 3 - a 次
    • b 只需要再次赢得 n / 3 - b 次
    • c 只需要再次赢得 n / 3 - c 次
    1
    2
    3
    n1 = a + n / 3 - a = n / 3
    n2 = b + (n / 3 - b) = n / 3
    n3 = c + (n / 3 - c) = n / 3

    代码(Python)

    牛客有点让人不爽, 需要 print 而不是 return

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    t = int(input())
    for i in range(t):
    n, k, d1, d2 = map(int, input().split(" "))
    if n % 3 != 0:
    print('no')
    continue
    abcs = []
    for r1 in [-1, 1]:
    for r2 in [-1, 1]:
    a = (k + 2 * r1 * d1 + r2 * d2) / 3
    b = (k + -1 * r1 * d1 + r2 * d2) / 3
    c = (k + -1 * r1 * d1 + -2 * r2 * d2) / 3
    a + r1
    if 0 <= a <= k and 0 <= b <= k and 0 <= c <= k and a.is_integer() and b.is_integer() and c.is_integer():
    abcs.append([a, b, c])
    flag = False
    for abc in abcs:
    if len(abc) > 0 and max(abc) <= n / 3:
    flag = True
    break
    if flag:
    print('yes')
    else:
    print('no')

    复杂度分析

    • 时间复杂度:$O(t)$
    • 空间复杂度:$O(t)$

    小结

    感觉这个难度也就是力扣中等水平吧,力扣也有一些数学等式转换的题目, 比如 494.target-sum

    转换字符串

    题目描述

    有一个仅包含’a’和’b’两种字符的字符串 s,长度为 n,每次操作可以把一个字符做一次转换(把一个’a’设置为’b’,或者把一个’b’置成’a’);但是操作的次数有上限 m,问在有限的操作数范围内,能够得到最大连续的相同字符的子串的长度是多少。

    思路

    看完题我就有种似曾相识的感觉。

    每次对妹子说出这句话的时候,她们都会觉得好假 ^_^

    不过这次是真的。 ”哦,不!每次都是真的“。 这道题其实就是我之前写的滑动窗口的一道题【1004. 最大连续 1 的个数 III】滑动窗口(Python3)的换皮题。 专题地址:https://github.com/azl397985856/leetcode/blob/master/thinkings/slide-window.md

    所以说,如果这道题你完全没有思路的话。说明:

    • 抽象能力不够。
    • 滑动窗口问题理解不到位。

    第二个问题可以看我上面贴的地址,仔细读读,并完成课后练习即可解决。

    第一个问题就比较困难了, 不过多看我的题解也可以慢慢提升的。比如:

    回归这道题。其实我们只需要稍微抽象一下, 就是一个纯算法题。 抽象的另外一个好处则是将很多不同的题目返璞归真,从而可以在茫茫题海中逃脱。这也是我开启《我是你的妈妈呀》 的原因之一。

    如果我们把 a 看成是 0 , b 看成是 1。或者将 b 看成 1, a 看成 0。不就抽象成了:

    1
    2
    3
    4

    给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 m 个值从 0 变成 1 。

    返回仅包含 1 的最长(连续)子数组的长度。

    这就是 力扣 1004. 最大连续 1 的个数 III 原题。

    因此实际上我们要求的是上面两种情况:

    1. a 表示 0, b 表示 1
    2. a 表示 1, b 表示 0

    的较大值。

    lucifer 小提示: 其实我们也可以仅仅考虑一种情况,比如 a 看成是 0 , b 看成是 1。这个时候, 我们操作变成了两种情况,0 变成 1 或者 1 变成 0,同时求解的也变成了最长连续 0 或者 最长连续 1 。 由于这种抽象操作起来更麻烦, 我们不考虑。

    问题得到了抽象就好解决了。我们只需要记录下加入窗口的是 0 还是 1:

    • 如果是 1,我们什么都不用做
    • 如果是 0,我们将 m 减 1

    相应地,我们需要记录移除窗口的是 0 还是 1:

    • 如果是 1,我们什么都不做
    • 如果是 0,说明加进来的时候就是 1,加进来的时候我们 m 减去了 1,这个时候我们再加 1。

    lucifer 小提示: 实际上题目中是求连续 a 或者 b 的长度。看到连续,大家也应该有滑动窗口的敏感度, 别管行不行, 想到总该有的。

    我们拿 A = [1, 1, 0, 1, 0, 1], m = 1 来说。看下算法的具体过程:

    lucifer 小提示: 左侧的数字表示此时窗口大小,黄色格子表示修补的墙,黑色方框表示的是窗口。

    这里我形象地将 0 看成是洞,1 看成是墙, 我们的目标就是补洞,使得连续的墙最长。

    每次碰到一个洞,我们都去不加选择地修补。由于 m 等于 1, 也就是说我们最多补一个洞。因此需要在修补超过一个洞的时候,我们需要调整窗口范围,使得窗口内最多修补一个墙。由于窗口表示的就是连续的墙(已有的或者修补的),因此最终我们返回窗口的最大值即可。

    由于下面的图窗口内有两个洞,这和”最多补一个洞“冲突, 我们需要收缩窗口使得满足“最多补一个洞”的先决条件。

    因此最大的窗口就是 max(2, 3, 4, …) = 4。

    lucifer 小提示: 可以看出我们不加选择地修补了所有的洞,并调整窗口,使得窗口内最多有 m 个修补的洞,因此窗口的最大值就是答案。然而实际上,我们并不需要真的”修补“(0 变成 1),而是仅仅修改 m 的值即可。

    我们先来看下抽象之后的其中一种情况的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Solution:
    def longestOnes(self, A: List[int], m: int) -> int:
    i = 0
    for j in range(len(A)):
    m -= 1 - A[j]
    if m < 0:
    m += 1 - A[i]
    i += 1
    return j - i + 1

    因此完整代码就是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def longestOnes(self, A: List[int], m: int) -> int:
    i = 0
    for j in range(len(A)):
    m -= 1 - A[j]
    if m < 0:
    m += 1 - A[i]
    i += 1
    return j - i + 1
    def longestAorB(self, A:List[int], m: int) -> int:
    return max(self.longestOnes(map(lambda x: 0 if x == 'a' else 1, A) ,m), self.longestOnes(map(lambda x: 1 if x == 'a' else 0, A),m))

    这里的两个 map 会生成两个不同的数组。 我只是为了方便大家理解才新建的两个数组, 实际上根本不需要,具体见后面的代码.

    代码(Python)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    i = 0
    n, m = map(int, input().split(" "))
    s = input()
    ans = 0
    k = m # 存一下,后面也要用这个初始值
    # 修补 b
    for j in range(n):
    m -= ord(s[j]) - ord('a')
    if m < 0:
    m += ord(s[i]) - ord('a')
    i += 1
    ans = j - i + 1
    i = 0
    # 修补 a
    for j in range(n):
    k += ord(s[j]) - ord('b')
    if k < 0:
    k -= ord(s[i]) - ord('b')
    i += 1
    print(max(ans, j - i + 1))

    复杂度分析

    • 时间复杂度:$O(N)$
    • 空间复杂度:$O(1)$

    小结

    这道题就是一道换了皮的力扣题,难度中等。如果你能将问题抽象,同时又懂得滑动窗口,那这道题就很容易。我看了题解区的参考答案, 内容比较混乱,不够清晰。这也是我写下这篇文章的原因之一。

    总结

    这一套字节跳动的题目一共四道,一道设计题,三道算法题。

    其中三道算法题从难度上来说,基本都是中等难度。从内容来看,基本都是力扣的换皮题。但是如果我不说他们是换皮题, 你们能发现么? 如果你可以的话,说明你的抽象能力已经略有小成了。如果看不出来也没有关系,关注我。 手把手扒皮给你们看,扒多了慢慢就会了。切记,不要盲目做题!如果你做了很多题, 这几道题还是看不出套路,说明你该缓缓,改变下刷题方式了。

    更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 36K+ star 啦。

    关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

    ]]>
    - 算法 - 数据结构 + 算法 + 数组 滑动窗口 @@ -3060,10 +3072,10 @@ - 算法 - 数据结构 + 算法 + 数组 动态规划 @@ -3426,12 +3438,12 @@ - 数学 - 数据结构 算法 + 数学 + BFS @@ -3481,16 +3493,16 @@ - 算法 - 数据结构 - 数组 + 算法 - 双指针 + 数组 Medium + 双指针 + @@ -3511,37 +3523,29 @@ - 一文看懂《最大子序列和问题》 - - /blog/2020/06/20/LSS/ + 穿上衣服我就不认识你了?来聊聊最长上升子序列 + + /blog/2020/06/20/LIS/ - 最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    题目描述

    求取数组中最大连续子序列和,例如给定数组为 A = [1, 3, -2, 4, -5], 则最大连续子序列和为 6,即 1 + 3 +(-2)+ 4 = 6。

    首先我们来明确一下题意。

    • 题目说的子数组是连续的
    • 题目只需要求和,不需要返回子数组的具体位置。
    • 数组中的元素是整数,可能是正数,负数和 0。
    • 子序列的最小长度为 1,不能为空。(这点很讨厌,不过题目就是这样~)

    比如:

    • 对于数组 [1, -2, 3, 5, -3, 2], 应该返回 3 + 5 = 8
    • 对于数组 [0, -2, 3, 5, -1, 2], 应该返回 3 + 5 + -1 + 2 = 9
    • 对于数组 [-9, -2, -3, -5, -3], 应该返回 -2(不能返回 0,即什么都不选)

    解法一 - 暴力法(超时法)

    一般情况下,先从暴力解分析,然后再进行一步步的优化。

    思路

    我们来试下最直接的方法,就是计算所有的子序列的和,然后取出最大值。
    记 Sum[i,….,j]为数组 A 中第 i 个元素到第 j 个元素的和,其中 0 <= i <= j < n,遍历所有可能的 Sum[i,….,j] 即可。

    枚举以 0,1,2…n-1 开头的所有子序列,对于每一个以其开头的子序列,都去枚举从当前开始到索引 n-1 的情况。

    这种做法的时间复杂度为 O(N^2), 空间复杂度为 O(1)。

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function LSS(list) {
    const len = list.length;
    let max = -Number.MAX_VALUE;
    let sum = 0;
    for (let i = 0; i < len; i++) {
    sum = 0;
    for (let j = i; j < len; j++) {
    sum += list[j];
    if (sum > max) {
    max = sum;
    }
    }
    }

    return max;
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class MaximumSubarrayPrefixSum {
    public int maxSubArray(int[] nums) {
    int len = nums.length;
    int maxSum = Integer.MIN_VALUE;
    int sum = 0;
    for (int i = 0; i < len; i++) {
    sum = 0;
    for (int j = i; j < len; j++) {
    sum += nums[j];
    maxSum = Math.max(maxSum, sum);
    }
    }
    return maxSum;
    }
    }

    Python 3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import sys
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    n = len(nums)
    maxSum = -sys.maxsize
    sum = 0
    for i in range(n):
    sum = 0
    for j in range(i, n):
    sum += nums[j]
    maxSum = max(maxSum, sum)

    return maxSum

    空间复杂度非常理想,但是时间复杂度有点高。怎么优化呢?我们来看下下一个解法。

    解法二 - 分治法

    思路

    先把数组平均分成左右两部分。

    此时有三种情况:

    • 最大子序列全部在数组左部分
    • 最大子序列全部在数组右部分
    • 最大子序列横跨左右数组

    对于前两种情况,我们相当于将原问题转化为了规模更小的同样问题。

    对于第三种情况,由于已知循环的一个端点(即中点),我们只需要进行一次循环,分别找出
    左边和右边的端点即可。

    因此我们可以每次都将数组分成左右两部分,然后分别计算上面三种情况的最大子序列和,取出最大的即可。

    举例说明,如下图:


    (by snowan)

    这种做法的时间复杂度为 O(N*logN), 空间复杂度为 O(1)。

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function helper(list, m, n) {
    if (m === n) return list[m];
    let sum = 0;
    let lmax = -Number.MAX_VALUE;
    let rmax = -Number.MAX_VALUE;
    const mid = ((n - m) >> 1) + m;
    const l = helper(list, m, mid);
    const r = helper(list, mid + 1, n);
    for (let i = mid; i >= m; i--) {
    sum += list[i];
    if (sum > lmax) lmax = sum;
    }

    sum = 0;

    for (let i = mid + 1; i <= n; i++) {
    sum += list[i];
    if (sum > rmax) rmax = sum;
    }

    return Math.max(l, r, lmax + rmax);
    }

    function LSS(list) {
    return helper(list, 0, list.length - 1);
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class MaximumSubarrayDivideConquer {
    public int maxSubArrayDividConquer(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    return helper(nums, 0, nums.length - 1);
    }
    private int helper(int[] nums, int l, int r) {
    if (l > r) return Integer.MIN_VALUE;
    int mid = (l + r) >>> 1;
    int left = helper(nums, l, mid - 1);
    int right = helper(nums, mid + 1, r);
    int leftMaxSum = 0;
    int sum = 0;
    // left surfix maxSum start from index mid - 1 to l
    for (int i = mid - 1; i >= l; i--) {
    sum += nums[i];
    leftMaxSum = Math.max(leftMaxSum, sum);
    }
    int rightMaxSum = 0;
    sum = 0;
    // right prefix maxSum start from index mid + 1 to r
    for (int i = mid + 1; i <= r; i++) {
    sum += nums[i];
    rightMaxSum = Math.max(sum, rightMaxSum);
    }
    // max(left, right, crossSum)
    return Math.max(leftMaxSum + rightMaxSum + nums[mid], Math.max(left, right));
    }
    }

    Python 3 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import sys
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    return self.helper(nums, 0, len(nums) - 1)
    def helper(self, nums, l, r):
    if l > r:
    return -sys.maxsize
    mid = (l + r) // 2
    left = self.helper(nums, l, mid - 1)
    right = self.helper(nums, mid + 1, r)
    left_suffix_max_sum = right_prefix_max_sum = 0
    sum = 0
    for i in reversed(range(l, mid)):
    sum += nums[i]
    left_suffix_max_sum = max(left_suffix_max_sum, sum)
    sum = 0
    for i in range(mid + 1, r + 1):
    sum += nums[i]
    right_prefix_max_sum = max(right_prefix_max_sum, sum)
    cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid]
    return max(cross_max_sum, left, right)

    解法三 - 动态规划

    思路

    上面的分治虽然将问题规模缩小了,但是分解的三个子问题有两个是规模变小的同样问题,而有一个不是。 那能不能将其全部拆解为规模更小的同样问题,并且能找出
    递推关系呢? 如果可以,那么我们就可以使用记忆化递归或者动态规划来解决了。

    不妨假设问题 Q(list, i) 表示 list 中以索引 i 结尾的情况下最大子序列和,那么原问题就转化为 max(Q(list, i)), 其中 i = 0,1,2…n-1 。

    明确了状态, 继续来看下递归关系,这里是 Q(list, i)和 Q(list, i - 1)的关系,即如何根据 Q(list, i - 1) 推导出 Q(list, i)。

    如果已知 Q(list, i - 1), 我们可以将问题分为两种情况,即以索引为 i 的元素终止,或者只有一个索引为 i 的元素。

    • 如果以索引为 i 的元素终止, 那么就是 Q(list, i - 1) + list[i]
    • 如果只有一个索引为 i 的元素,那么就是 list[i]

    分析到这里,递推关系就很明朗了,即Q(list, i) = Math.max(0, Q(list, i - 1)) + list[i]

    举例说明,如下图:

    53.maximum-sum-subarray-dp.png
    (by snowan)

    这种算法的时间复杂度 O(N), 空间复杂度为 O(1)

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function LSS(list) {
    const len = list.length;
    let max = list[0];
    for (let i = 1; i < len; i++) {
    list[i] = Math.max(0, list[i - 1]) + list[i];
    if (list[i] > max) max = list[i];
    }

    return max;
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class MaximumSubarrayDP {
    public int maxSubArray(int[] nums) {
    int currMaxSum = nums[0];
    int maxSum = nums[0];
    for (int i = 1; i < nums.length; i++) {
    currMaxSum = Math.max(currMaxSum + nums[i], nums[i]);
    maxSum = Math.max(maxSum, currMaxSum);
    }
    return maxSum;
    }
    }

    Python 3:

    1
    2
    3
    4
    5
    6
    7
    8
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    dp = [0] * len(nums)
    ans = dp[0] = nums[0]
    for i in range(1, len(nums)):
    dp[i] = max(nums[i], dp[i - 1] + nums[i])
    ans = max(ans, dp[i])
    return ans

    解法四 - 数学分析(前缀和)

    思路

    最后通过数学分析来看一下这个题目。

    定义函数 S(i) ,它的功能是计算以 0(包括 0)开始加到 i(包括 i)的值。那么 S(j) - S(i - 1) 就等于 从 i 开始(包括 i)加到 j(包括 j)的值。

    我们进一步分析,实际上我们只需要遍历一次计算出所有的 S(i), 其中 i 等于 0,1,2….,n-1。然后我们再减去之前的 S(k),其中 k 等于 0,1,i - 1,中的最小值即可。 因此我们需要
    用一个变量来维护这个最小值,还需要一个变量维护最大值。

    这种算法的时间复杂度 O(N), 空间复杂度为 O(1)。

    其实很多题目,都有这样的思想, 比如之前的《每日一题 - 电梯问题》。

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function LSS(list) {
    const len = list.length;
    let max = list[0];
    let min = 0;
    let sum = 0;
    for (let i = 0; i < len; i++) {
    sum += list[i];
    if (sum - min > max) max = sum - min;
    if (sum < min) {
    min = sum;
    }
    }

    return max;
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MaxSumSubarray {
    public int maxSubArray3(int[] nums) {
    int maxSum = nums[0];
    int sum = 0;
    int minSum = 0;
    for (int num : nums) {
    // prefix Sum
    sum += num;
    // update maxSum
    maxSum = Math.max(maxSum, sum - minSum);
    // update minSum
    minSum = Math.min(minSum, sum);
    }
    return maxSum;
    }
    }

    Python 3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    n = len(nums)
    maxSum = nums[0]
    minSum = sum = 0
    for i in range(n):
    sum += nums[i]
    maxSum = max(maxSum, sum - minSum)
    minSum = min(minSum, sum)

    return maxSum

    扩展

    如果题目变化为如下,问题该怎么解决呢?

    1
    求取数组拼接 k 次的最大连续子序列和,例如给定数组为 A = [1, 3, 4, -5] ,k = 2, 拼接会的数组为 [1, 3, 4, -5, 1, 3, 4, -5],那么最大连续子序列和为 11,即子数组 [1, 3, 4, -5, 1, 3, 4] 的和。

    直接将 A 拼接 k 次后转化为上面的问题的话空间会超出限制,空间复杂度为 $O(n * k)$。代码:

    1
    2
    3
    4
    5
    6
    7
    class Solution:
    def solve(self, A, k):
    A = A * k
    dp = [0] * len(A)
    for i in range(len(A)):
    dp[i] = max(A[i], dp[i - 1] + A[i])
    return max(dp)

    然而实际上,我们可以仅拼接两次,因为当拼接次数大于 2,那么之后只是无限循环罢了。这种算法空间复杂度可以降低到 $O(n)$。

    经过这样的处理,问题转化为求: A 拼接 min(2, k) 次后的最大子序和 + max(0, k-2) 次 A 的和(如果 A 的和小于 0,则不必加)。

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Solution:
    def solve(self, nums, k):
    if not nums or not k:
    return 0
    A = nums * min(2, k)
    dp = [0] * (len(A) + 1)
    for i in range(len(A)):
    dp[i] = max(A[i], dp[i - 1] + A[i])
    return max(dp) + max(0, sum(nums)) * max(0, (k - 2))

    总结

    我们使用四种方法解决了《最大子序列和问题》,并详细分析了各个解法的思路以及复杂度,相信下次你碰到相同或者类似的问题的时候也能够发散思维,做到一题多解,多题一解

    实际上,我们只是求出了最大的和,如果题目进一步要求出最大子序列和的子序列呢?如果要题目允许不连续呢? 我们又该如何思考和变通?如何将数组改成二维,求解最大矩阵和怎么计算?
    这些问题留给读者自己来思考。

    ]]>
    + 最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    300. 最长上升子序列

    题目地址

    https://leetcode-cn.com/problems/longest-increasing-subsequence

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    给定一个无序的整数数组,找到其中最长上升子序列的长度。

    示例:

    输入: [10,9,2,5,3,7,101,18]
    输出: 4
    解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
    说明:

    可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
    你算法的时间复杂度应该为 O(n2) 。
    进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

    思路

    美团和华为都考了这个题。

    题目的意思是让我们从给定数组中挑选若干数字,这些数字满足: 如果 i < j 则 nums[i] < nums[j]。问:一次可以挑选最多满足条件的数字是多少个。

    这种子序列求极值的题目,应该要考虑到贪心或者动态规划。这道题贪心是不可以的,我们考虑动态规划。

    按照动态规划定义状态的套路,我们有两种常见的定义状态的方式:

    • dp[i] : 以 i 结尾(一定包括 i)所能形成的最长上升子序列长度, 答案是 max(dp[i]),其中 i = 0,1,2, …, n - 1
    • dp[i] : 以 i 结尾(可能包括 i)所能形成的最长上升子序列长度,答案是 dp[-1] (-1 表示最后一个元素)

    容易看出第二种定义方式由于无需比较不同的 dp[i] 就可以获得答案,因此更加方便。但是想了下,状态转移方程会很不好写,因为 dp[i] 的末尾数字(最大的)可能是 任意 j < i 的位置。

    第一种定义方式虽然需要比较不同的 dp[i] 从而获得结果,但是我们可以在循环的时候顺便得出,对复杂度不会有影响,只是代码多了一点而已。因此我们选择第一种建模方式

    由于 dp[j] 中一定会包括 j,且以 j 结尾, 那么 nums[j] 一定是其所形成的序列中最大的元素,那么如果位于其后(意味着 i > j)的 nums[i] > nums[j],那么 nums[i] 一定能够融入 dp[j] 从而形成更大的序列,这个序列的长度是 dp[j] + 1。因此状态转移方程就有了:dp[i] = dp[j] + 1 (其中 i > j, nums[i] > nums[j])

    [10, 9, 2, 5, 3, 7, 101, 18] 为例,当我们计算到 dp[5]的时候,我们需要往回和 0,1,2,3,4 进行比较。

    具体的比较内容是:

    最后从三个中选一个最大的 + 1 赋给 dp[5]即可。

    记住这个状态转移方程,后面我们还会频繁用到。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
    n = len(nums)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    for i in range(n):
    for j in range(i):
    if nums[i] > nums[j]:
    dp[i] = max(dp[i], dp[j] + 1)
    ans = max(ans, dp[i])
    return ans

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    435. 无重叠区间

    题目地址

    https://leetcode-cn.com/problems/non-overlapping-intervals/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

    注意:

    可以认为区间的终点总是大于它的起点。
    区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
    示例 1:

    输入: [ [1,2], [2,3], [3,4], [1,3] ]

    输出: 1

    解释: 移除 [1,3] 后,剩下的区间没有重叠。
    示例 2:

    输入: [ [1,2], [1,2], [1,2] ]

    输出: 2

    解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
    示例 3:

    输入: [ [1,2], [2,3] ]

    输出: 0

    解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

    思路

    我们先来看下最终剩下的区间。由于剩下的区间都是不重叠的,因此剩下的相邻区间的后一个区间的开始时间一定是不小于前一个区间的结束时间的。 比如我们剩下的区间是[ [1,2], [2,3], [3,4] ]。就是第一个区间的 2 小于等于 第二个区间的 2,第二个区间的 3 小于等于第三个区间的 3。

    不难发现如果我们将前面区间的结束后面区间的开始结合起来看,其就是一个非严格递增序列。而我们的目标就是删除若干区间,从而剩下最长的非严格递增子序列。这不就是上面的题么?只不过上面是严格递增,这不重要,就是改个符号的事情。 上面的题你可以看成是删除了若干数字,然后剩下剩下最长的严格递增子序列这就是抽象的力量,这就是套路。

    如果对区间按照起点或者终点进行排序,那么就转化为上面的最长递增子序列问题了。和上面问题不同的是,由于是一个区间。因此实际上,我们是需要拿后面的开始时间前面的结束时间进行比较。

    而由于:

    • 题目求的是需要移除的区间,因此最后 return 的时候需要做一个转化。
    • 题目不是要求严格递增,而是可以相等,因此我们的判断条件要加上等号。

    这道题还有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。

    代码

    你看代码多像

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
    n = len(intervals)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    intervals.sort(key=lambda a: a[0])

    for i in range(len(intervals)):
    for j in range(i - 1, -1, -1):
    if intervals[i][0] >= intervals[j][1]:
    dp[i] = max(dp[i], dp[j] + 1)
    break # 由于是按照开始时间排序的, 因此可以剪枝

    return n - max(dp)

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    646. 最长数对链

    题目地址

    https://leetcode-cn.com/problems/maximum-length-of-pair-chain/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。

    现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。

    给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。

    示例 :

    输入: [[1,2], [2,3], [3,4]]
    输出: 2
    解释: 最长的数对链是 [1,2] -> [3,4]
    注意:

    给出数对的个数在 [1, 1000] 范围内。

    思路

    和上面的435. 无重叠区间是换皮题,唯一的区别这里又变成了严格增加。没关系,我们把等号去掉就行了。并且由于这道题求解的是最长的长度,因此转化也不需要了。

    当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。

    代码

    这代码更像了!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def findLongestChain(self, intervals: List[List[int]]) -> int:
    n = len(intervals)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    intervals.sort(key=lambda a: a[0])

    for i in range(len(intervals)):
    for j in range(i - 1, -1, -1):
    if intervals[i][0] > intervals[j][1]:
    dp[i] = max(dp[i], dp[j] + 1)
    break # 由于是按照开始时间排序的, 因此可以剪枝

    return max(dp)

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    452. 用最少数量的箭引爆气球

    题目地址

    https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

    一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

    Example:

    输入:
    [[10,16], [2,8], [1,6], [7,12]]

    输出:
    2

    解释:
    对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。

    思路

    把气球看成区间,几个箭可以全部射爆,意思就是有多少不重叠的区间。注意这里重叠的情况也可以射爆。这么一抽象,就和上面的646. 最长数对链一模一样了,不用我多说了吧?

    当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。

    代码

    代码像不像?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def findMinArrowShots(self, intervals: List[List[int]]) -> int:
    n = len(intervals)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    intervals.sort(key=lambda a: a[0])

    for i in range(len(intervals)):
    for j in range(i - 1, -1, -1):
    if intervals[i][0] > intervals[j][1]:
    dp[i] = max(dp[i], dp[j] + 1)
    break # 由于是按照开始时间排序的, 因此可以剪枝

    return max(dp)

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    优化

    大家想看效率高的,其实也不难。 LIS 也可以用 贪心 + 二分 达到不错的效率。代码如下:

    代码文字版如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Solution:
    def lengthOfLIS(self, A: List[int]) -> int:
    d = []
    for a in A:
    i = bisect.bisect_left(d, a)
    if i < len(d):
    d[i] = a
    elif not d or d[-1] < a:
    d.append(a)
    return len(d)

    如果求最长不递减子序列呢?

    我们只需要将最左插入改为最右插入即可。代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Solution:
    def lengthOfLIS(self, A: List[int]) -> int:
    d = []
    for a in A:
    # 这里改为最右
    i = bisect.bisect(d, a)
    if i < len(d):
    d[i] = a
    # 这里改为小于等号
    elif not d or d[-1] <= a:
    d.append(a)
    return len(d)

    最左插入和最右插入分不清的可以看看我的二分专题。

    也可以这么写,更简单一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def LIS(A):
    d = []
    for a in A:
    # 如果求要严格递增就改为最左插入 bisect_left 即可
    i = bisect.bisect(d, a)
    if i == len(d):
    d.append(a)
    elif d[i] != a:
    d[i] = a
    return len(d)

    More

    其他的我就不一一说了。

    比如 673. 最长递增子序列的个数 (滴滴面试题)。 不就是求出最长序列,之后再循环比对一次就可以得出答案了么?

    491. 递增子序列 由于需要找到所有的递增子序列,因此动态规划就不行了,妥妥回溯就行了,套一个模板就出来了。回溯的模板可以看我之前写的回溯专题

    最后推荐两道题大家练习一下,别看它们是 hard, 其实掌握了我这篇文章的内容一点都不难。

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Solution:
    def pileBox(self, box: List[List[int]]) -> int:
    box = sorted(box, key=sorted)
    n = len(box)
    dp = [0 if i == 0 else box[i - 1][2] for i in range(n + 1)]
    ans = max(dp)

    for i in range(1, n + 1):
    for j in range(i + 1, n + 1):
    if box[j - 1][0] > box[i - 1][0] and box[j - 1][1] > box[i - 1][1] and box[j - 1][2] > box[i - 1][2]:
    dp[j] = max(dp[j], dp[i] + box[j - 1][2])
    ans = max(ans , dp[j])
    return ans

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
    if not envelopes: return 0
    n = len(envelopes)
    dp = [1] * n
    envelopes.sort()
    for i in range(n):
    for j in range(i + 1, n):
    if envelopes[i][0] < envelopes[j][0] and envelopes[i][1] < envelopes[j][1]:
    dp[j] = max(dp[j], dp[i] + 1)
    return max(dp)

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def minDeletionSize(self, A):
    keep = 1
    m, n = len(A), len(A[0])
    dp = [1] * n
    for j in range(n):
    for k in range(j + 1, n):
    if all([A[i][k] >= A[i][j] for i in range(m)]):
    dp[k] = max(dp[k], dp[j] + 1)
    keep = max(keep, dp[k])
    return n - keep

    小任务:请尝试使用贪心在 NlogN 的时间内完成算法。(参考我上面的代码就行)

    由于这道题数据范围是 $10^5$,因此只能使用 $NlogN$ 的贪心才行。

    关于为什么 10 ^ 5 就必须使用 $NlogN$ 甚至更优的算法我在刷题技巧提过。更多复杂度速查可参考我的刷题插件,公众号《力扣加加》回复插件获取即可。

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Solution:
    def minOperations(self, target: List[int], A: List[int]) -> int:
    def LIS(A):
    d = []
    for a in A:
    i = bisect.bisect_left(d, a)
    if d and i < len(d):
    d[i] = a
    else:
    d.append(a)
    return len(d)
    B = []
    target = { t:i for i, t in enumerate(target)}
    for a in A:
    if a in target: B.append(target[a])
    return len(target) - LIS(B)

    不就是先排下序,然后求 scores 的最长上升子序列么?

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def bestTeamScore(self, scores: List[int], ages: List[int]) -> int:
    n = len(scores)
    persons = list(zip(ages, scores))
    persons.sort(key=lambda x : (x[0], x[1]))
    dp = [persons[i][1] for i in range(n)]
    for i in range(n):
    for j in range(i):
    if persons[i][1] >= persons[j][1]:
    dp[i] = max(dp[i], dp[j]+persons[i][1])
    return max(dp)

    再比如 这道题 无非就是加了一个条件,我们可以结合循环移位的技巧来做。

    关于循环移位算法西法在之前的文章 文科生都能看懂的循环移位算法 也做了详细讲解,不再赘述。

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def solve(self, nums):
    n = len(nums)
    ans = 1
    def LIS(A):
    d = []
    for a in A:
    i = bisect.bisect_left(d,a)
    if i == len(d): d.append(a)
    else: d[i] = a
    return len(d)
    nums += nums
    for i in range(n):
    ans = max(ans , LIS(nums[i:i+n]))
    return ans

    再再再比如 253 场周赛 Q4 压轴题 1964. 找出到每个位置为止最长的有效障碍赛跑路线 不就是求以每一个元素结尾的 LIS 么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Solution:
    def longestObstacleCourseAtEachPosition(self, obstacles: List[int]) -> List[int]:
    def LIS(A):
    d = []
    ans = []
    for a in A:
    i = bisect.bisect_right(d, a)
    if d and i < len(d):
    d[i] = a
    else:
    d.append(a)
    ans.append(i+1)
    return ans
    return LIS(obstacles)

    大家把我讲的思路搞懂,这几个题一写,还怕碰到类似的题不会么?只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 最长上升子序列就是一个非常经典的基础算法,把它彻底搞懂,再去面对出题人的各种换皮就不怕了。相反,如果你不去思考题目背后的逻辑,就会刷地很痛苦。题目稍微一变化你就不会了,这也是为什么很多人说刷了很多题,但是碰到新的题目还是不会做的原因之一。关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

    更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。

    ]]>
    - 算法 - - 数据结构 - - 数组 - LeetCode - 子序列 + 动态规划 - 数据结构 - - 算法 + 动态规划 LeetCode - 数组 + 最长上升子序列 @@ -3550,29 +3554,37 @@ - 穿上衣服我就不认识你了?来聊聊最长上升子序列 - - /blog/2020/06/20/LIS/ + 一文看懂《最大子序列和问题》 + + /blog/2020/06/20/LSS/ - 最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    300. 最长上升子序列

    题目地址

    https://leetcode-cn.com/problems/longest-increasing-subsequence

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    给定一个无序的整数数组,找到其中最长上升子序列的长度。

    示例:

    输入: [10,9,2,5,3,7,101,18]
    输出: 4
    解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
    说明:

    可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
    你算法的时间复杂度应该为 O(n2) 。
    进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

    思路

    美团和华为都考了这个题。

    题目的意思是让我们从给定数组中挑选若干数字,这些数字满足: 如果 i < j 则 nums[i] < nums[j]。问:一次可以挑选最多满足条件的数字是多少个。

    这种子序列求极值的题目,应该要考虑到贪心或者动态规划。这道题贪心是不可以的,我们考虑动态规划。

    按照动态规划定义状态的套路,我们有两种常见的定义状态的方式:

    • dp[i] : 以 i 结尾(一定包括 i)所能形成的最长上升子序列长度, 答案是 max(dp[i]),其中 i = 0,1,2, …, n - 1
    • dp[i] : 以 i 结尾(可能包括 i)所能形成的最长上升子序列长度,答案是 dp[-1] (-1 表示最后一个元素)

    容易看出第二种定义方式由于无需比较不同的 dp[i] 就可以获得答案,因此更加方便。但是想了下,状态转移方程会很不好写,因为 dp[i] 的末尾数字(最大的)可能是 任意 j < i 的位置。

    第一种定义方式虽然需要比较不同的 dp[i] 从而获得结果,但是我们可以在循环的时候顺便得出,对复杂度不会有影响,只是代码多了一点而已。因此我们选择第一种建模方式

    由于 dp[j] 中一定会包括 j,且以 j 结尾, 那么 nums[j] 一定是其所形成的序列中最大的元素,那么如果位于其后(意味着 i > j)的 nums[i] > nums[j],那么 nums[i] 一定能够融入 dp[j] 从而形成更大的序列,这个序列的长度是 dp[j] + 1。因此状态转移方程就有了:dp[i] = dp[j] + 1 (其中 i > j, nums[i] > nums[j])

    [10, 9, 2, 5, 3, 7, 101, 18] 为例,当我们计算到 dp[5]的时候,我们需要往回和 0,1,2,3,4 进行比较。

    具体的比较内容是:

    最后从三个中选一个最大的 + 1 赋给 dp[5]即可。

    记住这个状态转移方程,后面我们还会频繁用到。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
    n = len(nums)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    for i in range(n):
    for j in range(i):
    if nums[i] > nums[j]:
    dp[i] = max(dp[i], dp[j] + 1)
    ans = max(ans, dp[i])
    return ans

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    435. 无重叠区间

    题目地址

    https://leetcode-cn.com/problems/non-overlapping-intervals/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

    注意:

    可以认为区间的终点总是大于它的起点。
    区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
    示例 1:

    输入: [ [1,2], [2,3], [3,4], [1,3] ]

    输出: 1

    解释: 移除 [1,3] 后,剩下的区间没有重叠。
    示例 2:

    输入: [ [1,2], [1,2], [1,2] ]

    输出: 2

    解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
    示例 3:

    输入: [ [1,2], [2,3] ]

    输出: 0

    解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

    思路

    我们先来看下最终剩下的区间。由于剩下的区间都是不重叠的,因此剩下的相邻区间的后一个区间的开始时间一定是不小于前一个区间的结束时间的。 比如我们剩下的区间是[ [1,2], [2,3], [3,4] ]。就是第一个区间的 2 小于等于 第二个区间的 2,第二个区间的 3 小于等于第三个区间的 3。

    不难发现如果我们将前面区间的结束后面区间的开始结合起来看,其就是一个非严格递增序列。而我们的目标就是删除若干区间,从而剩下最长的非严格递增子序列。这不就是上面的题么?只不过上面是严格递增,这不重要,就是改个符号的事情。 上面的题你可以看成是删除了若干数字,然后剩下剩下最长的严格递增子序列这就是抽象的力量,这就是套路。

    如果对区间按照起点或者终点进行排序,那么就转化为上面的最长递增子序列问题了。和上面问题不同的是,由于是一个区间。因此实际上,我们是需要拿后面的开始时间前面的结束时间进行比较。

    而由于:

    • 题目求的是需要移除的区间,因此最后 return 的时候需要做一个转化。
    • 题目不是要求严格递增,而是可以相等,因此我们的判断条件要加上等号。

    这道题还有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。

    代码

    你看代码多像

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
    n = len(intervals)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    intervals.sort(key=lambda a: a[0])

    for i in range(len(intervals)):
    for j in range(i - 1, -1, -1):
    if intervals[i][0] >= intervals[j][1]:
    dp[i] = max(dp[i], dp[j] + 1)
    break # 由于是按照开始时间排序的, 因此可以剪枝

    return n - max(dp)

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    646. 最长数对链

    题目地址

    https://leetcode-cn.com/problems/maximum-length-of-pair-chain/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。

    现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。

    给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。

    示例 :

    输入: [[1,2], [2,3], [3,4]]
    输出: 2
    解释: 最长的数对链是 [1,2] -> [3,4]
    注意:

    给出数对的个数在 [1, 1000] 范围内。

    思路

    和上面的435. 无重叠区间是换皮题,唯一的区别这里又变成了严格增加。没关系,我们把等号去掉就行了。并且由于这道题求解的是最长的长度,因此转化也不需要了。

    当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。

    代码

    这代码更像了!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def findLongestChain(self, intervals: List[List[int]]) -> int:
    n = len(intervals)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    intervals.sort(key=lambda a: a[0])

    for i in range(len(intervals)):
    for j in range(i - 1, -1, -1):
    if intervals[i][0] > intervals[j][1]:
    dp[i] = max(dp[i], dp[j] + 1)
    break # 由于是按照开始时间排序的, 因此可以剪枝

    return max(dp)

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    452. 用最少数量的箭引爆气球

    题目地址

    https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

    一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

    Example:

    输入:
    [[10,16], [2,8], [1,6], [7,12]]

    输出:
    2

    解释:
    对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。

    思路

    把气球看成区间,几个箭可以全部射爆,意思就是有多少不重叠的区间。注意这里重叠的情况也可以射爆。这么一抽象,就和上面的646. 最长数对链一模一样了,不用我多说了吧?

    当然,这道题也有一种贪心的解法,其效率要比动态规划更好,但由于和本文的主题不一致,就不在这里讲了。

    代码

    代码像不像?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def findMinArrowShots(self, intervals: List[List[int]]) -> int:
    n = len(intervals)
    if n == 0: return 0
    dp = [1] * n
    ans = 1
    intervals.sort(key=lambda a: a[0])

    for i in range(len(intervals)):
    for j in range(i - 1, -1, -1):
    if intervals[i][0] > intervals[j][1]:
    dp[i] = max(dp[i], dp[j] + 1)
    break # 由于是按照开始时间排序的, 因此可以剪枝

    return max(dp)

    复杂度分析

    • 时间复杂度:$O(N ^ 2)$
    • 空间复杂度:$O(N)$

    优化

    大家想看效率高的,其实也不难。 LIS 也可以用 贪心 + 二分 达到不错的效率。代码如下:

    代码文字版如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Solution:
    def lengthOfLIS(self, A: List[int]) -> int:
    d = []
    for a in A:
    i = bisect.bisect_left(d, a)
    if i < len(d):
    d[i] = a
    elif not d or d[-1] < a:
    d.append(a)
    return len(d)

    如果求最长不递减子序列呢?

    我们只需要将最左插入改为最右插入即可。代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Solution:
    def lengthOfLIS(self, A: List[int]) -> int:
    d = []
    for a in A:
    # 这里改为最右
    i = bisect.bisect(d, a)
    if i < len(d):
    d[i] = a
    # 这里改为小于等号
    elif not d or d[-1] <= a:
    d.append(a)
    return len(d)

    最左插入和最右插入分不清的可以看看我的二分专题。

    也可以这么写,更简单一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def LIS(A):
    d = []
    for a in A:
    # 如果求要严格递增就改为最左插入 bisect_left 即可
    i = bisect.bisect(d, a)
    if i == len(d):
    d.append(a)
    elif d[i] != a:
    d[i] = a
    return len(d)

    More

    其他的我就不一一说了。

    比如 673. 最长递增子序列的个数 (滴滴面试题)。 不就是求出最长序列,之后再循环比对一次就可以得出答案了么?

    491. 递增子序列 由于需要找到所有的递增子序列,因此动态规划就不行了,妥妥回溯就行了,套一个模板就出来了。回溯的模板可以看我之前写的回溯专题

    最后推荐两道题大家练习一下,别看它们是 hard, 其实掌握了我这篇文章的内容一点都不难。

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Solution:
    def pileBox(self, box: List[List[int]]) -> int:
    box = sorted(box, key=sorted)
    n = len(box)
    dp = [0 if i == 0 else box[i - 1][2] for i in range(n + 1)]
    ans = max(dp)

    for i in range(1, n + 1):
    for j in range(i + 1, n + 1):
    if box[j - 1][0] > box[i - 1][0] and box[j - 1][1] > box[i - 1][1] and box[j - 1][2] > box[i - 1][2]:
    dp[j] = max(dp[j], dp[i] + box[j - 1][2])
    ans = max(ans , dp[j])
    return ans

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
    if not envelopes: return 0
    n = len(envelopes)
    dp = [1] * n
    envelopes.sort()
    for i in range(n):
    for j in range(i + 1, n):
    if envelopes[i][0] < envelopes[j][0] and envelopes[i][1] < envelopes[j][1]:
    dp[j] = max(dp[j], dp[i] + 1)
    return max(dp)

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def minDeletionSize(self, A):
    keep = 1
    m, n = len(A), len(A[0])
    dp = [1] * n
    for j in range(n):
    for k in range(j + 1, n):
    if all([A[i][k] >= A[i][j] for i in range(m)]):
    dp[k] = max(dp[k], dp[j] + 1)
    keep = max(keep, dp[k])
    return n - keep

    小任务:请尝试使用贪心在 NlogN 的时间内完成算法。(参考我上面的代码就行)

    由于这道题数据范围是 $10^5$,因此只能使用 $NlogN$ 的贪心才行。

    关于为什么 10 ^ 5 就必须使用 $NlogN$ 甚至更优的算法我在刷题技巧提过。更多复杂度速查可参考我的刷题插件,公众号《力扣加加》回复插件获取即可。

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Solution:
    def minOperations(self, target: List[int], A: List[int]) -> int:
    def LIS(A):
    d = []
    for a in A:
    i = bisect.bisect_left(d, a)
    if d and i < len(d):
    d[i] = a
    else:
    d.append(a)
    return len(d)
    B = []
    target = { t:i for i, t in enumerate(target)}
    for a in A:
    if a in target: B.append(target[a])
    return len(target) - LIS(B)

    不就是先排下序,然后求 scores 的最长上升子序列么?

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def bestTeamScore(self, scores: List[int], ages: List[int]) -> int:
    n = len(scores)
    persons = list(zip(ages, scores))
    persons.sort(key=lambda x : (x[0], x[1]))
    dp = [persons[i][1] for i in range(n)]
    for i in range(n):
    for j in range(i):
    if persons[i][1] >= persons[j][1]:
    dp[i] = max(dp[i], dp[j]+persons[i][1])
    return max(dp)

    再比如 这道题 无非就是加了一个条件,我们可以结合循环移位的技巧来做。

    关于循环移位算法西法在之前的文章 文科生都能看懂的循环移位算法 也做了详细讲解,不再赘述。

    参考代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution:
    def solve(self, nums):
    n = len(nums)
    ans = 1
    def LIS(A):
    d = []
    for a in A:
    i = bisect.bisect_left(d,a)
    if i == len(d): d.append(a)
    else: d[i] = a
    return len(d)
    nums += nums
    for i in range(n):
    ans = max(ans , LIS(nums[i:i+n]))
    return ans

    再再再比如 253 场周赛 Q4 压轴题 1964. 找出到每个位置为止最长的有效障碍赛跑路线 不就是求以每一个元素结尾的 LIS 么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Solution:
    def longestObstacleCourseAtEachPosition(self, obstacles: List[int]) -> List[int]:
    def LIS(A):
    d = []
    ans = []
    for a in A:
    i = bisect.bisect_right(d, a)
    if d and i < len(d):
    d[i] = a
    else:
    d.append(a)
    ans.append(i+1)
    return ans
    return LIS(obstacles)

    大家把我讲的思路搞懂,这几个题一写,还怕碰到类似的题不会么?只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 最长上升子序列就是一个非常经典的基础算法,把它彻底搞懂,再去面对出题人的各种换皮就不怕了。相反,如果你不去思考题目背后的逻辑,就会刷地很痛苦。题目稍微一变化你就不会了,这也是为什么很多人说刷了很多题,但是碰到新的题目还是不会做的原因之一。关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

    更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。

    ]]>
    + 最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    题目描述

    求取数组中最大连续子序列和,例如给定数组为 A = [1, 3, -2, 4, -5], 则最大连续子序列和为 6,即 1 + 3 +(-2)+ 4 = 6。

    首先我们来明确一下题意。

    • 题目说的子数组是连续的
    • 题目只需要求和,不需要返回子数组的具体位置。
    • 数组中的元素是整数,可能是正数,负数和 0。
    • 子序列的最小长度为 1,不能为空。(这点很讨厌,不过题目就是这样~)

    比如:

    • 对于数组 [1, -2, 3, 5, -3, 2], 应该返回 3 + 5 = 8
    • 对于数组 [0, -2, 3, 5, -1, 2], 应该返回 3 + 5 + -1 + 2 = 9
    • 对于数组 [-9, -2, -3, -5, -3], 应该返回 -2(不能返回 0,即什么都不选)

    解法一 - 暴力法(超时法)

    一般情况下,先从暴力解分析,然后再进行一步步的优化。

    思路

    我们来试下最直接的方法,就是计算所有的子序列的和,然后取出最大值。
    记 Sum[i,….,j]为数组 A 中第 i 个元素到第 j 个元素的和,其中 0 <= i <= j < n,遍历所有可能的 Sum[i,….,j] 即可。

    枚举以 0,1,2…n-1 开头的所有子序列,对于每一个以其开头的子序列,都去枚举从当前开始到索引 n-1 的情况。

    这种做法的时间复杂度为 O(N^2), 空间复杂度为 O(1)。

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function LSS(list) {
    const len = list.length;
    let max = -Number.MAX_VALUE;
    let sum = 0;
    for (let i = 0; i < len; i++) {
    sum = 0;
    for (let j = i; j < len; j++) {
    sum += list[j];
    if (sum > max) {
    max = sum;
    }
    }
    }

    return max;
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class MaximumSubarrayPrefixSum {
    public int maxSubArray(int[] nums) {
    int len = nums.length;
    int maxSum = Integer.MIN_VALUE;
    int sum = 0;
    for (int i = 0; i < len; i++) {
    sum = 0;
    for (int j = i; j < len; j++) {
    sum += nums[j];
    maxSum = Math.max(maxSum, sum);
    }
    }
    return maxSum;
    }
    }

    Python 3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import sys
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    n = len(nums)
    maxSum = -sys.maxsize
    sum = 0
    for i in range(n):
    sum = 0
    for j in range(i, n):
    sum += nums[j]
    maxSum = max(maxSum, sum)

    return maxSum

    空间复杂度非常理想,但是时间复杂度有点高。怎么优化呢?我们来看下下一个解法。

    解法二 - 分治法

    思路

    先把数组平均分成左右两部分。

    此时有三种情况:

    • 最大子序列全部在数组左部分
    • 最大子序列全部在数组右部分
    • 最大子序列横跨左右数组

    对于前两种情况,我们相当于将原问题转化为了规模更小的同样问题。

    对于第三种情况,由于已知循环的一个端点(即中点),我们只需要进行一次循环,分别找出
    左边和右边的端点即可。

    因此我们可以每次都将数组分成左右两部分,然后分别计算上面三种情况的最大子序列和,取出最大的即可。

    举例说明,如下图:


    (by snowan)

    这种做法的时间复杂度为 O(N*logN), 空间复杂度为 O(1)。

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function helper(list, m, n) {
    if (m === n) return list[m];
    let sum = 0;
    let lmax = -Number.MAX_VALUE;
    let rmax = -Number.MAX_VALUE;
    const mid = ((n - m) >> 1) + m;
    const l = helper(list, m, mid);
    const r = helper(list, mid + 1, n);
    for (let i = mid; i >= m; i--) {
    sum += list[i];
    if (sum > lmax) lmax = sum;
    }

    sum = 0;

    for (let i = mid + 1; i <= n; i++) {
    sum += list[i];
    if (sum > rmax) rmax = sum;
    }

    return Math.max(l, r, lmax + rmax);
    }

    function LSS(list) {
    return helper(list, 0, list.length - 1);
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class MaximumSubarrayDivideConquer {
    public int maxSubArrayDividConquer(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    return helper(nums, 0, nums.length - 1);
    }
    private int helper(int[] nums, int l, int r) {
    if (l > r) return Integer.MIN_VALUE;
    int mid = (l + r) >>> 1;
    int left = helper(nums, l, mid - 1);
    int right = helper(nums, mid + 1, r);
    int leftMaxSum = 0;
    int sum = 0;
    // left surfix maxSum start from index mid - 1 to l
    for (int i = mid - 1; i >= l; i--) {
    sum += nums[i];
    leftMaxSum = Math.max(leftMaxSum, sum);
    }
    int rightMaxSum = 0;
    sum = 0;
    // right prefix maxSum start from index mid + 1 to r
    for (int i = mid + 1; i <= r; i++) {
    sum += nums[i];
    rightMaxSum = Math.max(sum, rightMaxSum);
    }
    // max(left, right, crossSum)
    return Math.max(leftMaxSum + rightMaxSum + nums[mid], Math.max(left, right));
    }
    }

    Python 3 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import sys
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    return self.helper(nums, 0, len(nums) - 1)
    def helper(self, nums, l, r):
    if l > r:
    return -sys.maxsize
    mid = (l + r) // 2
    left = self.helper(nums, l, mid - 1)
    right = self.helper(nums, mid + 1, r)
    left_suffix_max_sum = right_prefix_max_sum = 0
    sum = 0
    for i in reversed(range(l, mid)):
    sum += nums[i]
    left_suffix_max_sum = max(left_suffix_max_sum, sum)
    sum = 0
    for i in range(mid + 1, r + 1):
    sum += nums[i]
    right_prefix_max_sum = max(right_prefix_max_sum, sum)
    cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid]
    return max(cross_max_sum, left, right)

    解法三 - 动态规划

    思路

    上面的分治虽然将问题规模缩小了,但是分解的三个子问题有两个是规模变小的同样问题,而有一个不是。 那能不能将其全部拆解为规模更小的同样问题,并且能找出
    递推关系呢? 如果可以,那么我们就可以使用记忆化递归或者动态规划来解决了。

    不妨假设问题 Q(list, i) 表示 list 中以索引 i 结尾的情况下最大子序列和,那么原问题就转化为 max(Q(list, i)), 其中 i = 0,1,2…n-1 。

    明确了状态, 继续来看下递归关系,这里是 Q(list, i)和 Q(list, i - 1)的关系,即如何根据 Q(list, i - 1) 推导出 Q(list, i)。

    如果已知 Q(list, i - 1), 我们可以将问题分为两种情况,即以索引为 i 的元素终止,或者只有一个索引为 i 的元素。

    • 如果以索引为 i 的元素终止, 那么就是 Q(list, i - 1) + list[i]
    • 如果只有一个索引为 i 的元素,那么就是 list[i]

    分析到这里,递推关系就很明朗了,即Q(list, i) = Math.max(0, Q(list, i - 1)) + list[i]

    举例说明,如下图:

    53.maximum-sum-subarray-dp.png
    (by snowan)

    这种算法的时间复杂度 O(N), 空间复杂度为 O(1)

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function LSS(list) {
    const len = list.length;
    let max = list[0];
    for (let i = 1; i < len; i++) {
    list[i] = Math.max(0, list[i - 1]) + list[i];
    if (list[i] > max) max = list[i];
    }

    return max;
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class MaximumSubarrayDP {
    public int maxSubArray(int[] nums) {
    int currMaxSum = nums[0];
    int maxSum = nums[0];
    for (int i = 1; i < nums.length; i++) {
    currMaxSum = Math.max(currMaxSum + nums[i], nums[i]);
    maxSum = Math.max(maxSum, currMaxSum);
    }
    return maxSum;
    }
    }

    Python 3:

    1
    2
    3
    4
    5
    6
    7
    8
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    dp = [0] * len(nums)
    ans = dp[0] = nums[0]
    for i in range(1, len(nums)):
    dp[i] = max(nums[i], dp[i - 1] + nums[i])
    ans = max(ans, dp[i])
    return ans

    解法四 - 数学分析(前缀和)

    思路

    最后通过数学分析来看一下这个题目。

    定义函数 S(i) ,它的功能是计算以 0(包括 0)开始加到 i(包括 i)的值。那么 S(j) - S(i - 1) 就等于 从 i 开始(包括 i)加到 j(包括 j)的值。

    我们进一步分析,实际上我们只需要遍历一次计算出所有的 S(i), 其中 i 等于 0,1,2….,n-1。然后我们再减去之前的 S(k),其中 k 等于 0,1,i - 1,中的最小值即可。 因此我们需要
    用一个变量来维护这个最小值,还需要一个变量维护最大值。

    这种算法的时间复杂度 O(N), 空间复杂度为 O(1)。

    其实很多题目,都有这样的思想, 比如之前的《每日一题 - 电梯问题》。

    代码

    JavaScript:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function LSS(list) {
    const len = list.length;
    let max = list[0];
    let min = 0;
    let sum = 0;
    for (let i = 0; i < len; i++) {
    sum += list[i];
    if (sum - min > max) max = sum - min;
    if (sum < min) {
    min = sum;
    }
    }

    return max;
    }

    Java:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MaxSumSubarray {
    public int maxSubArray3(int[] nums) {
    int maxSum = nums[0];
    int sum = 0;
    int minSum = 0;
    for (int num : nums) {
    // prefix Sum
    sum += num;
    // update maxSum
    maxSum = Math.max(maxSum, sum - minSum);
    // update minSum
    minSum = Math.min(minSum, sum);
    }
    return maxSum;
    }
    }

    Python 3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
    n = len(nums)
    maxSum = nums[0]
    minSum = sum = 0
    for i in range(n):
    sum += nums[i]
    maxSum = max(maxSum, sum - minSum)
    minSum = min(minSum, sum)

    return maxSum

    扩展

    如果题目变化为如下,问题该怎么解决呢?

    1
    求取数组拼接 k 次的最大连续子序列和,例如给定数组为 A = [1, 3, 4, -5] ,k = 2, 拼接会的数组为 [1, 3, 4, -5, 1, 3, 4, -5],那么最大连续子序列和为 11,即子数组 [1, 3, 4, -5, 1, 3, 4] 的和。

    直接将 A 拼接 k 次后转化为上面的问题的话空间会超出限制,空间复杂度为 $O(n * k)$。代码:

    1
    2
    3
    4
    5
    6
    7
    class Solution:
    def solve(self, A, k):
    A = A * k
    dp = [0] * len(A)
    for i in range(len(A)):
    dp[i] = max(A[i], dp[i - 1] + A[i])
    return max(dp)

    然而实际上,我们可以仅拼接两次,因为当拼接次数大于 2,那么之后只是无限循环罢了。这种算法空间复杂度可以降低到 $O(n)$。

    经过这样的处理,问题转化为求: A 拼接 min(2, k) 次后的最大子序和 + max(0, k-2) 次 A 的和(如果 A 的和小于 0,则不必加)。

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Solution:
    def solve(self, nums, k):
    if not nums or not k:
    return 0
    A = nums * min(2, k)
    dp = [0] * (len(A) + 1)
    for i in range(len(A)):
    dp[i] = max(A[i], dp[i - 1] + A[i])
    return max(dp) + max(0, sum(nums)) * max(0, (k - 2))

    总结

    我们使用四种方法解决了《最大子序列和问题》,并详细分析了各个解法的思路以及复杂度,相信下次你碰到相同或者类似的问题的时候也能够发散思维,做到一题多解,多题一解

    实际上,我们只是求出了最大的和,如果题目进一步要求出最大子序列和的子序列呢?如果要题目允许不连续呢? 我们又该如何思考和变通?如何将数组改成二维,求解最大矩阵和怎么计算?
    这些问题留给读者自己来思考。

    ]]>
    + 数据结构 + + 算法 + + 数组 + LeetCode - 动态规划 + 子序列 - 动态规划 + 数据结构 + + 算法 LeetCode - 最长上升子序列 + 数组 @@ -3778,33 +3790,29 @@ - 阿里面试题:如何寻找「两个数组」的中位数? - - /blog/2020/06/13/leetcode-median/ + 力扣加加闪亮登场~ + + /blog/2020/06/13/leetcode-pp/ - 一个数组的中位数很容易求,那两个数组呢?

    题目地址(4. 寻找两个正序数组的中位数)

    https://leetcode-cn.com/problems/median-of-two-sorted-arrays/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。

    请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

    你可以假设 nums1 和 nums2 不会同时为空。

     

    示例 1:

    nums1 = [1, 3]
    nums2 = [2]

    则中位数是 2.0
    示例 2:

    nums1 = [1, 2]
    nums2 = [3, 4]

    则中位数是 (2 + 3)/2 = 2.5

    前置知识

    • 中位数
    • 分治法
    • 二分查找

    公司

    • 阿里
    • 百度
    • 腾讯

    暴力法

    思路

    首先了解一下 Median 的概念,一个数组中 median 就是把数组分成左右等分的中位数。

    如下图:

    中位数概念

    知道了概念,我们先来看下如何使用暴力法来解决。

    试了一下,暴力解法也是可以被 Leetcode Accept 的。

    暴力解主要是要 merge 两个排序的数组(A,B)成一个排序的数组。

    用两个pointer(i,j)i 从数组A起始位置开始,即i=0开始,j 从数组B起始位置, 即j=0开始.
    一一比较 A[i] 和 B[j],

    1. 如果A[i] <= B[j], 则把A[i] 放入新的数组中,i 往后移一位,即 i+1.
    2. 如果A[i] > B[j], 则把B[j] 放入新的数组中,j 往后移一位,即 j+1.
    3. 重复步骤#1 和 #2,直到i移到A最后,或者j移到B最后。
    4. 如果j移动到B数组最后,那么直接把剩下的所有A依次放入新的数组中.
    5. 如果i移动到A数组最后,那么直接把剩下的所有B依次放入新的数组中.

    整个过程类似归并排序的合并过程

    Merge 的过程如下图。
    暴力法图解

    时间复杂度和空间复杂度都是O(m+n), 不符合题中给出O(log(m+n))时间复杂度的要求。

    代码

    代码支持: Java,JS:

    Java Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    class MedianTwoSortedArrayBruteForce {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int[] newArr = mergeTwoSortedArray(nums1, nums2);
    int n = newArr.length;
    if (n % 2 == 0) {
    // even
    return (double) (newArr[n / 2] + newArr[n / 2 - 1]) / 2;
    } else {
    // odd
    return (double) newArr[n / 2];
    }
    }
    private int[] mergeTwoSortedArray(int[] nums1, int[] nums2) {
    int m = nums1.length;
    int n = nums2.length;
    int[] res = new int[m + n];
    int i = 0;
    int j = 0;
    int idx = 0;
    while (i < m && j < n) {
    if (nums1[i] <= nums2[j]) {
    res[idx++] = nums1[i++];
    } else {
    res[idx++] = nums2[j++];
    }
    }
    while (i < m) {
    res[idx++] = nums1[i++];
    }
    while (j < n) {
    res[idx++] = nums2[j++];
    }
    return res;
    }
    }

    JS Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * @param {number[]} nums1
    * @param {number[]} nums2
    * @return {number}
    */
    var findMedianSortedArrays = function (nums1, nums2) {
    // 归并排序
    const merged = [];
    let i = 0;
    let j = 0;
    while (i < nums1.length && j < nums2.length) {
    if (nums1[i] < nums2[j]) {
    merged.push(nums1[i++]);
    } else {
    merged.push(nums2[j++]);
    }
    }
    while (i < nums1.length) {
    merged.push(nums1[i++]);
    }
    while (j < nums2.length) {
    merged.push(nums2[j++]);
    }

    const { length } = merged;
    return length % 2 === 1
    ? merged[Math.floor(length / 2)]
    : (merged[length / 2] + merged[length / 2 - 1]) / 2;
    };

    复杂度分析

    • 时间复杂度:$O(max(m, n))$
    • 空间复杂度:$O(m + n)$

    二分查找

    思路

    如果我们把上一种方法的最终结果拿出来单独看的话,不难发现最终结果就是 nums1 和 nums 两个数组交错形成的新数组,也就是说 nums1 和 nums2 的相对位置并不会发生变化,这是本题的关键信息之一。

    为了方便描述,不妨假设最终分割后,数组 nums1 左侧部分是 A,数组 nums2 左侧部分是 B。由于题中给出的数组都是排好序的,在排好序的数组中查找很容易想到可以用二分查找(Binary Search)·, 这里对数组长度小的做二分以减少时间复杂度。对较小的数组做二分可行的原因在于如果一个数组的索引 i 确定了,那么另一个数组的索引位置 j 也是确定的,因为 (i+1) + (j+1) 等于 (m + n + 1) / 2,其中 m 是数组 A 的长度, n 是数组 B 的长度。具体来说,我们可以保证数组 A 和 数组 B 做 partition 之后,len(Aleft)+len(Bleft)=(m+n+1)/2

    接下来需要特别注意四个指针:leftp1, rightp1, leftp2, rightp2,分别表示 A 数组分割点,A 数组分割点右侧数,B 数组分割点,B 数组分割点右侧数。不过这里有两个临界点需要特殊处理:

    • 如果分割点左侧没有数,即分割点索引是 0,那么其左侧应该设置为无限小。
    • 如果分割点右侧没有数,即分割点索引是数组长度-1,那么其左侧应该设置为无限大。

    如果我们二分之后满足:leftp1 < rightp2 and leftp2 < rightp1,那么说明分割是正确的,直接返回max(leftp1, leftp2)+min(rightp1, rightp2) 即可。否则,说明分割无效,我们需要调整分割点。

    如何调整呢?实际上只需要判断 leftp1 > rightp2 的大小关系即可。如果 leftp1 > rightp2,那么说明 leftp1 太大了,我们可以通过缩小上界来降低 leftp1,否则我们需要扩大下界。

    核心代码:

    1
    2
    3
    4
    if leftp1 > rightp2:
    hi = mid1 - 1
    else:
    lo = mid1 + 1

    上面的调整上下界的代码是建立在对数组 nums1 进行二分的基础上的,如果我们对数组 nums2 进行二分,那么相应地需要改为:

    1
    2
    3
    4
    if leftp2 > rightp1:
    hi = mid2 - 1
    else:
    lo = mid2 + 1

    下面我们通过一个具体的例子来说明。

    比如对数组 A 的做 partition 的位置是区间[0,m]

    如图:
    详细算法图解

    下图给出几种不同情况的例子(注意但左边或者右边没有元素的时候,左边用INF_MIN,右边用INF_MAX表示左右的元素:

    实例解析

    下图给出具体做的 partition 解题的例子步骤,

    更详细的实例解析

    这个算法关键在于:

    1. 要 partition 两个排好序的数组成左右两等份,partition 需要满足len(Aleft)+len(Bleft)=(m+n+1)/2 - m是数组A的长度, n是数组B的长度
    2. 且 partition 后 A 左边最大(maxLeftA), A 右边最小(minRightA), B 左边最大(maxLeftB), B 右边最小(minRightB) 满足
      (maxLeftA <= minRightB && maxLeftB <= minRightA)

    关键点分析

    • 有序数组容易想到二分查找
    • 对小的数组进行二分可降低时间复杂度
    • 根据 leftp1,rightp2,leftp2 和 rightp1 的大小关系确定结束点和收缩方向

    代码

    代码支持:JS,CPP, Python3,

    JS Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 二分解法
    * @param {number[]} nums1
    * @param {number[]} nums2
    * @return {number}
    */
    var findMedianSortedArrays = function (nums1, nums2) {
    // make sure to do binary search for shorten array
    if (nums1.length > nums2.length) {
    [nums1, nums2] = [nums2, nums1];
    }
    const m = nums1.length;
    const n = nums2.length;
    let low = 0;
    let high = m;
    while (low <= high) {
    const i = low + Math.floor((high - low) / 2);
    const j = Math.floor((m + n + 1) / 2) - i;

    const maxLeftA = i === 0 ? -Infinity : nums1[i - 1];
    const minRightA = i === m ? Infinity : nums1[i];
    const maxLeftB = j === 0 ? -Infinity : nums2[j - 1];
    const minRightB = j === n ? Infinity : nums2[j];

    if (maxLeftA <= minRightB && minRightA >= maxLeftB) {
    return (m + n) % 2 === 1
    ? Math.max(maxLeftA, maxLeftB)
    : (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2;
    } else if (maxLeftA > minRightB) {
    high = i - 1;
    } else {
    low = low + 1;
    }
    }
    };

    Java Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    class MedianSortedTwoArrayBinarySearch {
    public static double findMedianSortedArraysBinarySearch(int[] nums1, int[] nums2) {
    // do binary search for shorter length array, make sure time complexity log(min(m,n)).
    if (nums1.length > nums2.length) {
    return findMedianSortedArraysBinarySearch(nums2, nums1);
    }
    int m = nums1.length;
    int n = nums2.length;
    int lo = 0;
    int hi = m;
    while (lo <= hi) {
    // partition A position i
    int i = lo + (hi - lo) / 2;
    // partition B position j
    int j = (m + n + 1) / 2 - i;

    int maxLeftA = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
    int minRightA = i == m ? Integer.MAX_VALUE : nums1[i];

    int maxLeftB = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
    int minRightB = j == n ? Integer.MAX_VALUE : nums2[j];

    if (maxLeftA <= minRightB && maxLeftB <= minRightA) {
    // total length is even
    if ((m + n) % 2 == 0) {
    return (double) (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2;
    } else {
    // total length is odd
    return (double) Math.max(maxLeftA, maxLeftB);
    }
    } else if (maxLeftA > minRightB) {
    // binary search left half
    hi = i - 1;
    } else {
    // binary search right half
    lo = i + 1;
    }
    }
    return 0.0;
    }
    }

    CPP Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Solution {
    public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    if (nums1.size() > nums2.size()) swap(nums1, nums2);
    int M = nums1.size(), N = nums2.size(), L = 0, R = M, K = (M + N + 1) / 2;
    while (true) {
    int i = (L + R) / 2, j = K - i;
    if (i < M && nums2[j - 1] > nums1[i]) L = i + 1;
    else if (i > L && nums1[i - 1] > nums2[j]) R = i - 1;
    else {
    int maxLeft = max(i ? nums1[i - 1] : INT_MIN, j ? nums2[j - 1] : INT_MIN);
    if ((M + N) % 2) return maxLeft;
    int minRight = min(i == M ? INT_MAX : nums1[i], j == N ? INT_MAX : nums2[j]);
    return (maxLeft + minRight) / 2.0;
    }
    }
    }
    };

    Python3 Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
    N = len(nums1)
    M = len(nums2)
    if N > M:
    return self.findMedianSortedArrays(nums2, nums1)

    lo = 0
    hi = N
    combined = N + M

    while lo <= hi:
    mid1 = lo + hi >> 1
    mid2 = ((combined + 1) >> 1) - mid1

    leftp1 = -float("inf") if mid1 == 0 else nums1[mid1 - 1]
    rightp1 = float("inf") if mid1 == N else nums1[mid1]

    leftp2 = -float("inf") if mid2 == 0 else nums2[mid2 - 1]
    rightp2 = float("inf") if mid2 == M else nums2[mid2]

    # Check if the partition is valid for the case of
    if leftp1 <= rightp2 and leftp2 <= rightp1:
    if combined % 2 == 0:
    return (max(leftp1, leftp2)+min(rightp1, rightp2)) / 2.0

    return max(leftp1, leftp2)
    else:
    if leftp1 > rightp2:
    hi = mid1 - 1
    else:
    lo = mid1 + 1
    return -1

    复杂度分析

    • 时间复杂度:$O(log(min(m, n)))$
    • 空间复杂度:$O(log(min(m, n)))$

    大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。
    大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

    ]]>
    + 力扣加加,一个努力做西湖区最好的算法题解的团队。

    1
    2
    脑洞前端:你已经长大了,是时候自己写题解了。
    力扣加加:。。。

    独立的原因是让公众号的定位更加清晰,喜欢我的题解的朋友可以关注力扣加加,喜欢我的前端架构剖析,徒手造框架的朋友可以关注脑洞前端。当然如果你同时关注两个,那我会感动到哭。

    虽然是新的公众号,但是我的初心不变,依然是力求用清晰直白的方式还原解题过程,努力做西湖区最好的算法题解。最后感谢大家一路以来的支持,我一定不负众望,越做越好。

    规划

    预计力扣加加会推出五个板块。

    91 算法

    通过在 91 天的集中训练,帮助大家摆脱困境,征服算法。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。我会在结束之后将讲义和题解放到公众号号

    (仓库部分内容)

    算法题解

    就是对力扣中的题目进行讲解,尤其是经典的题目。并且尽量从多个角度思路,帮助大家纵向打开解题思路。目前差不多有几百的题解了。

    专题讲解

    对于共性比较强的,我会抽离成专题形式,帮助大家横向打开解题思路。

    (之后会陆续加入更多专题)

    视频题解

    对于一些题目采用视频的方式可能更加直观方便,之后还会考虑直播。

    刷题工具

    初步规划了两个小工具,之后出测试版本会第一时间告诉大家,如果你对开发小工具感兴趣,也可以点击公众号的联系我来和我取得联系。

    (力扣加加刷题小助手)

    悬赏令

    大家在日常生活或是在刷题过程中有时一定会产生一些疑问,百度谷歌翻来覆去的找也找不到心中所期待的答案,因此,大家可以通过力扣加加来将这些疑问提出来,不定期从大家的问题中挑选出比较经典的几个问题来供大家一起讨论,大致的问题类型如下:

    • 日常生活中有个问题想用数据结构+算法解决,但苦于思路不清晰
    • 问题本身比较纠结,网上众说纷纭,找不到明确的答案
    • 脑洞问题等等

    (比如这种问题)

    被抽中的问题对应的朋友及问题回答出色的朋友可以获得准备的小礼物哦!

    最后,每周我们会根据公众号后台的阅读量及分享次数最多的小伙伴给予现金奖励奖励哦~。分享最多 18.88 红包,阅读最多的 16.66 红包。

    不是第一也没有关系,我们会从分享排名 2-10撒 名的小伙伴中随机抽取2个 8.88 元红包,阅读排名 2-10 名的小伙伴中随机抽取2个 6.88 元红包,

    每周日下午,我们会对本周的数据进行统计,并随机抽取幸运的小伙伴,并对中奖结果在力扣加加公众号进行公布,中奖的小伙伴请主动联系我哦。领奖时间截止到每周日的 24:00。

    关注我

    公众号

    点关注,不迷路。如果再给 ➕ 个星标就更棒啦!

    关注加加,星标加加~

    官网

    ]]>
    - 算法 - - 二分法 + 力扣加加 - 数学 - 数据结构 算法 - 中位数 + 算法提高班 - 二分法 + 力扣加加 @@ -3822,14 +3830,14 @@ - 算法 - 数据结构 - BFS + 算法 hashtable + BFS + @@ -3848,16 +3856,18 @@ - 力扣加加闪亮登场~ - - /blog/2020/06/13/leetcode-pp/ + 阿里面试题:如何寻找「两个数组」的中位数? + + /blog/2020/06/13/leetcode-median/ - 力扣加加,一个努力做西湖区最好的算法题解的团队。

    1
    2
    脑洞前端:你已经长大了,是时候自己写题解了。
    力扣加加:。。。

    独立的原因是让公众号的定位更加清晰,喜欢我的题解的朋友可以关注力扣加加,喜欢我的前端架构剖析,徒手造框架的朋友可以关注脑洞前端。当然如果你同时关注两个,那我会感动到哭。

    虽然是新的公众号,但是我的初心不变,依然是力求用清晰直白的方式还原解题过程,努力做西湖区最好的算法题解。最后感谢大家一路以来的支持,我一定不负众望,越做越好。

    规划

    预计力扣加加会推出五个板块。

    91 算法

    通过在 91 天的集中训练,帮助大家摆脱困境,征服算法。我们会帮助大家规划学习路线,91 天见证不一样的自己。群里会有专门的资深算法竞赛大佬坐阵解答大家的问题和疑问,并且会对前一天的题目进行讲解。我会在结束之后将讲义和题解放到公众号号

    (仓库部分内容)

    算法题解

    就是对力扣中的题目进行讲解,尤其是经典的题目。并且尽量从多个角度思路,帮助大家纵向打开解题思路。目前差不多有几百的题解了。

    专题讲解

    对于共性比较强的,我会抽离成专题形式,帮助大家横向打开解题思路。

    (之后会陆续加入更多专题)

    视频题解

    对于一些题目采用视频的方式可能更加直观方便,之后还会考虑直播。

    刷题工具

    初步规划了两个小工具,之后出测试版本会第一时间告诉大家,如果你对开发小工具感兴趣,也可以点击公众号的联系我来和我取得联系。

    (力扣加加刷题小助手)

    悬赏令

    大家在日常生活或是在刷题过程中有时一定会产生一些疑问,百度谷歌翻来覆去的找也找不到心中所期待的答案,因此,大家可以通过力扣加加来将这些疑问提出来,不定期从大家的问题中挑选出比较经典的几个问题来供大家一起讨论,大致的问题类型如下:

    • 日常生活中有个问题想用数据结构+算法解决,但苦于思路不清晰
    • 问题本身比较纠结,网上众说纷纭,找不到明确的答案
    • 脑洞问题等等

    (比如这种问题)

    被抽中的问题对应的朋友及问题回答出色的朋友可以获得准备的小礼物哦!

    最后,每周我们会根据公众号后台的阅读量及分享次数最多的小伙伴给予现金奖励奖励哦~。分享最多 18.88 红包,阅读最多的 16.66 红包。

    不是第一也没有关系,我们会从分享排名 2-10撒 名的小伙伴中随机抽取2个 8.88 元红包,阅读排名 2-10 名的小伙伴中随机抽取2个 6.88 元红包,

    每周日下午,我们会对本周的数据进行统计,并随机抽取幸运的小伙伴,并对中奖结果在力扣加加公众号进行公布,中奖的小伙伴请主动联系我哦。领奖时间截止到每周日的 24:00。

    关注我

    公众号

    点关注,不迷路。如果再给 ➕ 个星标就更棒啦!

    关注加加,星标加加~

    官网

    ]]>
    + 一个数组的中位数很容易求,那两个数组呢?

    题目地址(4. 寻找两个正序数组的中位数)

    https://leetcode-cn.com/problems/median-of-two-sorted-arrays/

    题目描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。

    请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

    你可以假设 nums1 和 nums2 不会同时为空。

     

    示例 1:

    nums1 = [1, 3]
    nums2 = [2]

    则中位数是 2.0
    示例 2:

    nums1 = [1, 2]
    nums2 = [3, 4]

    则中位数是 (2 + 3)/2 = 2.5

    前置知识

    • 中位数
    • 分治法
    • 二分查找

    公司

    • 阿里
    • 百度
    • 腾讯

    暴力法

    思路

    首先了解一下 Median 的概念,一个数组中 median 就是把数组分成左右等分的中位数。

    如下图:

    中位数概念

    知道了概念,我们先来看下如何使用暴力法来解决。

    试了一下,暴力解法也是可以被 Leetcode Accept 的。

    暴力解主要是要 merge 两个排序的数组(A,B)成一个排序的数组。

    用两个pointer(i,j)i 从数组A起始位置开始,即i=0开始,j 从数组B起始位置, 即j=0开始.
    一一比较 A[i] 和 B[j],

    1. 如果A[i] <= B[j], 则把A[i] 放入新的数组中,i 往后移一位,即 i+1.
    2. 如果A[i] > B[j], 则把B[j] 放入新的数组中,j 往后移一位,即 j+1.
    3. 重复步骤#1 和 #2,直到i移到A最后,或者j移到B最后。
    4. 如果j移动到B数组最后,那么直接把剩下的所有A依次放入新的数组中.
    5. 如果i移动到A数组最后,那么直接把剩下的所有B依次放入新的数组中.

    整个过程类似归并排序的合并过程

    Merge 的过程如下图。
    暴力法图解

    时间复杂度和空间复杂度都是O(m+n), 不符合题中给出O(log(m+n))时间复杂度的要求。

    代码

    代码支持: Java,JS:

    Java Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    class MedianTwoSortedArrayBruteForce {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int[] newArr = mergeTwoSortedArray(nums1, nums2);
    int n = newArr.length;
    if (n % 2 == 0) {
    // even
    return (double) (newArr[n / 2] + newArr[n / 2 - 1]) / 2;
    } else {
    // odd
    return (double) newArr[n / 2];
    }
    }
    private int[] mergeTwoSortedArray(int[] nums1, int[] nums2) {
    int m = nums1.length;
    int n = nums2.length;
    int[] res = new int[m + n];
    int i = 0;
    int j = 0;
    int idx = 0;
    while (i < m && j < n) {
    if (nums1[i] <= nums2[j]) {
    res[idx++] = nums1[i++];
    } else {
    res[idx++] = nums2[j++];
    }
    }
    while (i < m) {
    res[idx++] = nums1[i++];
    }
    while (j < n) {
    res[idx++] = nums2[j++];
    }
    return res;
    }
    }

    JS Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * @param {number[]} nums1
    * @param {number[]} nums2
    * @return {number}
    */
    var findMedianSortedArrays = function (nums1, nums2) {
    // 归并排序
    const merged = [];
    let i = 0;
    let j = 0;
    while (i < nums1.length && j < nums2.length) {
    if (nums1[i] < nums2[j]) {
    merged.push(nums1[i++]);
    } else {
    merged.push(nums2[j++]);
    }
    }
    while (i < nums1.length) {
    merged.push(nums1[i++]);
    }
    while (j < nums2.length) {
    merged.push(nums2[j++]);
    }

    const { length } = merged;
    return length % 2 === 1
    ? merged[Math.floor(length / 2)]
    : (merged[length / 2] + merged[length / 2 - 1]) / 2;
    };

    复杂度分析

    • 时间复杂度:$O(max(m, n))$
    • 空间复杂度:$O(m + n)$

    二分查找

    思路

    如果我们把上一种方法的最终结果拿出来单独看的话,不难发现最终结果就是 nums1 和 nums 两个数组交错形成的新数组,也就是说 nums1 和 nums2 的相对位置并不会发生变化,这是本题的关键信息之一。

    为了方便描述,不妨假设最终分割后,数组 nums1 左侧部分是 A,数组 nums2 左侧部分是 B。由于题中给出的数组都是排好序的,在排好序的数组中查找很容易想到可以用二分查找(Binary Search)·, 这里对数组长度小的做二分以减少时间复杂度。对较小的数组做二分可行的原因在于如果一个数组的索引 i 确定了,那么另一个数组的索引位置 j 也是确定的,因为 (i+1) + (j+1) 等于 (m + n + 1) / 2,其中 m 是数组 A 的长度, n 是数组 B 的长度。具体来说,我们可以保证数组 A 和 数组 B 做 partition 之后,len(Aleft)+len(Bleft)=(m+n+1)/2

    接下来需要特别注意四个指针:leftp1, rightp1, leftp2, rightp2,分别表示 A 数组分割点,A 数组分割点右侧数,B 数组分割点,B 数组分割点右侧数。不过这里有两个临界点需要特殊处理:

    • 如果分割点左侧没有数,即分割点索引是 0,那么其左侧应该设置为无限小。
    • 如果分割点右侧没有数,即分割点索引是数组长度-1,那么其左侧应该设置为无限大。

    如果我们二分之后满足:leftp1 < rightp2 and leftp2 < rightp1,那么说明分割是正确的,直接返回max(leftp1, leftp2)+min(rightp1, rightp2) 即可。否则,说明分割无效,我们需要调整分割点。

    如何调整呢?实际上只需要判断 leftp1 > rightp2 的大小关系即可。如果 leftp1 > rightp2,那么说明 leftp1 太大了,我们可以通过缩小上界来降低 leftp1,否则我们需要扩大下界。

    核心代码:

    1
    2
    3
    4
    if leftp1 > rightp2:
    hi = mid1 - 1
    else:
    lo = mid1 + 1

    上面的调整上下界的代码是建立在对数组 nums1 进行二分的基础上的,如果我们对数组 nums2 进行二分,那么相应地需要改为:

    1
    2
    3
    4
    if leftp2 > rightp1:
    hi = mid2 - 1
    else:
    lo = mid2 + 1

    下面我们通过一个具体的例子来说明。

    比如对数组 A 的做 partition 的位置是区间[0,m]

    如图:
    详细算法图解

    下图给出几种不同情况的例子(注意但左边或者右边没有元素的时候,左边用INF_MIN,右边用INF_MAX表示左右的元素:

    实例解析

    下图给出具体做的 partition 解题的例子步骤,

    更详细的实例解析

    这个算法关键在于:

    1. 要 partition 两个排好序的数组成左右两等份,partition 需要满足len(Aleft)+len(Bleft)=(m+n+1)/2 - m是数组A的长度, n是数组B的长度
    2. 且 partition 后 A 左边最大(maxLeftA), A 右边最小(minRightA), B 左边最大(maxLeftB), B 右边最小(minRightB) 满足
      (maxLeftA <= minRightB && maxLeftB <= minRightA)

    关键点分析

    • 有序数组容易想到二分查找
    • 对小的数组进行二分可降低时间复杂度
    • 根据 leftp1,rightp2,leftp2 和 rightp1 的大小关系确定结束点和收缩方向

    代码

    代码支持:JS,CPP, Python3,

    JS Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 二分解法
    * @param {number[]} nums1
    * @param {number[]} nums2
    * @return {number}
    */
    var findMedianSortedArrays = function (nums1, nums2) {
    // make sure to do binary search for shorten array
    if (nums1.length > nums2.length) {
    [nums1, nums2] = [nums2, nums1];
    }
    const m = nums1.length;
    const n = nums2.length;
    let low = 0;
    let high = m;
    while (low <= high) {
    const i = low + Math.floor((high - low) / 2);
    const j = Math.floor((m + n + 1) / 2) - i;

    const maxLeftA = i === 0 ? -Infinity : nums1[i - 1];
    const minRightA = i === m ? Infinity : nums1[i];
    const maxLeftB = j === 0 ? -Infinity : nums2[j - 1];
    const minRightB = j === n ? Infinity : nums2[j];

    if (maxLeftA <= minRightB && minRightA >= maxLeftB) {
    return (m + n) % 2 === 1
    ? Math.max(maxLeftA, maxLeftB)
    : (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2;
    } else if (maxLeftA > minRightB) {
    high = i - 1;
    } else {
    low = low + 1;
    }
    }
    };

    Java Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    class MedianSortedTwoArrayBinarySearch {
    public static double findMedianSortedArraysBinarySearch(int[] nums1, int[] nums2) {
    // do binary search for shorter length array, make sure time complexity log(min(m,n)).
    if (nums1.length > nums2.length) {
    return findMedianSortedArraysBinarySearch(nums2, nums1);
    }
    int m = nums1.length;
    int n = nums2.length;
    int lo = 0;
    int hi = m;
    while (lo <= hi) {
    // partition A position i
    int i = lo + (hi - lo) / 2;
    // partition B position j
    int j = (m + n + 1) / 2 - i;

    int maxLeftA = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
    int minRightA = i == m ? Integer.MAX_VALUE : nums1[i];

    int maxLeftB = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
    int minRightB = j == n ? Integer.MAX_VALUE : nums2[j];

    if (maxLeftA <= minRightB && maxLeftB <= minRightA) {
    // total length is even
    if ((m + n) % 2 == 0) {
    return (double) (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2;
    } else {
    // total length is odd
    return (double) Math.max(maxLeftA, maxLeftB);
    }
    } else if (maxLeftA > minRightB) {
    // binary search left half
    hi = i - 1;
    } else {
    // binary search right half
    lo = i + 1;
    }
    }
    return 0.0;
    }
    }

    CPP Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Solution {
    public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    if (nums1.size() > nums2.size()) swap(nums1, nums2);
    int M = nums1.size(), N = nums2.size(), L = 0, R = M, K = (M + N + 1) / 2;
    while (true) {
    int i = (L + R) / 2, j = K - i;
    if (i < M && nums2[j - 1] > nums1[i]) L = i + 1;
    else if (i > L && nums1[i - 1] > nums2[j]) R = i - 1;
    else {
    int maxLeft = max(i ? nums1[i - 1] : INT_MIN, j ? nums2[j - 1] : INT_MIN);
    if ((M + N) % 2) return maxLeft;
    int minRight = min(i == M ? INT_MAX : nums1[i], j == N ? INT_MAX : nums2[j]);
    return (maxLeft + minRight) / 2.0;
    }
    }
    }
    };

    Python3 Code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
    N = len(nums1)
    M = len(nums2)
    if N > M:
    return self.findMedianSortedArrays(nums2, nums1)

    lo = 0
    hi = N
    combined = N + M

    while lo <= hi:
    mid1 = lo + hi >> 1
    mid2 = ((combined + 1) >> 1) - mid1

    leftp1 = -float("inf") if mid1 == 0 else nums1[mid1 - 1]
    rightp1 = float("inf") if mid1 == N else nums1[mid1]

    leftp2 = -float("inf") if mid2 == 0 else nums2[mid2 - 1]
    rightp2 = float("inf") if mid2 == M else nums2[mid2]

    # Check if the partition is valid for the case of
    if leftp1 <= rightp2 and leftp2 <= rightp1:
    if combined % 2 == 0:
    return (max(leftp1, leftp2)+min(rightp1, rightp2)) / 2.0

    return max(leftp1, leftp2)
    else:
    if leftp1 > rightp2:
    hi = mid1 - 1
    else:
    lo = mid1 + 1
    return -1

    复杂度分析

    • 时间复杂度:$O(log(min(m, n)))$
    • 空间复杂度:$O(log(min(m, n)))$

    大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。
    大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

    ]]>
    - 力扣加加 + 算法 + + 二分法 @@ -3868,9 +3878,11 @@ 算法 - 算法提高班 + 数学 - 力扣加加 + 中位数 + + 二分法 @@ -4068,10 +4080,10 @@ - 算法 - 数据结构 + 算法 + 数组 动态规划 @@ -4105,10 +4117,10 @@ - 算法 - 数据结构 + 算法 + hashtable DFS @@ -4210,10 +4222,10 @@ - 算法 - 数据结构 + 算法 + hashtable 动态规划 @@ -4808,14 +4820,14 @@ - 算法 - 数据结构 - 数组 + 算法 字符串 + 数组 + LeetCode 循环移位 @@ -4913,10 +4925,10 @@ - 算法 - 数据结构 + 算法 + 数组 动态规划 @@ -5066,10 +5078,10 @@ - 算法 - 数据结构 + 算法 + LeetCode 学习方法 @@ -5103,10 +5115,10 @@ - 算法 - 数据结构 + 算法 + hashtable 就地算法 @@ -5142,14 +5154,14 @@ - 算法 - 数据结构 - 数学 + 算法 hashtable + 数学 + 位运算 @@ -5200,10 +5212,10 @@ - 算法 - 数据结构 + 算法 + LeetCode 链表反转 @@ -5268,10 +5280,10 @@ - 算法 - 数据结构 + 算法 + 字符串 回文 @@ -5412,10 +5424,10 @@ 数学 - 概率 - 动态规划 + 概率 + 递归 @@ -5423,12 +5435,12 @@ - 数学 - 数据结构 算法 + 数学 + 概率 递归 diff --git a/tags/2022/index.html b/tags/2022/index.html index 14c967e5f2..52cf8f1b91 100644 --- a/tags/2022/index.html +++ b/tags/2022/index.html @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" "b/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" index 009373c531..5bdf7bb655 100644 --- "a/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" +++ "b/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/index.html" @@ -2187,6 +2187,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2335,7 +2337,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" "b/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" index 5d1ceb60f0..db2c96836f 100644 --- "a/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" +++ "b/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/2/index.html" @@ -2197,6 +2197,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2345,7 +2347,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" "b/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" index 838b3c2605..809f1b9960 100644 --- "a/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" +++ "b/tags/91\345\244\251\345\255\246\347\256\227\346\263\225/page/3/index.html" @@ -1347,6 +1347,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1495,7 +1497,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/AI/index.html b/tags/AI/index.html index cb3697cb4a..abbe78c97d 100644 --- a/tags/AI/index.html +++ b/tags/AI/index.html @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/AST/index.html b/tags/AST/index.html index 5e097945df..0b88e3eec3 100644 --- a/tags/AST/index.html +++ b/tags/AST/index.html @@ -918,6 +918,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1066,7 +1068,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/BFS/index.html b/tags/BFS/index.html index 6d23088435..e7c57d10a3 100644 --- a/tags/BFS/index.html +++ b/tags/BFS/index.html @@ -547,12 +547,12 @@

    @@ -1106,6 +1106,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1254,7 +1256,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/BigPipe/index.html b/tags/BigPipe/index.html index cf2a52ba74..b59e03b68d 100644 --- a/tags/BigPipe/index.html +++ b/tags/BigPipe/index.html @@ -914,6 +914,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1062,7 +1064,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/CD/index.html b/tags/CD/index.html index 1f99e268d9..b1a365e3e9 100644 --- a/tags/CD/index.html +++ b/tags/CD/index.html @@ -936,6 +936,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1084,7 +1086,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/CSS/index.html b/tags/CSS/index.html index 478a3f3760..0a0c87c4d6 100644 --- a/tags/CSS/index.html +++ b/tags/CSS/index.html @@ -918,6 +918,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1066,7 +1068,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/Canvas/index.html b/tags/Canvas/index.html index d5d5d4776d..72eb283aaa 100644 --- a/tags/Canvas/index.html +++ b/tags/Canvas/index.html @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/Chrome/index.html b/tags/Chrome/index.html index 2d96cb8d44..7dda65bfb1 100644 --- a/tags/Chrome/index.html +++ b/tags/Chrome/index.html @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/D2/index.html b/tags/D2/index.html index e0c49c376c..a91a98001a 100644 --- a/tags/D2/index.html +++ b/tags/D2/index.html @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/DFS/index.html b/tags/DFS/index.html index 438c65a79e..e68a5cde2f 100644 --- a/tags/DFS/index.html +++ b/tags/DFS/index.html @@ -973,6 +973,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1121,7 +1123,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git a/tags/Easy/index.html b/tags/Easy/index.html index d60a1f2192..2e30715075 100644 --- a/tags/Easy/index.html +++ b/tags/Easy/index.html @@ -346,7 +346,7 @@

    @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/Floyd-Warshall/index.html b/tags/Floyd-Warshall/index.html index 1092ce69b0..2382aecaee 100644 --- a/tags/Floyd-Warshall/index.html +++ b/tags/Floyd-Warshall/index.html @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/GitHub/index.html b/tags/GitHub/index.html index b9b74072c3..7702415ce5 100644 --- a/tags/GitHub/index.html +++ b/tags/GitHub/index.html @@ -1054,6 +1054,8 @@

    前言
    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1202,7 +1204,7 @@

    前言
    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/Google-IO/index.html b/tags/Google-IO/index.html index 82c76de7da..b0a6873ebb 100644 --- a/tags/Google-IO/index.html +++ b/tags/Google-IO/index.html @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/Hard/index.html b/tags/Hard/index.html index a2803159be..15ca297c5c 100644 --- a/tags/Hard/index.html +++ b/tags/Hard/index.html @@ -346,7 +346,7 @@

    @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/JSConf/index.html b/tags/JSConf/index.html index 6662b858e3..ad2d381248 100644 --- a/tags/JSConf/index.html +++ b/tags/JSConf/index.html @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/LeetCode/index.html b/tags/LeetCode/index.html index c106adbf62..27216e7e3a 100644 --- a/tags/LeetCode/index.html +++ b/tags/LeetCode/index.html @@ -2131,6 +2131,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2279,7 +2281,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/LeetCode/page/2/index.html b/tags/LeetCode/page/2/index.html index 5a27d4cd59..36cd71d500 100644 --- a/tags/LeetCode/page/2/index.html +++ b/tags/LeetCode/page/2/index.html @@ -2166,6 +2166,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2314,7 +2316,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/LeetCode/page/3/index.html b/tags/LeetCode/page/3/index.html index 196895f35e..29e896986e 100644 --- a/tags/LeetCode/page/3/index.html +++ b/tags/LeetCode/page/3/index.html @@ -986,8 +986,8 @@

    - - 一文看懂《最大子序列和问题》 + + 穿上衣服我就不认识你了?来聊聊最长上升子序列

    @@ -1027,9 +1027,9 @@

    @@ -1049,7 +1049,7 @@

      |   @@ -1057,7 +1057,7 @@

    @@ -1075,10 +1075,15 @@

    -

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    +

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    +

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    +

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    +
    +

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    +
    @@ -1206,15 +1209,10 @@

    -

    最长上升子序列是一个很经典的算法题。有的会直接让你求最长上升子序列,有的则会换个说法,但最终考察的还是最长上升子序列。那么问题来了,它穿上衣服你还看得出来是么?

    -

    如果你完全看不出来了,说明抽象思维还不到火候。经常看我的题解的同学应该会知道,我经常强调抽象思维。没有抽象思维,所有的题目对你来说都是新题。你无法将之前做题的经验迁移到这道题,那你做的题意义何在?

    -

    虽然抽象思维很难练成,但是幸好算法套路是有限的,经常考察的题型更是有限的。从这些入手,或许可以让你轻松一些。本文就从一个经典到不行的题型《最长上升子序列》,来帮你进一步理解抽象思维

    -
    -

    注意。 本文是帮助你识别套路,从横向上理清解题的思维框架,并没有采用最优解,所有的题目给的解法都不是最优的,但是都可以通过所有的测试用例。如果你想看最优解,可以直接去讨论区看。或者期待我的深入剖析系列

    -
    +

    最大子序列和是一道经典的算法题, leetcode 也有原题《53.maximum-sum-subarray》,今天我们就来彻底攻克它。

    - + 阅读全文 @@ -1224,11 +1222,13 @@

    @@ -2144,6 +2144,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2292,7 +2294,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git a/tags/LeetCode/page/4/index.html b/tags/LeetCode/page/4/index.html index 6cd2ec4f34..bb4421c9b5 100644 --- a/tags/LeetCode/page/4/index.html +++ b/tags/LeetCode/page/4/index.html @@ -866,7 +866,7 @@

    @@ -998,7 +998,7 @@

    @@ -1731,6 +1731,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1879,7 +1881,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/LeetCode\346\227\245\350\256\260/index.html" "b/tags/LeetCode\346\227\245\350\256\260/index.html" index ad1999e857..90cdff376e 100644 --- "a/tags/LeetCode\346\227\245\350\256\260/index.html" +++ "b/tags/LeetCode\346\227\245\350\256\260/index.html" @@ -362,9 +362,9 @@

    @@ -494,9 +494,9 @@

    @@ -760,7 +760,7 @@

    @@ -889,7 +889,7 @@

    @@ -1018,7 +1018,7 @@

    @@ -1150,7 +1150,7 @@

    @@ -1280,7 +1280,7 @@

    @@ -1412,7 +1412,7 @@

    @@ -1542,7 +1542,7 @@

    @@ -2123,6 +2123,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2271,7 +2273,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/Mac/index.html b/tags/Mac/index.html index ad92fbebb9..7dd21fbdad 100644 --- a/tags/Mac/index.html +++ b/tags/Mac/index.html @@ -916,6 +916,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1064,7 +1066,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/Medium/index.html b/tags/Medium/index.html index 5c1dea4030..247a652a02 100644 --- a/tags/Medium/index.html +++ b/tags/Medium/index.html @@ -346,9 +346,9 @@

    @@ -480,7 +480,7 @@

    @@ -1061,6 +1061,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1209,7 +1211,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/PPT/index.html b/tags/PPT/index.html index 191f1d7507..e042bbc7cd 100644 --- a/tags/PPT/index.html +++ b/tags/PPT/index.html @@ -1036,6 +1036,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1184,7 +1186,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/QCon/index.html b/tags/QCon/index.html index 659882cf01..204cc0e1c9 100644 --- a/tags/QCon/index.html +++ b/tags/QCon/index.html @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/RFC/index.html b/tags/RFC/index.html index 74c2dfdff1..75a3b4a977 100644 --- a/tags/RFC/index.html +++ b/tags/RFC/index.html @@ -916,6 +916,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1064,7 +1066,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/React/index.html b/tags/React/index.html index 0df388fecc..f9bc454e4a 100644 --- a/tags/React/index.html +++ b/tags/React/index.html @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/TypeScript/index.html b/tags/TypeScript/index.html index 944ebdbbae..3ea6a9494d 100644 --- a/tags/TypeScript/index.html +++ b/tags/TypeScript/index.html @@ -1998,6 +1998,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2146,7 +2148,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/VSCODE/index.html b/tags/VSCODE/index.html index e33af4580c..8faf276c4e 100644 --- a/tags/VSCODE/index.html +++ b/tags/VSCODE/index.html @@ -1183,6 +1183,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1331,7 +1333,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/css-in-js/index.html b/tags/css-in-js/index.html index 4cb009efc4..eb681ea54a 100644 --- a/tags/css-in-js/index.html +++ b/tags/css-in-js/index.html @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/eslint/index.html b/tags/eslint/index.html index 0e75185f3b..94c20baf58 100644 --- a/tags/eslint/index.html +++ b/tags/eslint/index.html @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/immutable/index.html b/tags/immutable/index.html index 1ddcc3472c..181c1c0096 100644 --- a/tags/immutable/index.html +++ b/tags/immutable/index.html @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/immutablejs/index.html b/tags/immutablejs/index.html index c1f32964d9..b77faad1bc 100644 --- a/tags/immutablejs/index.html +++ b/tags/immutablejs/index.html @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/index.html b/tags/index.html index 510439b93b..c7f3d73942 100644 --- a/tags/index.html +++ b/tags/index.html @@ -1362,6 +1362,15 @@

    所有标签

    + + + + (1) + + + + + @@ -2043,6 +2052,8 @@

    所有标签

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2191,7 +2202,7 @@

    所有标签

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/k-\351\227\256\351\242\230/index.html" "b/tags/k-\351\227\256\351\242\230/index.html" index e4d8518cf3..9e2551156a 100644 --- "a/tags/k-\351\227\256\351\242\230/index.html" +++ "b/tags/k-\351\227\256\351\242\230/index.html" @@ -948,6 +948,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1096,7 +1098,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/swc/index.html b/tags/swc/index.html index 8d761d5a62..331ddfad52 100644 --- a/tags/swc/index.html +++ b/tags/swc/index.html @@ -914,6 +914,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1062,7 +1064,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/vite/index.html b/tags/vite/index.html index 80547ad214..b4be611c96 100644 --- a/tags/vite/index.html +++ b/tags/vite/index.html @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/vue/index.html b/tags/vue/index.html index 3ff9c080bc..aa7713dcff 100644 --- a/tags/vue/index.html +++ b/tags/vue/index.html @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/web-component/index.html b/tags/web-component/index.html index 82ca3e56cf..d391f4f178 100644 --- a/tags/web-component/index.html +++ b/tags/web-component/index.html @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/webkit/index.html b/tags/webkit/index.html index ebd32cbe7a..3fcd11862c 100644 --- a/tags/webkit/index.html +++ b/tags/webkit/index.html @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/tags/webpack/index.html b/tags/webpack/index.html index 628c61a609..7d2a0b6ffe 100644 --- a/tags/webpack/index.html +++ b/tags/webpack/index.html @@ -914,6 +914,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1062,7 +1064,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" "b/tags/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" index 088e8e4036..ef8e70b9f3 100644 --- "a/tags/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" +++ "b/tags/\343\200\212\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257\343\200\213/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\270\255\344\275\215\346\225\260/index.html" "b/tags/\344\270\255\344\275\215\346\225\260/index.html" index 317e8e56c9..202cc30a48 100644 --- "a/tags/\344\270\255\344\275\215\346\225\260/index.html" +++ "b/tags/\344\270\255\344\275\215\346\225\260/index.html" @@ -406,12 +406,12 @@

    - 数学 - 数据结构 算法 + 数学 + 中位数 二分法 @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\270\255\347\255\211/index.html" "b/tags/\344\270\255\347\255\211/index.html" index c39754926c..843cc83199 100644 --- "a/tags/\344\270\255\347\255\211/index.html" +++ "b/tags/\344\270\255\347\255\211/index.html" @@ -1090,6 +1090,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1238,7 +1240,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\271\246/index.html" "b/tags/\344\271\246/index.html" index d1bea395a6..b6f944f259 100644 --- "a/tags/\344\271\246/index.html" +++ "b/tags/\344\271\246/index.html" @@ -1202,6 +1202,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1350,7 +1352,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\271\246\345\215\225/index.html" "b/tags/\344\271\246\345\215\225/index.html" index 259338d74e..934647d201 100644 --- "a/tags/\344\271\246\345\215\225/index.html" +++ "b/tags/\344\271\246\345\215\225/index.html" @@ -951,6 +951,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1099,7 +1101,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\271\246\346\221\230/index.html" "b/tags/\344\271\246\346\221\230/index.html" index 1616521690..e4f495df2a 100644 --- "a/tags/\344\271\246\346\221\230/index.html" +++ "b/tags/\344\271\246\346\221\230/index.html" @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\272\213\344\273\266/index.html" "b/tags/\344\272\213\344\273\266/index.html" index 7ed3a478f6..25d9979272 100644 --- "a/tags/\344\272\213\344\273\266/index.html" +++ "b/tags/\344\272\213\344\273\266/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\272\213\344\273\266\345\276\252\347\216\257/index.html" "b/tags/\344\272\213\344\273\266\345\276\252\347\216\257/index.html" index b0afe47c42..10d78dac1c 100644 --- "a/tags/\344\272\213\344\273\266\345\276\252\347\216\257/index.html" +++ "b/tags/\344\272\213\344\273\266\345\276\252\347\216\257/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\272\214\345\210\206/index.html" "b/tags/\344\272\214\345\210\206/index.html" index 96719fb077..e5446fc6af 100644 --- "a/tags/\344\272\214\345\210\206/index.html" +++ "b/tags/\344\272\214\345\210\206/index.html" @@ -1196,6 +1196,8 @@

    前言
    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1344,7 +1346,7 @@

    前言
    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\272\214\345\210\206\346\263\225/index.html" "b/tags/\344\272\214\345\210\206\346\263\225/index.html" index ae78d6f0ad..750aef8a80 100644 --- "a/tags/\344\272\214\345\210\206\346\263\225/index.html" +++ "b/tags/\344\272\214\345\210\206\346\263\225/index.html" @@ -406,12 +406,12 @@

    - 数学 - 数据结构 算法 + 数学 + 中位数 二分法 @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\272\214\345\217\211\346\240\221/index.html" "b/tags/\344\272\214\345\217\211\346\240\221/index.html" index 0608fc704b..e40580e550 100644 --- "a/tags/\344\272\214\345\217\211\346\240\221/index.html" +++ "b/tags/\344\272\214\345\217\211\346\240\221/index.html" @@ -1204,6 +1204,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1352,7 +1354,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\344\275\215\350\277\220\347\256\227/index.html" "b/tags/\344\275\215\350\277\220\347\256\227/index.html" index 5a59b09cb9..12b11ed19b 100644 --- "a/tags/\344\275\215\350\277\220\347\256\227/index.html" +++ "b/tags/\344\275\215\350\277\220\347\256\227/index.html" @@ -914,6 +914,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1062,7 +1064,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\210\240\351\231\244-k-\344\270\252\345\255\227\347\254\246/index.html" "b/tags/\345\210\240\351\231\244-k-\344\270\252\345\255\227\347\254\246/index.html" index e5fa9695fa..f8fb48a4d7 100644 --- "a/tags/\345\210\240\351\231\244-k-\344\270\252\345\255\227\347\254\246/index.html" +++ "b/tags/\345\210\240\351\231\244-k-\344\270\252\345\255\227\347\254\246/index.html" @@ -935,6 +935,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1083,7 +1085,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\210\267\351\242\230/index.html" "b/tags/\345\210\267\351\242\230/index.html" index 79c84cd0b8..bbb2252b9b 100644 --- "a/tags/\345\210\267\351\242\230/index.html" +++ "b/tags/\345\210\267\351\242\230/index.html" @@ -1189,6 +1189,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1337,7 +1339,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" "b/tags/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" index 9ad4a6d1cd..f75e8f68ca 100644 --- "a/tags/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" +++ "b/tags/\345\210\267\351\242\230\346\212\200\345\267\247/index.html" @@ -1564,6 +1564,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1712,7 +1714,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" "b/tags/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" index 650ec03e75..fa7aee5b0b 100644 --- "a/tags/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" +++ "b/tags/\345\210\267\351\242\230\346\226\271\346\263\225/index.html" @@ -1197,6 +1197,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1345,7 +1347,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\211\215\347\253\257/index.html" "b/tags/\345\211\215\347\253\257/index.html" index 37868020de..9e72bea47d 100644 --- "a/tags/\345\211\215\347\253\257/index.html" +++ "b/tags/\345\211\215\347\253\257/index.html" @@ -2194,6 +2194,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2342,7 +2344,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\211\215\347\253\257/page/2/index.html" "b/tags/\345\211\215\347\253\257/page/2/index.html" index a83b91a393..ba37ff7bb1 100644 --- "a/tags/\345\211\215\347\253\257/page/2/index.html" +++ "b/tags/\345\211\215\347\253\257/page/2/index.html" @@ -2192,6 +2192,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2340,7 +2342,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\211\215\347\253\257/page/3/index.html" "b/tags/\345\211\215\347\253\257/page/3/index.html" index 79c481d151..5c3f8549eb 100644 --- "a/tags/\345\211\215\347\253\257/page/3/index.html" +++ "b/tags/\345\211\215\347\253\257/page/3/index.html" @@ -1736,6 +1736,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1884,7 +1886,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\211\215\347\253\257\357\274\214\350\207\252\345\212\250\345\214\226\357\274\214automator/index.html" "b/tags/\345\211\215\347\253\257\357\274\214\350\207\252\345\212\250\345\214\226\357\274\214automator/index.html" index af14fdc475..b1794c8ffb 100644 --- "a/tags/\345\211\215\347\253\257\357\274\214\350\207\252\345\212\250\345\214\226\357\274\214automator/index.html" +++ "b/tags/\345\211\215\347\253\257\357\274\214\350\207\252\345\212\250\345\214\226\357\274\214automator/index.html" @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\211\215\347\274\200\345\222\214/index.html" "b/tags/\345\211\215\347\274\200\345\222\214/index.html" index b8c645e5d5..b35494cafc 100644 --- "a/tags/\345\211\215\347\274\200\345\222\214/index.html" +++ "b/tags/\345\211\215\347\274\200\345\222\214/index.html" @@ -1341,6 +1341,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1489,7 +1491,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\211\215\347\274\200\350\241\250\350\276\276\345\274\217/index.html" "b/tags/\345\211\215\347\274\200\350\241\250\350\276\276\345\274\217/index.html" index 4c7ba452dd..162fc26c29 100644 --- "a/tags/\345\211\215\347\274\200\350\241\250\350\276\276\345\274\217/index.html" +++ "b/tags/\345\211\215\347\274\200\350\241\250\350\276\276\345\274\217/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" "b/tags/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" index 5a9f707455..523d08f236 100644 --- "a/tags/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" +++ "b/tags/\345\212\233\346\211\243\345\212\240\345\212\240/index.html" @@ -2187,6 +2187,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2335,7 +2337,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" "b/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" index 16e296c05f..49e4e4d39c 100644 --- "a/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" +++ "b/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/2/index.html" @@ -2199,6 +2199,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2347,7 +2349,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" "b/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" index edc5125e88..8dd7f49d8a 100644 --- "a/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" +++ "b/tags/\345\212\233\346\211\243\345\212\240\345\212\240/page/3/index.html" @@ -1615,6 +1615,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1763,7 +1765,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" "b/tags/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" index d1c8a8e4c0..de811ff541 100644 --- "a/tags/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" +++ "b/tags/\345\212\250\346\200\201\350\247\204\345\210\222/index.html" @@ -755,7 +755,7 @@

    @@ -1420,7 +1420,7 @@

    @@ -1480,12 +1480,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -2005,6 +2005,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2153,7 +2155,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\215\225\345\205\203\346\265\213\350\257\225/index.html" "b/tags/\345\215\225\345\205\203\346\265\213\350\257\225/index.html" index 4b819b5236..165382d18e 100644 --- "a/tags/\345\215\225\345\205\203\346\265\213\350\257\225/index.html" +++ "b/tags/\345\215\225\345\205\203\346\265\213\350\257\225/index.html" @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\215\225\350\260\203\346\240\210/index.html" "b/tags/\345\215\225\350\260\203\346\240\210/index.html" index a4cf915415..443f70a17c 100644 --- "a/tags/\345\215\225\350\260\203\346\240\210/index.html" +++ "b/tags/\345\215\225\350\260\203\346\240\210/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\221\275\344\273\244\350\241\214/index.html" "b/tags/\345\221\275\344\273\244\350\241\214/index.html" index 9d2e10c7bc..d24cd81a34 100644 --- "a/tags/\345\221\275\344\273\244\350\241\214/index.html" +++ "b/tags/\345\221\275\344\273\244\350\241\214/index.html" @@ -1061,6 +1061,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1209,7 +1211,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\345\233\236\346\272\257/index.html" "b/tags/\345\233\236\346\272\257/index.html" index b31e2c6e50..187342f8f7 100644 --- "a/tags/\345\233\236\346\272\257/index.html" +++ "b/tags/\345\233\236\346\272\257/index.html" @@ -973,6 +973,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1121,7 +1123,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\345\233\260\351\232\276/index.html" "b/tags/\345\233\260\351\232\276/index.html" index 064a9e6af9..54cf69fd6f 100644 --- "a/tags/\345\233\260\351\232\276/index.html" +++ "b/tags/\345\233\260\351\232\276/index.html" @@ -1090,6 +1090,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1238,7 +1240,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\233\276/index.html" "b/tags/\345\233\276/index.html" index 8617088833..12bb98365f 100644 --- "a/tags/\345\233\276/index.html" +++ "b/tags/\345\233\276/index.html" @@ -1062,6 +1062,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1210,7 +1212,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\233\276\347\211\207\345\244\204\347\220\206/index.html" "b/tags/\345\233\276\347\211\207\345\244\204\347\220\206/index.html" index b2508e655b..ae25a39974 100644 --- "a/tags/\345\233\276\347\211\207\345\244\204\347\220\206/index.html" +++ "b/tags/\345\233\276\347\211\207\345\244\204\347\220\206/index.html" @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\240\206/index.html" "b/tags/\345\240\206/index.html" index 9d28bb3aae..c8a51a2c14 100644 --- "a/tags/\345\240\206/index.html" +++ "b/tags/\345\240\206/index.html" @@ -1079,6 +1079,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1227,7 +1229,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\245\275\346\234\252\346\235\245/index.html" "b/tags/\345\245\275\346\234\252\346\235\245/index.html" index 28f1e6ff87..7a4a91bb45 100644 --- "a/tags/\345\245\275\346\234\252\346\235\245/index.html" +++ "b/tags/\345\245\275\346\234\252\346\235\245/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\255\220\346\225\260\347\273\204/index.html" "b/tags/\345\255\220\346\225\260\347\273\204/index.html" index 7eae87b452..66d6bbf5a0 100644 --- "a/tags/\345\255\220\346\225\260\347\273\204/index.html" +++ "b/tags/\345\255\220\346\225\260\347\273\204/index.html" @@ -948,6 +948,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1096,7 +1098,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\255\227\347\254\246\344\270\262/index.html" "b/tags/\345\255\227\347\254\246\344\270\262/index.html" index 051bc1dc7c..e5cdc16296 100644 --- "a/tags/\345\255\227\347\254\246\344\270\262/index.html" +++ "b/tags/\345\255\227\347\254\246\344\270\262/index.html" @@ -346,7 +346,7 @@

    @@ -934,6 +934,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1082,7 +1084,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" "b/tags/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" index bafea7fe93..57ee5312ea 100644 --- "a/tags/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" +++ "b/tags/\345\255\227\350\212\202\350\267\263\345\212\250/index.html" @@ -307,8 +307,8 @@

    lucifer

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -350,7 +350,7 @@

    @@ -370,7 +370,7 @@

      |   @@ -378,7 +378,7 @@

    @@ -396,14 +396,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -533,11 +530,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -621,7 +621,7 @@

    @@ -1203,6 +1203,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1351,7 +1353,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" "b/tags/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" index 58b883b8e1..9c9212363e 100644 --- "a/tags/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" +++ "b/tags/\345\255\246\344\271\240\346\226\271\346\263\225/index.html" @@ -475,7 +475,7 @@

    @@ -1056,6 +1056,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1204,7 +1206,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\345\267\245\345\205\267/index.html" "b/tags/\345\267\245\345\205\267/index.html" index 128a4f9434..245bd7d572 100644 --- "a/tags/\345\267\245\345\205\267/index.html" +++ "b/tags/\345\267\245\345\205\267/index.html" @@ -1453,6 +1453,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1601,7 +1603,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" index da5b273667..9aad627a93 100644 --- "a/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" +++ "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\272\217\345\210\227\345\214\226/index.html" "b/tags/\345\272\217\345\210\227\345\214\226/index.html" index 152de1f388..1453369be0 100644 --- "a/tags/\345\272\217\345\210\227\345\214\226/index.html" +++ "b/tags/\345\272\217\345\210\227\345\214\226/index.html" @@ -935,6 +935,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1083,7 +1085,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\274\202\345\270\270\345\244\204\347\220\206/index.html" "b/tags/\345\274\202\345\270\270\345\244\204\347\220\206/index.html" index 785093749f..0622278b23 100644 --- "a/tags/\345\274\202\345\270\270\345\244\204\347\220\206/index.html" +++ "b/tags/\345\274\202\345\270\270\345\244\204\347\220\206/index.html" @@ -1056,6 +1056,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1204,7 +1206,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\274\202\350\256\256\357\274\201/index.html" "b/tags/\345\274\202\350\256\256\357\274\201/index.html" index acd444680d..f93c525c61 100644 --- "a/tags/\345\274\202\350\256\256\357\274\201/index.html" +++ "b/tags/\345\274\202\350\256\256\357\274\201/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" "b/tags/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" index 9eb2725190..84e911e1f3 100644 --- "a/tags/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" +++ "b/tags/\345\276\252\347\216\257\347\247\273\344\275\215/index.html" @@ -346,7 +346,7 @@

    @@ -934,6 +934,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1082,7 +1084,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\276\256\345\211\215\347\253\257/index.html" "b/tags/\345\276\256\345\211\215\347\253\257/index.html" index 5662c11645..ccf1887584 100644 --- "a/tags/\345\276\256\345\211\215\347\253\257/index.html" +++ "b/tags/\345\276\256\345\211\215\347\253\257/index.html" @@ -914,6 +914,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1062,7 +1064,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\345\277\205\345\244\207\350\275\257\344\273\266/index.html" "b/tags/\345\277\205\345\244\207\350\275\257\344\273\266/index.html" index ca205df3da..ba722bb43c 100644 --- "a/tags/\345\277\205\345\244\207\350\275\257\344\273\266/index.html" +++ "b/tags/\345\277\205\345\244\207\350\275\257\344\273\266/index.html" @@ -916,6 +916,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1064,7 +1066,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" "b/tags/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" index 3139e358b0..76a2fc3195 100644 --- "a/tags/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" +++ "b/tags/\346\210\220\351\225\277\347\273\217\345\216\206/index.html" @@ -1050,6 +1050,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1198,7 +1200,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\211\251\345\261\225\347\250\213\345\272\217/index.html" "b/tags/\346\211\251\345\261\225\347\250\213\345\272\217/index.html" index 3b1c2665a0..fefe34db90 100644 --- "a/tags/\346\211\251\345\261\225\347\250\213\345\272\217/index.html" +++ "b/tags/\346\211\251\345\261\225\347\250\213\345\272\217/index.html" @@ -928,6 +928,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/tags/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" "b/tags/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" index c35f5165da..ccc638881e 100644 --- "a/tags/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" +++ "b/tags/\346\212\200\346\234\257\345\244\247\344\274\232/index.html" @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\212\200\346\234\257\350\260\203\347\240\224/index.html" "b/tags/\346\212\200\346\234\257\350\260\203\347\240\224/index.html" index 8167679c41..e55c9eb0ef 100644 --- "a/tags/\346\212\200\346\234\257\350\260\203\347\240\224/index.html" +++ "b/tags/\346\212\200\346\234\257\350\260\203\347\240\224/index.html" @@ -916,6 +916,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1064,7 +1066,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\212\200\350\203\275/index.html" "b/tags/\346\212\200\350\203\275/index.html" index 05a4f8bbe1..73a0432539 100644 --- "a/tags/\346\212\200\350\203\275/index.html" +++ "b/tags/\346\212\200\350\203\275/index.html" @@ -1036,6 +1036,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1184,7 +1186,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\214\201\347\273\255\351\233\206\346\210\220/index.html" "b/tags/\346\214\201\347\273\255\351\233\206\346\210\220/index.html" index ca3d210a7f..37898319ca 100644 --- "a/tags/\346\214\201\347\273\255\351\233\206\346\210\220/index.html" +++ "b/tags/\346\214\201\347\273\255\351\233\206\346\210\220/index.html" @@ -923,6 +923,8 @@

    前言
    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    前言
    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\217\222\344\273\266/index.html" "b/tags/\346\217\222\344\273\266/index.html" index 2190d33dba..27c1ea17d6 100644 --- "a/tags/\346\217\222\344\273\266/index.html" +++ "b/tags/\346\217\222\344\273\266/index.html" @@ -1577,6 +1577,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1725,7 +1727,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\220\234\347\264\242/index.html" "b/tags/\346\220\234\347\264\242/index.html" index 1c43c9c405..97c8d9e225 100644 --- "a/tags/\346\220\234\347\264\242/index.html" +++ "b/tags/\346\220\234\347\264\242/index.html" @@ -973,6 +973,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1121,7 +1123,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/tags/\346\225\210\347\216\207/index.html" "b/tags/\346\225\210\347\216\207/index.html" index 819ab64b05..b2a3e704e6 100644 --- "a/tags/\346\225\210\347\216\207/index.html" +++ "b/tags/\346\225\210\347\216\207/index.html" @@ -929,6 +929,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 diff --git "a/tags/\346\225\260\345\255\246/index.html" "b/tags/\346\225\260\345\255\246/index.html" index 8401eb2ecb..697d135074 100644 --- "a/tags/\346\225\260\345\255\246/index.html" +++ "b/tags/\346\225\260\345\255\246/index.html" @@ -418,12 +418,12 @@

    @@ -549,12 +549,12 @@

    @@ -681,12 +681,12 @@

    - 数学 - 数据结构 算法 + 数学 + 中位数 二分法 @@ -755,7 +755,7 @@

    @@ -815,12 +815,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -1340,6 +1340,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1488,7 +1490,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" index e5e8538f9b..db497a4c13 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" @@ -2226,6 +2226,8 @@

    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2374,7 +2376,7 @@

    2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" index 38b3ba1ca9..d03da39f71 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/2/index.html" @@ -2230,6 +2230,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2378,7 +2380,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" index 0e0c3ed16e..e569245303 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/3/index.html" @@ -803,12 +803,12 @@

    @@ -1380,8 +1380,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -1423,7 +1423,7 @@

    @@ -1443,7 +1443,7 @@

      |   @@ -1451,7 +1451,7 @@

    @@ -1469,14 +1469,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -1606,11 +1603,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -2205,6 +2205,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2353,7 +2355,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/4/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/4/index.html" index df8ef6ee93..d47704f789 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/4/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/4/index.html" @@ -472,7 +472,7 @@

    @@ -793,12 +793,12 @@

    @@ -863,9 +863,9 @@

    @@ -997,7 +997,7 @@

    @@ -1085,8 +1085,8 @@

    - - 阿里面试题:如何寻找「两个数组」的中位数? + + 力扣加加闪亮登场~

    @@ -1126,9 +1126,9 @@

    @@ -1148,7 +1148,7 @@

      |   @@ -1156,7 +1156,7 @@

    @@ -1174,11 +1174,12 @@

    -

    一个数组的中位数很容易求,那两个数组呢?

    +

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    +

    @@ -1442,12 +1441,11 @@

    -

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    -

    +

    一个数组的中位数很容易求,那两个数组呢?

    @@ -2137,6 +2137,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2285,7 +2287,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/5/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/5/index.html" index abe5d864ff..f357cf259e 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/5/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/5/index.html" @@ -733,7 +733,7 @@

    @@ -862,7 +862,7 @@

    @@ -1261,7 +1261,7 @@

    @@ -2132,6 +2132,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2280,7 +2282,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/6/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/6/index.html" index 696f14ed6b..9d17bd7078 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/6/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/6/index.html" @@ -475,7 +475,7 @@

    @@ -614,7 +614,7 @@

    @@ -877,7 +877,7 @@

    @@ -1009,7 +1009,7 @@

    @@ -1141,7 +1141,7 @@

    @@ -1271,7 +1271,7 @@

    @@ -1404,7 +1404,7 @@

    @@ -2140,6 +2140,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2288,7 +2290,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/7/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/7/index.html" index f9d4e97872..7bb4d8a457 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/7/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/7/index.html" @@ -604,7 +604,7 @@

    @@ -664,12 +664,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -2132,6 +2132,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2280,7 +2282,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/8/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/8/index.html" index 41eed3cac8..2ed954098c 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/8/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204/page/8/index.html" @@ -935,6 +935,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1083,7 +1085,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214Hard/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214Hard/index.html" index 5acf044472..baee6b475f 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214Hard/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214Hard/index.html" @@ -1049,6 +1049,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1197,7 +1199,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214\344\270\255\347\255\211/index.html" "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214\344\270\255\347\255\211/index.html" index 5222f1f878..325ca06ca0 100644 --- "a/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214\344\270\255\347\255\211/index.html" +++ "b/tags/\346\225\260\346\215\256\347\273\223\346\236\204\357\274\214\347\256\227\346\263\225\357\274\214LeetCode-\346\227\245\350\256\260\357\274\214\344\270\255\347\255\211/index.html" @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\225\260\347\273\204/index.html" "b/tags/\346\225\260\347\273\204/index.html" index db82940e73..cd3388465b 100644 --- "a/tags/\346\225\260\347\273\204/index.html" +++ "b/tags/\346\225\260\347\273\204/index.html" @@ -348,7 +348,7 @@

    @@ -479,7 +479,7 @@

    @@ -1067,6 +1067,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1215,7 +1217,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\227\245\350\256\260/index.html" "b/tags/\346\227\245\350\256\260/index.html" index 29e1accac9..ab7c8a62ac 100644 --- "a/tags/\346\227\245\350\256\260/index.html" +++ "b/tags/\346\227\245\350\256\260/index.html" @@ -1047,6 +1047,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1195,7 +1197,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\230\245\346\213\233/index.html" "b/tags/\346\230\245\346\213\233/index.html" index 69317c370d..ddbdaa1667 100644 --- "a/tags/\346\230\245\346\213\233/index.html" +++ "b/tags/\346\230\245\346\213\233/index.html" @@ -1230,6 +1230,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1378,7 +1380,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260/index.html" "b/tags/\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260/index.html" index 4da282aac1..56aa9abc3f 100644 --- "a/tags/\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260/index.html" +++ "b/tags/\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260/index.html" @@ -412,12 +412,12 @@

    @@ -933,6 +933,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1081,7 +1083,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227/index.html" "b/tags/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227/index.html" index 2715f0fcc6..0cff0f791c 100644 --- "a/tags/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227/index.html" +++ "b/tags/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/index.html" "b/tags/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/index.html" index 3c48d6837f..b24b482340 100644 --- "a/tags/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/index.html" +++ "b/tags/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/index.html" @@ -930,6 +930,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1078,7 +1080,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\240\210/index.html" "b/tags/\346\240\210/index.html" index 81138a648c..512e87b142 100644 --- "a/tags/\346\240\210/index.html" +++ "b/tags/\346\240\210/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\240\221/index.html" "b/tags/\346\240\221/index.html" index 77e7703172..7f698aa062 100644 --- "a/tags/\346\240\221/index.html" +++ "b/tags/\346\240\221/index.html" @@ -931,6 +931,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1079,7 +1081,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\240\241\346\213\233/index.html" "b/tags/\346\240\241\346\213\233/index.html" index 490d39454f..e4d01ad899 100644 --- "a/tags/\346\240\241\346\213\233/index.html" +++ "b/tags/\346\240\241\346\213\233/index.html" @@ -1458,6 +1458,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1606,7 +1608,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\246\202\347\216\207/index.html" "b/tags/\346\246\202\347\216\207/index.html" index 498743070f..64fa516572 100644 --- "a/tags/\346\246\202\347\216\207/index.html" +++ "b/tags/\346\246\202\347\216\207/index.html" @@ -478,7 +478,7 @@

    @@ -538,12 +538,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -1063,6 +1063,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1211,7 +1213,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\250\241\345\235\227/index.html" "b/tags/\346\250\241\345\235\227/index.html" index 4717f6cc71..032de4f68d 100644 --- "a/tags/\346\250\241\345\235\227/index.html" +++ "b/tags/\346\250\241\345\235\227/index.html" @@ -922,6 +922,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1070,7 +1072,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" "b/tags/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" index f997cc107c..c08bac8b37 100644 --- "a/tags/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" +++ "b/tags/\346\250\241\346\213\237\351\235\242\350\257\225/index.html" @@ -924,6 +924,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1072,7 +1074,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\257\215\351\242\230/index.html" "b/tags/\346\257\215\351\242\230/index.html" index 99892d3db4..c43593482e 100644 --- "a/tags/\346\257\215\351\242\230/index.html" +++ "b/tags/\346\257\215\351\242\230/index.html" @@ -940,6 +940,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1088,7 +1090,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" "b/tags/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" index 7941ac7650..c4844b1faa 100644 --- "a/tags/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" +++ "b/tags/\346\257\217\346\227\245\344\270\200\350\215\220/index.html" @@ -1694,6 +1694,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1842,7 +1844,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\262\237\351\200\232/index.html" "b/tags/\346\262\237\351\200\232/index.html" index d6bbab7709..34bc109f54 100644 --- "a/tags/\346\262\237\351\200\232/index.html" +++ "b/tags/\346\262\237\351\200\232/index.html" @@ -923,6 +923,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1071,7 +1073,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\263\233\345\236\213/index.html" "b/tags/\346\263\233\345\236\213/index.html" index fdeef40155..03cfb08478 100644 --- "a/tags/\346\263\233\345\236\213/index.html" +++ "b/tags/\346\263\233\345\236\213/index.html" @@ -1088,6 +1088,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1236,7 +1238,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\265\213\350\257\225/index.html" "b/tags/\346\265\213\350\257\225/index.html" index ca64d4cf3b..b3c3e95a0f 100644 --- "a/tags/\346\265\213\350\257\225/index.html" +++ "b/tags/\346\265\213\350\257\225/index.html" @@ -927,6 +927,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1075,7 +1077,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\265\217\350\247\210\345\231\250/index.html" "b/tags/\346\265\217\350\247\210\345\231\250/index.html" index 2f94b5ae50..e6c484197e 100644 --- "a/tags/\346\265\217\350\247\210\345\231\250/index.html" +++ "b/tags/\346\265\217\350\247\210\345\231\250/index.html" @@ -1188,6 +1188,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1336,7 +1338,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" "b/tags/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" index 406f39d550..763f32b8fd 100644 --- "a/tags/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" +++ "b/tags/\346\273\221\345\212\250\347\252\227\345\217\243/index.html" @@ -464,8 +464,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -507,7 +507,7 @@

    @@ -527,7 +527,7 @@

      |   @@ -535,7 +535,7 @@

    @@ -553,14 +553,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -690,11 +687,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1484,6 +1484,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1632,7 +1634,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\346\273\244\351\225\234/index.html" "b/tags/\346\273\244\351\225\234/index.html" index dc4c3a068b..be327ed420 100644 --- "a/tags/\346\273\244\351\225\234/index.html" +++ "b/tags/\346\273\244\351\225\234/index.html" @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" "b/tags/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" index 888e38f289..e5cc756625 100644 --- "a/tags/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" +++ "b/tags/\347\212\266\346\200\201\345\216\213\347\274\251/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\212\266\346\200\201\346\234\272/index.html" "b/tags/\347\212\266\346\200\201\346\234\272/index.html" index ca7be58eb8..ec761f3791 100644 --- "a/tags/\347\212\266\346\200\201\346\234\272/index.html" +++ "b/tags/\347\212\266\346\200\201\346\234\272/index.html" @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git a/archives/2024/02/index.html "b/tags/\347\220\206\350\264\242/index.html" similarity index 94% rename from archives/2024/02/index.html rename to "tags/\347\220\206\350\264\242/index.html" index c6a6a3e054..07e48c138d 100644 --- a/archives/2024/02/index.html +++ "b/tags/\347\220\206\350\264\242/index.html" @@ -4,7 +4,7 @@ - Archives: 2024/2 | lucifer的网络博客 + Tag: 理财 | lucifer的网络博客 @@ -275,9 +275,11 @@

    lucifer

    -
    - - + +
    + + +
    @@ -290,7 +292,7 @@

    lucifer

    -
    @@ -1165,14 +1184,6 @@

    - - - - - - - - diff --git "a/tags/\347\224\265\345\255\220\344\271\246/index.html" "b/tags/\347\224\265\345\255\220\344\271\246/index.html" index e2d58f6c37..fb7ebae2e9 100644 --- "a/tags/\347\224\265\345\255\220\344\271\246/index.html" +++ "b/tags/\347\224\265\345\255\220\344\271\246/index.html" @@ -933,6 +933,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1081,7 +1083,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\224\265\345\275\261/index.html" "b/tags/\347\224\265\345\275\261/index.html" index 710d625ea7..f3510b0064 100644 --- "a/tags/\347\224\265\345\275\261/index.html" +++ "b/tags/\347\224\265\345\275\261/index.html" @@ -1048,6 +1048,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1196,7 +1198,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\231\276\345\272\246/index.html" "b/tags/\347\231\276\345\272\246/index.html" index 58045cf8ac..d3bfa488a1 100644 --- "a/tags/\347\231\276\345\272\246/index.html" +++ "b/tags/\347\231\276\345\272\246/index.html" @@ -929,6 +929,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1077,7 +1079,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\233\221\346\216\247/index.html" "b/tags/\347\233\221\346\216\247/index.html" index 608fa6ab90..6e98103f61 100644 --- "a/tags/\347\233\221\346\216\247/index.html" +++ "b/tags/\347\233\221\346\216\247/index.html" @@ -916,6 +916,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1064,7 +1066,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\247\213\346\213\233/index.html" "b/tags/\347\247\213\346\213\233/index.html" index c3119240fb..4b4877472f 100644 --- "a/tags/\347\247\213\346\213\233/index.html" +++ "b/tags/\347\247\213\346\213\233/index.html" @@ -912,6 +912,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1060,7 +1062,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225/index.html" "b/tags/\347\256\227\346\263\225/index.html" index a936f213a7..067403ab46 100644 --- "a/tags/\347\256\227\346\263\225/index.html" +++ "b/tags/\347\256\227\346\263\225/index.html" @@ -2216,6 +2216,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2364,7 +2366,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225/page/2/index.html" "b/tags/\347\256\227\346\263\225/page/2/index.html" index b9fb0354dc..53edefffb2 100644 --- "a/tags/\347\256\227\346\263\225/page/2/index.html" +++ "b/tags/\347\256\227\346\263\225/page/2/index.html" @@ -2228,6 +2228,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2376,7 +2378,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225/page/3/index.html" "b/tags/\347\256\227\346\263\225/page/3/index.html" index e0434e8ae0..f8c2abf0a2 100644 --- "a/tags/\347\256\227\346\263\225/page/3/index.html" +++ "b/tags/\347\256\227\346\263\225/page/3/index.html" @@ -1196,12 +1196,12 @@

    @@ -2192,6 +2192,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2340,7 +2342,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225/page/4/index.html" "b/tags/\347\256\227\346\263\225/page/4/index.html" index 7113d63d80..e0676976fe 100644 --- "a/tags/\347\256\227\346\263\225/page/4/index.html" +++ "b/tags/\347\256\227\346\263\225/page/4/index.html" @@ -561,8 +561,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -604,7 +604,7 @@

    @@ -624,7 +624,7 @@

      |   @@ -632,7 +632,7 @@

    @@ -650,14 +650,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -787,11 +784,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1007,7 +1007,7 @@

    @@ -1473,12 +1473,12 @@

    @@ -1543,9 +1543,9 @@

    @@ -2150,6 +2150,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2298,7 +2300,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\347\256\227\346\263\225/page/5/index.html" "b/tags/\347\256\227\346\263\225/page/5/index.html" index f73ea36374..9252123237 100644 --- "a/tags/\347\256\227\346\263\225/page/5/index.html" +++ "b/tags/\347\256\227\346\263\225/page/5/index.html" @@ -340,7 +340,7 @@

    @@ -428,8 +428,8 @@

    - - 阿里面试题:如何寻找「两个数组」的中位数? + + 力扣加加闪亮登场~

    @@ -469,9 +469,9 @@

    @@ -491,7 +491,7 @@

      |   @@ -499,7 +499,7 @@

    @@ -517,11 +517,12 @@

    -

    一个数组的中位数很容易求,那两个数组呢?

    +

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    +

    @@ -785,12 +784,11 @@

    -

    力扣加加,一个努力做西湖区最好的算法题解的团队。

    -

    +

    一个数组的中位数很容易求,那两个数组呢?

    @@ -1400,7 +1400,7 @@

    @@ -1529,7 +1529,7 @@

    @@ -2131,6 +2131,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2279,7 +2281,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\347\256\227\346\263\225/page/6/index.html" "b/tags/\347\256\227\346\263\225/page/6/index.html" index 160b83808c..a12a482a6d 100644 --- "a/tags/\347\256\227\346\263\225/page/6/index.html" +++ "b/tags/\347\256\227\346\263\225/page/6/index.html" @@ -610,7 +610,7 @@

    @@ -1262,7 +1262,7 @@

    @@ -1401,7 +1401,7 @@

    @@ -2137,6 +2137,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2285,7 +2287,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\347\256\227\346\263\225/page/7/index.html" "b/tags/\347\256\227\346\263\225/page/7/index.html" index 08f7da8a2d..2cd4a81656 100644 --- "a/tags/\347\256\227\346\263\225/page/7/index.html" +++ "b/tags/\347\256\227\346\263\225/page/7/index.html" @@ -340,7 +340,7 @@

    @@ -472,7 +472,7 @@

    @@ -604,7 +604,7 @@

    @@ -734,7 +734,7 @@

    @@ -867,7 +867,7 @@

    @@ -1394,7 +1394,7 @@

    @@ -1454,12 +1454,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -2136,6 +2136,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2284,7 +2286,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试

    diff --git "a/tags/\347\256\227\346\263\225/page/8/index.html" "b/tags/\347\256\227\346\263\225/page/8/index.html" index 486b12a08e..bf51b559b1 100644 --- "a/tags/\347\256\227\346\263\225/page/8/index.html" +++ "b/tags/\347\256\227\346\263\225/page/8/index.html" @@ -1720,6 +1720,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1868,7 +1870,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/index.html" "b/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/index.html" index 8f2a6620a6..82c18cd191 100644 --- "a/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/index.html" +++ "b/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/index.html" @@ -2187,6 +2187,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2335,7 +2337,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/2/index.html" "b/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/2/index.html" index f7ab162ed1..4810c268b4 100644 --- "a/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/2/index.html" +++ "b/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/2/index.html" @@ -2195,6 +2195,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -2343,7 +2345,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/3/index.html" "b/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/3/index.html" index ab51e3e583..da803e8f43 100644 --- "a/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/3/index.html" +++ "b/tags/\347\256\227\346\263\225\346\217\220\351\253\230\347\217\255/page/3/index.html" @@ -1482,6 +1482,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1630,7 +1632,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" "b/tags/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" index 4835a1663d..5804d6b0b1 100644 --- "a/tags/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" +++ "b/tags/\347\256\227\346\263\225\346\257\224\350\265\233/index.html" @@ -920,6 +920,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1068,7 +1070,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225\347\263\273\345\210\227/index.html" "b/tags/\347\256\227\346\263\225\347\263\273\345\210\227/index.html" index cf29969fdd..b0640c9d70 100644 --- "a/tags/\347\256\227\346\263\225\347\263\273\345\210\227/index.html" +++ "b/tags/\347\256\227\346\263\225\347\263\273\345\210\227/index.html" @@ -1332,6 +1332,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1480,7 +1482,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257/index.html" "b/tags/\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257/index.html" index ea45b14936..4c69573bd5 100644 --- "a/tags/\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257/index.html" +++ "b/tags/\347\256\227\346\263\225\351\200\232\345\205\263\344\271\213\350\267\257/index.html" @@ -917,6 +917,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1065,7 +1067,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\256\227\346\263\225\357\274\214\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210/index.html" "b/tags/\347\256\227\346\263\225\357\274\214\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210/index.html" index 4477bfab9e..11ba2f20ea 100644 --- "a/tags/\347\256\227\346\263\225\357\274\214\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210/index.html" +++ "b/tags/\347\256\227\346\263\225\357\274\214\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210/index.html" @@ -913,6 +913,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1061,7 +1063,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\272\277\346\256\265\346\240\221/index.html" "b/tags/\347\272\277\346\256\265\346\240\221/index.html" index 428c00f1d7..fee1f2006a 100644 --- "a/tags/\347\272\277\346\256\265\346\240\221/index.html" +++ "b/tags/\347\272\277\346\256\265\346\240\221/index.html" @@ -972,6 +972,8 @@

    背景
    事件
    (1)
    +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1120,7 +1122,7 @@

    背景
    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\273\204\344\273\266\345\214\226/index.html" "b/tags/\347\273\204\344\273\266\345\214\226/index.html" index a95f0a75d7..b476d92693 100644 --- "a/tags/\347\273\204\344\273\266\345\214\226/index.html" +++ "b/tags/\347\273\204\344\273\266\345\214\226/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" "b/tags/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" index 14b2512527..f547db5843 100644 --- "a/tags/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" +++ "b/tags/\347\273\217\351\252\214\345\210\206\344\272\253/index.html" @@ -1222,6 +1222,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1370,7 +1372,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" "b/tags/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" index b8ee325b12..dd5d599ce8 100644 --- "a/tags/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" +++ "b/tags/\347\274\226\347\250\213\344\271\213\347\276\216/index.html" @@ -346,7 +346,7 @@

    @@ -934,6 +934,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1082,7 +1084,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\215\211\347\250\277/index.html" "b/tags/\350\215\211\347\250\277/index.html" index d4e751607f..ec71c4ea5e 100644 --- "a/tags/\350\215\211\347\250\277/index.html" +++ "b/tags/\350\215\211\347\250\277/index.html" @@ -1063,6 +1063,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1211,7 +1213,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" "b/tags/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" index 4b6840d686..b6c8d42987 100644 --- "a/tags/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" +++ "b/tags/\350\223\204\346\260\264\346\261\240\346\212\275\346\240\267/index.html" @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\231\276\347\232\256/index.html" "b/tags/\350\231\276\347\232\256/index.html" index 03c50a8e7c..d5ab64d78e 100644 --- "a/tags/\350\231\276\347\232\256/index.html" +++ "b/tags/\350\231\276\347\232\256/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\243\205\346\234\272/index.html" "b/tags/\350\243\205\346\234\272/index.html" index 21191b77b0..04fea82237 100644 --- "a/tags/\350\243\205\346\234\272/index.html" +++ "b/tags/\350\243\205\346\234\272/index.html" @@ -916,6 +916,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1064,7 +1066,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" "b/tags/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" index 7aa9a2b5d5..6ee0e46e77 100644 --- "a/tags/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" +++ "b/tags/\350\247\243\351\242\230\346\250\241\346\235\277/index.html" @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\260\267\346\255\214/index.html" "b/tags/\350\260\267\346\255\214/index.html" index 1ff2efccbb..b851e5e9de 100644 --- "a/tags/\350\260\267\346\255\214/index.html" +++ "b/tags/\350\260\267\346\255\214/index.html" @@ -1200,6 +1200,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1348,7 +1350,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\264\252\345\251\252/index.html" "b/tags/\350\264\252\345\251\252/index.html" index 064bb51c86..913249eb44 100644 --- "a/tags/\350\264\252\345\251\252/index.html" +++ "b/tags/\350\264\252\345\251\252/index.html" @@ -925,6 +925,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1073,7 +1075,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\264\252\345\277\203/index.html" "b/tags/\350\264\252\345\277\203/index.html" index cee1e8709b..bd182fd675 100644 --- "a/tags/\350\264\252\345\277\203/index.html" +++ "b/tags/\350\264\252\345\277\203/index.html" @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" "b/tags/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" index e28b1cf4ed..feffc5cc23 100644 --- "a/tags/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" +++ "b/tags/\350\265\260\345\207\272\347\247\221\345\255\246/index.html" @@ -921,6 +921,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1069,7 +1071,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\351\200\206\345\220\221\346\200\235\347\273\264/index.html" "b/tags/\351\200\206\345\220\221\346\200\235\347\273\264/index.html" index 5e105f3163..5ceba849a1 100644 --- "a/tags/\351\200\206\345\220\221\346\200\235\347\273\264/index.html" +++ "b/tags/\351\200\206\345\220\221\346\200\235\347\273\264/index.html" @@ -928,6 +928,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1076,7 +1078,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\351\200\222\345\275\222/index.html" "b/tags/\351\200\222\345\275\222/index.html" index c655d06068..1ae145963b 100644 --- "a/tags/\351\200\222\345\275\222/index.html" +++ "b/tags/\351\200\222\345\275\222/index.html" @@ -346,7 +346,7 @@

    @@ -406,12 +406,12 @@

    - 数学 - 数据结构 算法 + 数学 + 概率 递归 @@ -931,6 +931,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1079,7 +1081,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\351\223\276\350\241\250/index.html" "b/tags/\351\223\276\350\241\250/index.html" index 72fddc3399..0fa83d750e 100644 --- "a/tags/\351\223\276\350\241\250/index.html" +++ "b/tags/\351\223\276\350\241\250/index.html" @@ -486,7 +486,7 @@

    @@ -625,7 +625,7 @@

    @@ -1207,6 +1207,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1355,7 +1357,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\351\231\267\351\230\261\351\242\230/index.html" "b/tags/\351\231\267\351\230\261\351\242\230/index.html" index cf260b5981..6eeea08a7a 100644 --- "a/tags/\351\231\267\351\230\261\351\242\230/index.html" +++ "b/tags/\351\231\267\351\230\261\351\242\230/index.html" @@ -926,6 +926,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1074,7 +1076,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\351\235\242\347\273\217/index.html" "b/tags/\351\235\242\347\273\217/index.html" index c66ea84788..3e670a3a4a 100644 --- "a/tags/\351\235\242\347\273\217/index.html" +++ "b/tags/\351\235\242\347\273\217/index.html" @@ -1329,6 +1329,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1477,7 +1479,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试
    diff --git "a/tags/\351\235\242\350\257\225/index.html" "b/tags/\351\235\242\350\257\225/index.html" index 6c94c5315f..5a3ecfa8f6 100644 --- "a/tags/\351\235\242\350\257\225/index.html" +++ "b/tags/\351\235\242\350\257\225/index.html" @@ -695,8 +695,8 @@

    - - 字节跳动的算法面试题是什么难度? + + 字节跳动的算法面试题是什么难度?(第二弹)

    @@ -738,7 +738,7 @@

    @@ -758,7 +758,7 @@

      |   @@ -766,7 +766,7 @@

    @@ -784,14 +784,11 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    -
    -

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    -
    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    @@ -921,11 +918,14 @@

    -

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 字节跳动2017秋招编程题汇总来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/6035789/summary

    +

    由于 lucifer 我是一个小前端, 最近也在准备写一个《前端如何搞定算法面试》的专栏,因此最近没少看各大公司的面试题。都说字节跳动算法题比较难,我就先拿 ta 下手,做了几套 。这次我们就拿一套 2018 年的前端校招(第四批)来看下字节的算法笔试题的难度几何。地址:https://www.nowcoder.com/test/8536639/summary

    +
    +

    实际上,这套字节的前端岗位笔试题和后端以及算法岗位的笔试题也只有一道题目(红包的设计题被换成了另外一个设计题)不一样而已,因此也不需要担心你不是前端,题目类型和难度和你的岗位不匹配。

    +

    - + 阅读全文 @@ -1458,6 +1458,8 @@

  • 事件
    (1)
  • +
  • 理财
    (1)
  • +
  • 电影
    (2)
  • 观后感
    (2)
  • @@ -1606,7 +1608,7 @@

    - 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试 + 2022 91天学算法 AI AST BFS BigPipe CD CSS Canvas Chrome D2 DFS Easy Floyd-Warshall GitHub Github Google IO Hard JSConf LeetCode LeetCode日记 Mac Medium PPT QCon RFC React TypeScript VSCODE chrome css-in-js eslint immutable immutablejs k 问题 swc vite vue web-component webkit webpack 《算法通关之路》 中位数 中等 书单 书摘 事件 事件循环 二分 二分法 二叉树 位运算 删除 k 个字符 刷题 刷题技巧 刷题方法 前端 前端,自动化,automator 前缀和 前缀表达式 力扣加加 动态规划 单元测试 单调栈 命令行 回溯 困难 图片处理 好未来 子数组 字符串 字节跳动 学习方法 工具 年终总结 序列化 异常处理 异议! 循环移位 微前端 必备软件 成长经历 扩展程序 技术大会 技术调研 技能 持续集成 插件 搜索 效率 数学 数据结构 数据结构,算法,LeetCode 日记,Hard 数据结构,算法,LeetCode 日记,中等 数组 日记 春招 最大公约数 最长上升子序列 最长公共子序列 校招 概率 模块 模拟面试 母题 每日一荐 沟通 泛型 测试 浏览器 滑动窗口 滤镜 状态压缩 状态机 理财 电子书 电影 百度 监控 秋招 算法 算法提高班 算法比赛 算法系列 算法通关之路 算法,最近公共祖先 线段树 组件化 经验分享 编程之美 草稿 蓄水池抽样 虾皮 装机 解题模板 谷歌 贪婪 贪心 走出科学 逆向思维 递归 链表 陷阱题 面经 面试